Every example below uses one small shop: an file, an file, and a database holding and .
Reading Files
DuckDB queries files in place. A quoted path works anywhere a table name does.
| Command | What it does |
|---|---|
| Query a file directly, format picked from the extension | |
| Same query, FROM-first shorthand for | |
| Read CSV with auto-detected delimiter, header and types (file not found?) | |
| Name columns of a headerless file | |
| Force a column type instead of the guess | |
| Parse day-first dates | |
| Sniff types from every row, not the first 20,480 | |
| Skip rows that fail to parse or cast | |
| Show what the sniffer detected, as a reusable query | |
| Combine files with different columns, tag the source file | |
| Read many Parquet files as one table | |
| Turn folders into columns | |
| Inspect Parquet column types without reading data | |
| Read JSON arrays or newline-delimited JSON | |
| Read one sheet of an Excel file | |
| Query S3 directly, needs httpfs and a secret | |
| List the columns and types DuckDB inferred |
Example: revenue by order status since September 1, queried straight from .
SELECT status, count(*) AS orders, sum(amount) AS revenue
FROM read_csv('orders.csv')
WHERE created_at >= DATE '2026-09-01'
GROUP BY ALL
ORDER BY revenue DESC;
In 1bench: connect straight to a Parquet, CSV or JSON file at an S3 or HTTPS URL and query it as a table, with S3, MinIO and R2 credentials in the connection form. See the DuckDB GUI.
CLI Commands
Shell flags first, then the dot commands you type inside the prompt.
| Command | What it does |
|---|---|
| Open a database file, creating it if missing | |
| Start an in-memory session, nothing is saved | |
| Open without write access, several readers can share the file | |
| Run one statement and exit | |
| Run a SQL file and exit | |
| Open the local web UI in a browser (not loading?) | |
| List tables and views with their columns | |
| Show column names and types | |
| Min, max, nulls and quantiles for every column | |
| Print the CREATE statement | |
| Run a SQL file inside the session | |
| Switch output: , , , , | |
| Show more than the default 40 rows in duckbox mode | |
| Re-print the previous result without truncation | |
| Print run time after each query | |
| Send only the next result to a file | |
| List attached databases and their files | |
| Leave the shell, as do and Ctrl+D | |
| Cap memory, default is 80% of RAM (out of memory?) | |
| Limit CPU threads | |
| Choose where larger-than-memory work spills | |
| Cut memory on big imports and exports | |
| Read one setting; lists all |
Example: export customers ranked by total spend to a CSV file with one shell command.
duckdb shop.duckdb -csv -c "
SELECT customer_id, sum(amount) AS total
FROM orders GROUP BY ALL ORDER BY total DESC" > top_customers.csv
In 1bench: open and the sidebar lists every table and view with its row count, and each table's Schema tab shows its columns and types, so you skip and . See the DuckDB GUI.
Functions
The functions that come up in almost every query. The full list belongs on its own page.
| Function | Example | Returns |
|---|---|---|
| Joined string, NULLs skipped | ||
| as a list | ||
| First capture group | ||
| First instant of the month | ||
| as text | ||
| , | , | , |
| Timestamp one week ago | ||
| Conditional count | ||
| Order ID of the largest order | ||
| One comma-separated string | ||
| , | 90th percentile | |
| 1, 2, 3 per customer | ||
| Previous order amount | ||
| JSON field as text | ||
| or | ||
| New list, each value mapped | ||
| List comprehension | ||
| First non-NULL value |
Example: order count, revenue and refunds per month from the table.
SELECT strftime(date_trunc('month', created_at), '%Y-%m') AS month,
count(*) AS orders,
round(sum(amount), 2) AS revenue,
count(*) FILTER (WHERE status = 'refunded') AS refunds
FROM orders
GROUP BY ALL
ORDER BY month;
Filtering and Sorting
| Command | What it does |
|---|---|
| Match any value in a list | |
| Drop rows with a missing value | |
| Pattern match, case-sensitive | |
| Pattern match, case-insensitive | |
| Regular expression match | |
| Inclusive range on both ends | |
| Rows with no match in another table | |
| Filter on an aggregate (not in WHERE) | |
| Top N per group without a subquery | |
| Sort with NULLs at the end | |
| Sort by every column, left to right | |
| Page through results | |
| Random sample of a big table |
Example: each customer's two largest non-refunded orders, using instead of a subquery.
-- each customer's two largest orders
SELECT customer_id, order_id, amount
FROM orders
WHERE status <> 'refunded'
QUALIFY row_number() OVER (PARTITION BY customer_id ORDER BY amount DESC) <= 2
ORDER BY customer_id, amount DESC;
Tables and Writes
| Command | What it does |
|---|---|
| Create and load a table from a file | |
| Define a table by hand | |
| DestructiveDrops the old table and its data first | |
| Session-only table, gone on exit | |
| Append rows from a file | |
| Match columns by name, not position | |
| Upsert on a key | |
| Skip rows whose key exists | |
| Update matches, insert the rest in one pass | |
| Change rows in place | |
| DestructiveRemove matching rows, all rows without WHERE | |
| DestructiveEmpty the table | |
| DestructiveRemove the table and its data | |
| Add a column; and also work | |
| Saved query, no stored data | |
| Reusable SQL function | |
| Group writes in one transaction, undoes |
Example: load new orders into a temp table, then them to update statuses and add new rows.
CREATE TEMP TABLE staging AS FROM 'orders_new.csv';
MERGE INTO orders o
USING staging s ON o.order_id = s.order_id
WHEN MATCHED THEN UPDATE SET status = s.status
WHEN NOT MATCHED THEN INSERT BY NAME;
Joins and Reshaping
| Command | What it does |
|---|---|
| Inner join on a shared column name | |
| Keep every left row | |
| Left rows with no match | |
| Left rows with a match, no duplicates | |
| Join to the latest earlier event | |
| Group by every non-aggregated column | |
| All columns but some | |
| All columns, one rewritten | |
| Apply one expression to columns matching a regex | |
| Rows to columns | |
| Columns to rows | |
| One row per list element | |
| Flatten a list of structs into columns | |
| Stack queries, matching columns by name |
Example: pivot orders into one row per month with a revenue column for each status.
PIVOT (
SELECT strftime(created_at, '%Y-%m') AS month, status, amount FROM orders
)
ON status USING sum(amount)
GROUP BY month
ORDER BY month;
Exporting
| Command | What it does |
|---|---|
| DestructiveWrite Parquet, silently overwrites an existing file | |
| Smaller Parquet files | |
| Export a query result to CSV | |
| Newline-delimited JSON; add for one array | |
| Write Excel, run first | |
| Write hive-partitioned folders | |
| DestructiveClears the target folder before writing | |
| Load a file into an existing table | |
| Dump every table plus schema SQL | |
| Restore an exported database |
Example: write the orders table to Parquet folders partitioned by year and month.
COPY (
SELECT *, year(created_at) AS year, month(created_at) AS month
FROM orders
) TO 'lake/orders' (FORMAT parquet, PARTITION_BY (year, month), OVERWRITE_OR_IGNORE);
In 1bench: export any table to CSV, JSON, NDJSON or SQL from a dialog, keeping your current filter, sort and visible columns, no needed. See the DuckDB GUI.
ATTACH
ATTACH opens another database file, or a live SQLite, PostgreSQL or MySQL database, next to the current one.
| Command | What it does |
|---|---|
| Open a second DuckDB file, creating it if missing | |
| No error if already attached | |
| Attach without write access | |
| Query a SQLite file as tables | |
| Query a live PostgreSQL database | |
| Query a live MySQL database | |
| Qualify tables in other databases (catalog error?) | |
| Make it the default for unqualified names | |
| List attached databases | |
| Copy every table across | |
| Close it |
Example: attach a live Postgres database read-only and join its customers to local Parquet events.
ATTACH 'dbname=shop host=localhost user=app' AS pg (TYPE postgres, READ_ONLY);
-- join live Postgres customers to local Parquet events
SELECT c.email, count(*) AS events
FROM pg.public.customers c
JOIN 'events.parquet' e ON e.user_id = c.customer_id
GROUP BY ALL
ORDER BY events DESC;
Extensions
Core extensions autoload on first use. Install them ahead of time for offline machines, or by hand when autoload fails.
| Command | What it does |
|---|---|
| List extensions, installed and loaded state | |
| Install and load by hand (download failed?) | |
| Install a community extension | |
| Upgrade installed extensions | |
| Read and write , , paths | |
| and | |
| GEOMETRY type, functions, GDAL file formats | |
| and Iceberg catalogs | |
| DuckLake lakehouse tables | |
| , , | ATTACH and scan those databases |
| Full-text search with BM25 ranking | |
| HNSW indexes for vector similarity search | |
| Use your AWS credentials for S3 | |
| Keep the secret across sessions |
Example: count the last 30 days of S3 Parquet events by type, signed in with your AWS credentials.
CREATE SECRET (TYPE s3, PROVIDER credential_chain);
SELECT event, count(*) AS n
FROM 's3://shop-data/events/*.parquet'
WHERE ts >= now() - INTERVAL 30 DAY
GROUP BY ALL
ORDER BY n DESC;
Data Types
| Type or cast | Example | Returns |
|---|---|---|
| / | ||
| instead of a cast error | ||
| , | 64-bit and 128-bit integers | |
| Exact money values | ||
| / | Local time versus a point in time | |
| TIMESTAMPTZ from epoch seconds | ||
| Seconds since 1970 | ||
| List | , indexes start at 1 | |
| Struct | ||
| List of structs | ||
| Map | ||
| Fixed set of strings, stored compactly |
Example: find the rows in whose amount or timestamp would fail a typed load.
-- find the rows that would break a typed load
SELECT order_id, amount, created_at
FROM read_csv('orders.csv', all_varchar = true)
WHERE TRY_CAST(amount AS DECIMAL(10,2)) IS NULL
OR TRY_CAST(created_at AS TIMESTAMP) IS NULL;
Python API
(install failing?). Deeper coverage goes on a separate Python page.
| Command | What it does |
|---|---|
| Query on the default in-memory connection | |
| Open a database file (no attribute connect?) | |
| Open read-only, several processes can share it | |
| Lazy relation, runs when you fetch or show it | |
| Run now with bound parameters | |
| , , | Fetch as pandas, Polars or Arrow |
| , | Fetch as Python tuples |
| Query a pandas or Polars DataFrame by its variable name | |
| Expose a DataFrame under a fixed name | |
| Write a relation to Parquet; too | |
| Release the file lock, or use |
Example: fetch the top 10 paid customers as a pandas DataFrame and save them to Parquet.
import duckdb
with duckdb.connect("shop.duckdb") as con:
top = con.execute(
"SELECT customer_id, sum(amount) AS total FROM orders "
"WHERE status = ? GROUP BY ALL ORDER BY total DESC LIMIT 10",
["paid"],
).df()
con.sql("FROM top").write_parquet("top_customers.parquet")
Gotchas
- Only one process can open a file for writing. A second writer, or a reader while a writer holds it, fails with a lock error. Open readers with or , and close the Python connection when done.
- with no file name is in-memory. Everything you create is lost on unless you passed a path or ran .
- overwrites an existing file without asking, and drops the old table first. Partitioned writes refuse a non-empty folder unless you add , which deletes what was there.
- The CSV sniffer reads the first 20,480 rows. A bad value further down, like in a number column, fails the whole read with a cast error. Use , set , or load with and .
- The arrow lambda is deprecated in 1.5 and prints a warning. Write .
- fails when a table has both a primary key and a separate column. Use and name the key.