This is DuckDB's killer feature: answer questions about data files without an import step.
DuckDB treats a file path as a table. Write SELECT * FROM 'sales.csv' and it detects the format from the extension, infers column names and types, and streams the file through the query. The same works for .parquet, .json and compressed files like .csv.gz.
Behind that shortcut are table functions you can call explicitly when you need options: read_csv, read_parquet and read_json. A few patterns you will use daily:
DESCRIBE SELECT * FROM 'file.parquet'shows inferred column names and typesSUMMARIZE 'file.csv'prints min, max, distinct count, nulls and quantiles for each columnCREATE TABLE t AS FROM 'file.csv'imports a file once if you will query it many times
Querying in place is great for exploration, but each query re-reads (and for CSV re-parses) the file. Parquet is columnar and compressed, so DuckDB reads only the columns and row groups a query needs; CSV must be parsed in full. If you run many queries over a big CSV, import it into a table or convert it to Parquet first.
Paths are relative to the process's working directory, and remote URLs work too once the httpfs extension is loaded (it auto-loads for https:// and s3:// in most installations).
Example
import duckdb, os, tempfile
os.chdir(tempfile.mkdtemp())
with open("sales.csv", "w") as f:
f.write("region,amount\nnorth,10\nsouth,25\nnorth,5\n")
print(duckdb.sql("""
SELECT region, sum(amount) AS total
FROM 'sales.csv'
GROUP BY region ORDER BY total DESC
""").fetchall()) # [('south', 25), ('north', 15)]
print(duckdb.sql("DESCRIBE SELECT * FROM 'sales.csv'").fetchall()[1][:2])
# ('amount', 'BIGINT')Remember
- A quoted file path in FROM is a table; the format comes from the extension
read_csv,read_parquet,read_jsontake options when auto-detection is not enoughDESCRIBEandSUMMARIZEgive a fast first look at an unknown file
Watch out for
- Re-querying a multi-gigabyte CSV over and over instead of importing it or converting to Parquet once.