Parquet functions
QuestDB can read and query external Apache Parquet files using SQL.
To export data as Parquet, see Parquet Export.
read_parquet
Reads a parquet file as a table.
read_parquet(parquet_file_path)
Usage
The file path must be within the configured root directory. It can be specified as a relative path (resolved under the root) or as an absolute path (which must still start with the root directory). Path traversal (../) is not allowed.
SELECT * FROM read_parquet('trades.parquet')
WHERE side = 'buy'
LIMIT 1;
| symbol | side | price | amount | timestamp |
|---|---|---|---|---|
| BTC-USD | buy | 62755.6 | 0.00043367 | 2024-07-01T00:46:39.754075Z |
SELECT * FROM read_parquet('/var/lib/questdb/import/trades.parquet');
Designated timestamp
Parquet files carry no notion of a
designated timestamp, so read_parquet
returns a table without one. Time-series clauses such as SAMPLE BY reject that
input:
SELECT timestamp, symbol, side,
avg(price) AS avg_price,
avg(amount) AS avg_amount
FROM read_parquet('trades.parquet')
WHERE side = 'buy'
SAMPLE BY 1m;
base query does not provide designated TIMESTAMP column
Nominate the timestamp column with
TIMESTAMP().
It can be applied directly to the read_parquet() call:
SELECT timestamp, symbol, side,
avg(price) AS avg_price,
avg(amount) AS avg_amount
FROM read_parquet('trades.parquet') TIMESTAMP(timestamp)
WHERE side = 'buy'
SAMPLE BY 1m;
It works equally on a wrapping sub-query, which is useful when the read is already nested:
SELECT timestamp, symbol, side,
avg(price) AS avg_price,
avg(amount) AS avg_amount
FROM (
SELECT * FROM read_parquet('trades.parquet')
WHERE side = 'buy'
) TIMESTAMP(timestamp)
SAMPLE BY 1m;
Or on a CTE:
WITH buys AS (
SELECT * FROM read_parquet('trades.parquet')
WHERE side = 'buy'
)
SELECT timestamp, symbol, side,
avg(price) AS avg_price,
avg(amount) AS avg_amount
FROM buys TIMESTAMP(timestamp)
SAMPLE BY 1m;
Time-series joins need the same treatment. ASOF JOIN rejects a Parquet file on
its left side with left side of time series join has no timestamp until the
column is nominated. Note that the table alias comes before the TIMESTAMP()
clause:
SELECT p.timestamp, p.symbol,
p.price AS archived_price,
t.price AS live_price
FROM read_parquet('trades.parquet') p TIMESTAMP(timestamp)
ASOF JOIN trades t ON (symbol);
In practice most non-trivial read_parquet queries need this override, so it is
worth applying it by default.
TIMESTAMP() assumes the rows are already ordered by that column. If the file
is not sorted on its timestamp column, add ORDER BY inside the sub-query
before applying TIMESTAMP(), otherwise the results will be wrong.
SELECT timestamp, symbol, avg(price) AS avg_price
FROM (
SELECT * FROM read_parquet('trades.parquet')
ORDER BY timestamp
) TIMESTAMP(timestamp)
SAMPLE BY 1h;
Importing a Parquet file into a table
Combine read_parquet with INSERT INTO to load a
file into a QuestDB table.
When the target table already exists, its own designated timestamp governs the
write, so a straight column-for-column load needs no TIMESTAMP() override:
INSERT INTO trades
SELECT symbol, side, price, amount, timestamp
FROM read_parquet('trades.parquet');
Add the override when the SELECT itself uses a time-series clause, such as
aggregating the file into one-minute buckets on the way in:
INSERT INTO trades_1m
SELECT timestamp, symbol, side,
avg(price) AS avg_price,
avg(amount) AS avg_amount
FROM (
SELECT * FROM read_parquet('trades.parquet')
WHERE side = 'buy'
) TIMESTAMP(timestamp)
SAMPLE BY 1m;
Creating the target table
If the table does not exist yet,
CREATE TABLE AS infers the
schema from the Parquet file. Here the TIMESTAMP() clause is mandatory:
without it the new table inherits the sub-query's lack of a designated
timestamp.
CREATE TABLE trades AS (
SELECT * FROM read_parquet('trades.parquet')
) TIMESTAMP(timestamp) PARTITION BY DAY;
Omitting TIMESTAMP() does not raise an error. It silently creates a
non-partitioned, non-WAL table with no designated timestamp, so SAMPLE BY and
other time-series clauses fail against it afterwards. Adding PARTITION BY
without TIMESTAMP() does fail, with
partitioning is possible only on tables with designated timestamps.
The designated timestamp
cannot be set after table creation,
so recovering means recreating the table and copying the data across. Get it
right in the CREATE TABLE statement.
Inference is convenient but it only gives you what the file describes. Parquet
string columns land as VARCHAR, and nothing in the file tells QuestDB about
deduplication, indexes, or symbol capacities. Create the table up front when you
care about any of that:
CREATE TABLE trades (
symbol SYMBOL CAPACITY 256,
side SYMBOL,
price DOUBLE,
amount DOUBLE,
timestamp TIMESTAMP
) TIMESTAMP(timestamp) PARTITION BY DAY
DEDUP UPSERT KEYS(timestamp, symbol);
INSERT INTO trades
SELECT symbol, side, price, amount, timestamp
FROM read_parquet('trades.parquet');
With deduplication enabled the import is idempotent, so re-running it after a partial failure will not duplicate rows.
Configuration
For security reasons, reading is only allowed from a configured directory. By default, this is the import directory
inside the QuestDB root directory (e.g. /var/lib/questdb/import/). To change it, set cairo.sql.copy.root:
- In
server.conf:cairo.sql.copy.root=/path/to/dir - Or via the environment variable
QDB_CAIRO_SQL_COPY_ROOT
Limitations
Parquet format supports a rich set of data types, including structural types. QuestDB can only read Parquet columns whose types map to QuestDB types:
- Boolean
- Byte
- Short
- Char
- Int
- Long
- Long128
- Long256
- Float
- Double
- Varchar (also reads Symbol columns as Varchar)
- Timestamp
- Date
- UUID
- IPv4
- GeoHash (Byte, Short, Int, Long)
- Binary
- Array (Double)
Parquet columns with unsupported data types are ignored.
Only a single file can be read per read_parquet call.
Related documentation
- Parquet covers in-place partition conversion, compression, bloom filters, and exporting QuestDB data as Parquet.
- Partition format shows how
to store a table's partitions as Parquet with
FORMAT PARQUET. - Designated timestamp explains what the designated timestamp is and what it enables.
- INSERT documents
INSERT INTO ... SELECT, including theATOMICandBATCHkeywords.