
The Pain: 200 Lines of Pandas Code Nightmare
As a data engineer, have you ever faced this scenario?
Every morning, you need to export CSV files from 5 business databases (sales, inventory, users, orders, refunds), with filenames containing dates. Your boss wants a consolidated wide table, updated automatically every day.
Your code looked something like this:
import pandas as pd
import glob
from datetime import datetime
# 1. Read all files (30 lines just for this)
files = sorted(glob.glob('data/sales_2026-*.csv'))
dfs = []
for f in files:
try:
df = pd.read_csv(f)
dfs.append(df)
except Exception as e:
print(f"Error reading {f}: {e}")
# 2. Concatenate (another 50 lines)
result = pd.concat(dfs, ignore_index=True)
# 3. Clean dirty data (complex logic, 80 lines)
result = result[result['amount'] > 0]
result = result[result['region'].notna()]
result['date'] = pd.to_datetime(result['date'])
# 4. Align columns (different files have different column names!)
# ... lots of manual adaptation ...
# 5. Output (20 lines)
result.to_parquet('output/sales_agg.parquet')
200+ lines of code, and it still breaks when column names are inconsistent. One missing column in a single file crashes the entire pipeline.
The DuckDB Solution: 3 Steps, 10 Lines of Code
import duckdb
con = duckdb.connect('etl.duckdb')
# Step 1: Multi-file read + clean + merge
# (auto schema inference, handles inconsistent columns gracefully)
con.execute("""
CREATE OR REPLACE TABLE sales_agg AS
SELECT
filename,
region,
SUM(amount) AS total_amount,
COUNT(*) AS order_cnt
FROM read_csv_auto('data/sales_*.csv',
header=true,
filename=true,
sample_size=-1)
WHERE amount > 0
GROUP BY 1, 2
""")
# Step 2: Output directly as Parquet (10x compression, ready for downstream)
con.execute("COPY sales_agg TO 'output/sales_agg.parquet' (FORMAT PARQUET)")
con.close()
Yes, only 3 function calls, 10 lines of code. 20x more concise than the previous 200-line Pandas code.
Core Technology Deep Dive
1. read_csv_auto: The Smart CSV Reader
read_csv_auto is DuckDB’s intelligent CSV reading function that automatically:
| Capability | Description |
|---|---|
| Auto-detect delimiter | Comma, tab, semicolon — all auto-detected |
| Auto-infer types | Dates, numbers, strings — all auto-inferred |
| Auto-handle encoding | UTF-8, GBK, and more |
| Skip blank lines & comments | No manual filtering needed |
| Auto-align column names | Merge even when files have different columns |
2. filename=true: Automatic Filename Column
This is one of the most useful parameters. With filename=true, DuckDB automatically adds a filename column to the results, recording which file each row came from. This is critical for data lineage tracking and incremental updates.
3. sample_size=-1: Global Schema Inference
By default, DuckDB only reads the first few rows of a file to infer the schema. Setting sample_size=-1 makes it scan the entire file, ensuring accurate schema inference — especially important when dealing with large files or mixed types.
Complete Practical Example: E-commerce Sales Automation
Assume you run an e-commerce business with this directory structure:
data/
├── sales_2026-09-01.csv # Sales data
├── sales_2026-09-02.csv
├── inventory_2026-09-01.csv # Inventory data
├── inventory_2026-09-02.csv
├── users_2026-09-01.csv # User data
└── orders_2026-09-01.csv # Orders data
Step 1: Unified ETL Pipeline
import duckdb
from pathlib import Path
class SalesETLPipeline:
def __init__(self, db_path='sales_analytics.duckdb'):
self.con = duckdb.connect(db_path)
self._setup_schema()
def _setup_schema(self):
"""Initialize table structures"""
self.con.execute("""
CREATE TABLE IF NOT EXISTS daily_sales (
order_id VARCHAR,
product_id VARCHAR,
amount DECIMAL(10,2),
region VARCHAR,
sale_date DATE,
source_file VARCHAR
)
""")
self.con.execute("""
CREATE TABLE IF NOT EXISTS sales_summary (
report_date DATE,
region VARCHAR,
total_amount DECIMAL(12,2),
order_count BIGINT,
avg_order DECIMAL(10,2),
updated_at TIMESTAMP
)
""")
def run_daily_etl(self, date_str=None):
"""Run daily ETL"""
if date_str is None:
date_str = Path('.').absolute().strftime('%Y-%m-%d')
print(f"🔄 Processing sales data for {date_str}...")
# Read today's sales CSV (auto-merge, auto-handle schema differences)
csv_pattern = f'data/sales_{date_str}.csv'
self.con.execute(f"""
INSERT INTO daily_sales
SELECT
order_id, product_id, amount, region,
'{date_str}'::DATE as sale_date,
'{csv_pattern}' as source_file
FROM read_csv_auto('{csv_pattern}',
header=true,
filename=true,
sample_size=-1)
WHERE amount > 0
AND order_id IS NOT NULL
""")
# Update summary table
self.con.execute(f"""
INSERT INTO sales_summary
SELECT
'{date_str}'::DATE as report_date,
region,
SUM(amount) as total_amount,
COUNT(*) as order_count,
AVG(amount) as avg_order,
CURRENT_TIMESTAMP as updated_at
FROM daily_sales
WHERE sale_date = '{date_str}'::DATE
GROUP BY region
""")
# Output Parquet for downstream use
self.con.execute("""
COPY (SELECT * FROM sales_summary)
TO 'output/sales_summary.parquet' (FORMAT PARQUET)
""")
print(f"✅ ETL complete! Written to output/sales_summary.parquet")
if __name__ == '__main__':
pipeline = SalesETLPipeline()
pipeline.run_daily_etl('2026-09-02')
Step 2: Incremental Update Mode
DuckDB’s glob pattern natively supports incremental updates. Just filter by filename:
-- Only process new files (filenames contain dates)
FROM read_csv_auto('data/sales_2026-09-*.csv',
header=true, filename=true)
WHERE filename >= 'data/sales_2026-09-01.csv'
DuckDB pushes the WHERE condition down to the file reading layer, reading only the matching files instead of scanning everything.
DuckDB vs Pandas Comparison Table
| Feature | Pandas | DuckDB |
|---|---|---|
| Multi-file read | Requires glob + for loop | One-line SQL wildcard |
| Schema auto-inference | ❌ Manual dtype specification | ✅ read_csv_auto auto-inference |
| Inconsistent columns | ❌ concat throws errors | ✅ Auto-aligned, missing columns filled with NULL |
| Memory efficiency | Loads everything into memory | Streaming, reads on demand |
| Parallel processing | Requires manual multiprocessing | Automatic parallel file reading |
| Incremental filtering | Manual file traversal | WHERE pushed to file layer |
| Output format | Requires extra conversion | Native Parquet/JSON/CSV support |
| Code lines | 200+ | 10 |
💡 Key Insight: For multi-file ETL scenarios, DuckDB achieves 5-10x faster processing with 90% less memory, using just 1/20th of the code.
Advanced: Handling Heterogeneous Data Sources
In reality, CSV files from different business lines often have inconsistent formats. DuckDB’s read_csv_auto handles these elegantly:
-- Different files have different columns, DuckDB auto-aligns
SELECT * FROM read_csv_auto('data/business_*.csv',
header=true,
union_by_name=true,
null_padding=true)
union_by_name=true: Merge by column name, not positionnull_padding=true: Missing columns auto-filled with NULL, no errors
Monetization Advice
Productization Ideas
Once you master this technique, you can build several paid data products:
1. Automated Report SaaS
- Target: Small and medium businesses
- Provide daily/weekly sales report auto-generation service
- Clients simply upload CSV files, the system auto-ETL → generates reports → pushes to WeChat Work/Slack
- Pricing: ¥299/month (Basic) → ¥999/month (Pro, includes anomaly detection)
- Marginal cost is near zero (DuckDB is open-source free)
2. Data Governance Consulting
- Help businesses clean up messy CSV export data
- Build standardized ETL pipelines
- One-time project fee: ¥5,000-20,000
3. Embedded Analytics API
- Package DuckDB ETL capabilities as REST API
- Pay-per-call pricing (¥0.01/call)
- Suitable for integration into existing business systems
Revenue Projections:
- 50 SaaS clients × ¥299/month = ¥14,950/month
- 10 consulting projects × ¥8,000 = ¥80,000/project
- Combined monthly revenue: ¥20,000-50,000
Summary
DuckDB’s read_csv_auto + glob pattern completely solves the pain points of multi-file CSV processing:
- 3 functions replace 200 lines of Pandas code
- Auto schema inference, tolerates inconsistent column names
- Streaming processing, no memory explosion
- Native Parquet output, optimal performance
Don’t let your ETL pipeline become a business bottleneck. Start rewriting your data processing scripts with DuckDB today!