| name | duckdb |
| description | Fast in-process analytical database for SQL queries on DataFrames, CSV, Parquet, JSON files, and more. Use when user wants to perform SQL analytics on data files or Python DataFrames (pandas, Polars), run complex aggregations, joins, or window functions, or query external data sources without loading into memory. Best for analytical workloads, OLAP queries, and data exploration. |
DuckDB
Overview
DuckDB is a high-performance, in-process analytical database management system (often called "SQLite for analytics"). Execute complex SQL queries directly on CSV, Parquet, JSON files, and Python DataFrames (pandas, Polars) without importing data or running a separate database server.
When to Use This Skill
Activate when the user:
- Wants to run SQL queries on data files (CSV, Parquet, JSON)
- Needs to perform complex analytical queries (aggregations, joins, window functions)
- Asks to query pandas or Polars DataFrames using SQL
- Wants to explore or analyze data without loading it into memory
- Needs fast analytical performance on medium to large datasets
- Mentions DuckDB explicitly or wants OLAP-style analytics
Installation
Check if DuckDB is installed:
python3 -c "import duckdb; print(duckdb.__version__)"
If not installed:
pip3 install duckdb
For Polars integration:
pip3 install duckdb 'polars[pyarrow]'
Core Capabilities
1. Querying Data Files Directly
DuckDB can query files without loading them into memory:
import duckdb
result = duckdb.sql("SELECT * FROM 'data.csv' WHERE age > 25")
print(result.df())
result = duckdb.sql("""
SELECT category, SUM(amount) as total
FROM 'sales.parquet'
GROUP BY category
ORDER BY total DESC
""")
result = duckdb.sql("SELECT * FROM 'users.json' LIMIT 10")
result = duckdb.sql("SELECT * FROM 'data/*.parquet'")
2. Working with Pandas DataFrames
DuckDB can directly query pandas DataFrames:
import duckdb
import pandas as pd
df = pd.read_csv('data.csv')
result = duckdb.sql("""
SELECT
category,
AVG(price) as avg_price,
COUNT(*) as count
FROM df
WHERE price > 100
GROUP BY category
HAVING count > 5
""")
result_df = result.df()
print(result_df)
3. Working with Polars DataFrames
DuckDB integrates seamlessly with Polars using Apache Arrow:
import duckdb
import polars as pl
df = pl.read_csv('data.csv')
result = duckdb.sql("""
SELECT
date_trunc('month', date) as month,
SUM(revenue) as monthly_revenue
FROM df
GROUP BY month
ORDER BY month
""")
result_df = result.pl()
lazy_result = result.pl(lazy=True)
4. Creating Persistent Databases
Create database files for persistent storage:
import duckdb
con = duckdb.connect('my_database.duckdb')
con.execute("""
CREATE TABLE users AS
SELECT * FROM 'users.csv'
""")
result = con.execute("SELECT * FROM users WHERE age > 30").fetchdf()
con.close()
5. Complex Analytical Queries
DuckDB excels at analytical queries:
import duckdb
result = duckdb.sql("""
SELECT
name,
department,
salary,
AVG(salary) OVER (PARTITION BY department) as dept_avg,
RANK() OVER (PARTITION BY department ORDER BY salary DESC) as dept_rank
FROM 'employees.csv'
""")
result = duckdb.sql("""
WITH monthly_sales AS (
SELECT
date_trunc('month', sale_date) as month,
product_id,
SUM(amount) as total_sales
FROM 'sales.parquet'
GROUP BY month, product_id
)
SELECT
m.month,
p.product_name,
m.total_sales,
LAG(m.total_sales) OVER (
PARTITION BY m.product_id
ORDER BY m.month
) as prev_month_sales
FROM monthly_sales m
JOIN 'products.csv' p ON m.product_id = p.id
ORDER BY m.month DESC, m.total_sales DESC
""")
6. Joins Across Different Data Sources
Join data from multiple files and DataFrames:
import duckdb
import pandas as pd
customers_df = pd.read_csv('customers.csv')
result = duckdb.sql("""
SELECT
c.customer_name,
c.email,
o.order_date,
o.total_amount
FROM customers_df c
JOIN 'orders.parquet' o ON c.customer_id = o.customer_id
WHERE o.order_date >= '2024-01-01'
ORDER BY o.order_date DESC
""")
Common Patterns
Pattern 1: Quick Data Exploration
import duckdb
duckdb.sql("DESCRIBE SELECT * FROM 'data.parquet'").show()
duckdb.sql("""
SELECT
COUNT(*) as rows,
COUNT(DISTINCT user_id) as unique_users,
MIN(created_at) as earliest_date,
MAX(created_at) as latest_date
FROM 'data.csv'
""").show()
duckdb.sql("SELECT * FROM 'large_file.parquet' USING SAMPLE 1000").show()
Pattern 2: Data Transformation Pipeline
import duckdb
con = duckdb.connect('analytics.duckdb')
con.execute("""
CREATE TABLE clean_sales AS
SELECT
date_trunc('day', timestamp) as sale_date,
UPPER(TRIM(product_name)) as product_name,
quantity,
price,
quantity * price as total_amount,
CASE
WHEN quantity > 10 THEN 'bulk'
ELSE 'retail'
END as sale_type
FROM 'raw_sales.csv'
WHERE price > 0 AND quantity > 0
""")
con.execute("""
CREATE VIEW daily_summary AS
SELECT
sale_date,
sale_type,
COUNT(*) as num_sales,
SUM(total_amount) as revenue
FROM clean_sales
GROUP BY sale_date, sale_type
""")
result = con.execute("SELECT * FROM daily_summary ORDER BY sale_date DESC").fetchdf()
con.close()
Pattern 3: Combining DuckDB + Polars for Optimal Performance
import duckdb
import polars as pl
df = pl.read_parquet('data/*.parquet')
result = duckdb.sql("""
SELECT
customer_segment,
product_category,
COUNT(DISTINCT customer_id) as customers,
SUM(revenue) as total_revenue,
AVG(revenue) as avg_revenue,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY revenue) as median_revenue
FROM df
WHERE order_date >= CURRENT_DATE - INTERVAL '30 days'
GROUP BY customer_segment, product_category
HAVING total_revenue > 10000
ORDER BY total_revenue DESC
""").pl()
final_result = result.with_columns([
(pl.col('total_revenue') / pl.col('customers')).alias('revenue_per_customer')
])
Pattern 4: Export Query Results
import duckdb
duckdb.sql("""
COPY (
SELECT * FROM 'input.parquet' WHERE status = 'active'
) TO 'output.csv' (HEADER, DELIMITER ',')
""")
duckdb.sql("""
COPY (
SELECT date, category, SUM(amount) as total
FROM 'sales.csv'
GROUP BY date, category
) TO 'summary.parquet' (FORMAT PARQUET)
""")
duckdb.sql("""
COPY (SELECT * FROM users WHERE age > 21)
TO 'filtered_users.json' (FORMAT JSON)
""")
Performance Tips
- Use Parquet for large datasets: Parquet is columnar and compressed, ideal for analytical queries
- Filter early: Push filters down to file reads when possible
- Partition large files: Use DuckDB's automatic partitioning for large datasets
- Use projections: Only select columns you need
- Leverage indexes: For persistent databases, create indexes on frequently queried columns
duckdb.sql("SELECT name, age FROM 'users.parquet' WHERE age > 25")
duckdb.sql("SELECT * FROM 'users.parquet'").df()[lambda x: x['age'] > 25]
Integration with Polars
DuckDB and Polars work together seamlessly via Apache Arrow:
import duckdb
import polars as pl
df = (
pl.scan_parquet('data/*.parquet')
.filter(pl.col('date') >= '2024-01-01')
.collect()
)
result = duckdb.sql("""
SELECT
user_id,
COUNT(*) as sessions,
SUM(duration) as total_duration,
AVG(duration) as avg_duration,
MAX(duration) as max_duration
FROM df
GROUP BY user_id
HAVING sessions > 5
""").pl()
top_users = result.top_k(10, by='total_duration')
See the polars skill for more Polars-specific operations and the references/integration.md file for detailed integration examples.
Error Handling
Common issues and solutions:
import duckdb
try:
result = duckdb.sql("SELECT * FROM 'data.csv'")
except duckdb.Error as e:
print(f"DuckDB error: {e}")
except FileNotFoundError:
print("File not found")
except Exception as e:
print(f"Unexpected error: {e}")
Resources