DuckDB Personal Finance Automation: Build an Intelligent Financial Assistant with SQL
The Problem: Where Did Your Money Go?
Many people earn a decent salary each month but have no idea where it all went by the end of the month. The traditional approaches each have significant pain points:
- Excel manual entry: Tedious to enter every transaction, easy to abandon; hard to aggregate data across months
- Budgeting apps: Your data lives on someone else’s server — privacy concerns; want to export raw data for custom analysis? Not happening
- Bank apps: Data is siloed per bank, can’t get a unified view across accounts
DuckDB offers a fundamentally different approach: download your bank statements as CSV files locally, analyze everything with SQL, and keep all your data completely private. The entire pipeline from data import to report generation runs in seconds, powered by pure SQL.

Step 1: Import Bank Statement Data
Most banks in China support exporting transaction histories as CSV files. Taking China Merchants Bank as an example, the exported format typically includes: transaction date, transaction time, transaction amount, balance, counterparty account, and transaction description.
import duckdb
from pathlib import Path
# Connect to in-memory database (no installation needed)
con = duckdb.connect(':memory:')
# Read China Merchants Bank CSV export
# Assuming file path: ~/Downloads/CMB_202609.csv
cmb_csv = Path.home() / 'Downloads' / 'CMB_202609.csv'
con.execute(f"""
CREATE TABLE cmb_transactions AS
SELECT * FROM read_csv_auto('{cmb_csv}',
header=true,
columns={{
'交易日期': 'VARCHAR',
'交易时间': 'VARCHAR',
'交易金额': 'DECIMAL(12,2)',
'余额': 'DECIMAL(12,2)',
'对方账户': 'VARCHAR',
'交易摘要': 'VARCHAR'
}}
)
""")
print(f"CMB transaction records: {con.execute('SELECT COUNT(*) FROM cmb_transactions').fetchone()[0]}")
💡 Pro tip:
read_csv_autoauto-detects column types, but if the bank CSV format is irregular (mixed date formats), you can manually specify types via thecolumnsparameter. Different banks have slightly different export formats, but DuckDB handles them all intelligently.
Merging Multiple Bank Accounts
If you have accounts at multiple banks (CMB, ICBC, Alipay, WeChat Pay), import each separately then UNION ALL them together:
-- Read ICBC transactions
CREATE TABLE icbc_transactions AS
SELECT * FROM read_csv_auto('~/Downloads/ICBC_202609.csv', header=true);
-- Read Alipay statements
CREATE TABLE alipay_transactions AS
SELECT * FROM read_csv_auto('~/Downloads/ALIPAY_202609.csv', header=true);
-- Merge all into one unified table
CREATE TABLE all_transactions AS
SELECT 'CMB' AS bank, * FROM cmb_transactions
UNION ALL
SELECT 'ICBC' AS bank, * FROM icbc_transactions
UNION ALL
SELECT 'Alipay' AS bank, * FROM alipay_transactions;
Step 2: Intelligent Expense Categorization
The biggest challenge with bank statements is that transaction descriptions only tell you “transferred out 50 yuan” but don’t tell you whether it was for “food” or “transportation”. We need to build a categorization rule engine:
-- Create category rules table
CREATE TABLE category_rules AS
SELECT * FROM (VALUES
('Food', '%takeout%'),
('Food', '%Meituan%'),
('Food', '%Ele.me%'),
('Food', '%McDonald%'),
('Food', '%KFC%'),
('Food', '%Starbucks%'),
('Transport', '%DiDi%'),
('Transport', '%subway%'),
('Transport', '%bus%'),
('Shopping', '%Taobao%'),
('Shopping', '%JD%'),
('Shopping', '%Pinduoduo%'),
('Entertainment', '%iQiyi%'),
('Entertainment', '%Netflix%'),
('Entertainment', '%movie%'),
('Housing', '%rent%'),
('Housing', '%property%'),
('Housing', '%utilities%'),
('Salary', '%salary%'),
('Salary', '%bonus%'),
('Investment', '%fund%'),
('Investment', '%stock%'),
('Transfer', '%transfer%'),
('Refund', '%refund%'),
('Refund', '%return%')
) AS t(category_keyword, pattern);
-- Classify each transaction
CREATE TABLE classified_transactions AS
SELECT
t.*,
COALESCE(
(SELECT r.category_keyword
FROM category_rules r
WHERE t.交易摘要 ILIKE r.pattern
LIMIT 1),
'Other'
) AS category
FROM all_transactions t;
Expanding Category Rules
As you use the system, you can continuously add new rules. If you notice a restaurant is always misclassified, just add a rule:
-- Add new category rules
INSERT INTO category_rules (category_keyword, pattern) VALUES
('Food', '%Haidilao%'),
('Food', '%Xibei%'),
('Fitness', '%Keep%'),
('Fitness', '%gym%');
Step 3: Monthly Expense Analysis
With categorized data, you can generate reports across multiple dimensions:
-- Monthly spending by category
SELECT
category,
SUM(ABS(交易金额)) AS total_spent,
COUNT(*) AS transaction_count,
AVG(ABS(交易金额)) AS avg_amount,
ROUND(100.0 * SUM(ABS(交易金额)) / SUM(SUM(ABS(交易金额))) OVER(), 1) AS percentage
FROM classified_transactions
WHERE 交易金额 < 0
GROUP BY category
ORDER BY total_spent DESC;
Sample output:
| category | total_spent | transaction_count | avg_amount | percentage |
|---|---|---|---|---|
| Housing | 4500.00 | 1 | 4500.00 | 45.0 |
| Food | 2800.50 | 87 | 32.19 | 28.0 |
| Shopping | 1200.00 | 23 | 52.17 | 12.0 |
| Transport | 680.00 | 45 | 15.11 | 6.8 |
| Entertainment | 450.00 | 12 | 37.50 | 4.5 |
| Other | 369.50 | 15 | 24.63 | 3.7 |
Month-over-Month Comparison
-- Compare spending between consecutive months
WITH monthly_spending AS (
SELECT
strftime(交易日期, '%Y-%m') AS month,
category,
SUM(ABS(交易金额)) AS total_spent
FROM classified_transactions
WHERE 交易金额 < 0
GROUP BY strftime(交易日期, '%Y-%m'), category
),
with_change AS (
SELECT *,
LAG(total_spent) OVER (PARTITION BY category ORDER BY month) AS prev_month_spent,
ROUND(total_spent - LAG(total_spent) OVER (PARTITION BY category ORDER BY month), 2) AS change_amount,
ROUND(100.0 * (total_spent - LAG(total_spent) OVER (PARTITION BY category ORDER BY month))
/ NULLIF(LAG(total_spent) OVER (PARTITION BY category ORDER BY month), 0), 1) AS change_pct
FROM monthly_spending
)
SELECT month, category, total_spent, change_amount, change_pct
FROM with_change
WHERE month = (SELECT MAX(month) FROM monthly_spending)
ORDER BY total_spent DESC;
Budget Monitoring: Are You Overspending?
-- Set monthly budgets and check for overspending
CREATE TABLE budgets AS
SELECT * FROM (VALUES
('Food', 3000.00),
('Transport', 800.00),
('Shopping', 1500.00),
('Entertainment', 500.00),
('Housing', 5000.00),
('Other', 500.00)
) AS t(category, budget_amount);
-- Check which categories exceeded budget
SELECT
b.category,
b.budget_amount,
COALESCE(s.total_spent, 0) AS actual_spent,
b.budget_amount - COALESCE(s.total_spent, 0) AS remaining,
CASE
WHEN COALESCE(s.total_spent, 0) > b.budget_amount THEN 'OVER BUDGET'
WHEN COALESCE(s.total_spent, 0) > b.budget_amount * 0.8 THEN 'WARNING'
ELSE 'OK'
END AS status
FROM budgets b
LEFT JOIN (
SELECT category, SUM(ABS(交易金额)) AS total_spent
FROM classified_transactions
WHERE 交易金额 < 0
GROUP BY category
) s ON b.category = s.category
ORDER BY remaining ASC;
Step 4: Automated Monthly Financial Report
Integrate everything into a Python script for one-click report generation:
import duckdb
from pathlib import Path
def generate_monthly_report(year_month: str):
"""Generate monthly financial report"""
con = duckdb.connect(':memory:')
# 1. Import data
con.execute(f"""
CREATE TABLE all_transactions AS
SELECT 'CMB' AS bank, * FROM read_csv_auto(
'{Path.home()}/Downloads/CMB_{year_month}.csv', header=true)
UNION ALL
SELECT 'Alipay' AS bank, * FROM read_csv_auto(
'{Path.home()}/Downloads/ALIPAY_{year_month}.csv', header=true);
""")
# 2. Classify transactions
con.execute("""
CREATE TABLE category_rules AS
SELECT * FROM (VALUES
('Food', '%takeout%'), ('Food', '%Meituan%'),
('Transport', '%DiDi%'), ('Transport', '%subway%'),
('Shopping', '%Taobao%'), ('Shopping', '%JD%'),
('Entertainment', '%iQiyi%'), ('Entertainment', '%movie%'),
('Housing', '%rent%'), ('Housing', '%property%'),
('Salary', '%salary%'), ('Salary', '%bonus%'),
('Investment', '%fund%'), ('Investment', '%stock%'),
('Refund', '%refund%')
) AS t(category_keyword, pattern);
""")
con.execute("""
CREATE TABLE classified AS
SELECT t.*, COALESCE(
(SELECT r.category_keyword FROM category_rules r
WHERE t.交易摘要 ILIKE r.pattern LIMIT 1), 'Other'
) AS category
FROM all_transactions t;
""")
# 3. Generate report
print(f"{'='*50}")
print(f"📊 {year_month} Monthly Financial Report")
print(f"{'='*50}")
overview = con.execute("""
SELECT
SUM(CASE WHEN 交易金额 >= 0 THEN 交易金额 ELSE 0 END) AS income,
SUM(CASE WHEN 交易金额 < 0 THEN ABS(交易金额) ELSE 0 END) AS expense,
SUM(交易金额) AS net_savings
FROM classified
").fetchone()
print(f"\n💰 Income: ${overview[0]:,.2f}")
print(f"💸 Expenses: ${overview[1]:,.2f}")
print(f"📈 Net Savings: ${overview[2]:,.2f} ({overview[2]/overview[0]*100:.1f}%)")
print(f"\n📋 Top 5 Spending Categories:")
top_spending = con.execute("""
SELECT category, SUM(ABS(交易金额)) AS total
FROM classified WHERE 交易金额 < 0
GROUP BY category ORDER BY total DESC LIMIT 5
").fetchall()
for i, (cat, amt) in enumerate(top_spending, 1):
print(f" {i}. {cat}: ${amt:,.2f}")
print(f"\n🏪 Top 5 Frequent Merchants:")
top_merchants = con.execute("""
SELECT 交易摘要, COUNT(*) AS cnt, SUM(ABS(交易金额)) AS total
FROM classified WHERE 交易金额 < 0
GROUP BY 交易摘要 ORDER BY cnt DESC LIMIT 5
").fetchall()
for i, (merch, cnt, total) in enumerate(top_merchants, 1):
print(f" {i}. {merch}: {cnt} times, ${total:,.2f}")
print(f"\n{'='*50}")
con.close()
# Usage
generate_monthly_report('202609')
Expected output:
==================================================
📊 202609 Monthly Financial Report
==================================================
💰 Income: $15,000.00
💸 Expenses: $9,900.00
📈 Net Savings: $5,100.00 (34.0%)
📋 Top 5 Spending Categories:
1. Housing: $4,500.00
2. Food: $2,800.50
3. Shopping: $1,200.00
4. Transport: $680.00
5. Entertainment: $450.00
🏪 Top 5 Frequent Merchants:
1. Meituan: 32 times, $1,280.00
2. DiDi: 18 times, $540.00
3. Taobao: 8 times, $890.00
4. Ele.me: 15 times, $620.00
5. JD.com: 5 times, $310.00
==================================================
Comparison: Traditional Tools vs DuckDB
| Dimension | Excel Manual Entry | Budgeting Apps | DuckDB Automation |
|---|---|---|---|
| Data Import | Manual entry or copy-paste | Manual entry | One-click CSV import |
| Multi-account Aggregation | Switch between multiple sheets | Single account only | UNION ALL merge |
| Expense Categorization | Manual tagging per transaction | App auto-categorizes (not customizable) | SQL rule engine, fully extensible |
| Privacy & Security | Local files, relatively secure | Data uploaded to cloud, privacy risk | Completely local, data never leaves your machine |
| Analysis Flexibility | Limited by Excel features | Only charts the app provides | Arbitrary SQL analysis |
| Automation Level | Low, manual operations required | Medium, but limited analysis | High, one-script report generation |
| Learning Curve | Low | Zero | Moderate (basic SQL sufficient) |
| Cost | Free | Some features paid | Completely free |
Why DuckDB?
- Zero installation:
pip install duckdband you’re ready — no database server needed - Blazing fast: Columnar storage + vectorized execution, million-row statements analyzed in seconds
- Local-first: All data stays local, no network transmission, complete privacy
- SQL-native: No new APIs to learn — standard SQL handles 90% of analysis needs
- Highly extensible: Easily scales from monthly analysis to yearly trends and predictive modeling
💰 Monetization Strategies
Productize this personal finance system with several monetization paths:
Low Barrier (Free/Low-cost startup)
- Template sales: Package categorization rules and SQL templates as a “Finance Analysis Template Pack” on Gumroad or Xiaohongshu (¥19.9-49.9)
- Tutorial monetization: Create a “DuckDB Personal Finance Automation” video series on Bilibili/YouTube, monetize through ads and paid courses
- Paid consulting: Offer one-click “data import + classification setup” service for non-technical users, ¥100-300 per session
Medium Investment (Requires some development/operations)
- SaaS dashboard: Build a web app with DuckDB + Streamlit where users upload bank CSVs and get instant visual reports, subscription model ¥29/month
- Multi-user version: Support couples/family shared financial data with real-time synchronized analysis, annual fee ¥199/family
- Enterprise edition: Provide expense analysis and team budget management SaaS for small businesses, ¥500-2000/month per client
High Investment (Requires team/funding)
- AI financial advisor: Integrate LLMs for natural language Q&A (“How much did I spend on food this month? Is it more than last month?”), DuckDB handles queries, LLM explains results
- Financial data platform: Aggregate multiple bank APIs (via OAuth), enable auto-sync and real-time analysis, compete with MoneyWiz and YNAB
- White-label solutions: Provide white-label analysis platforms for financial advisors/planners, charge ¥50-200/user/month
Summary
DuckDB transforms personal finance management from “manual bookkeeping” to “automated intelligent analysis”. Each month, you only need to download your bank statements once, and a single SQL pipeline handles data import, intelligent categorization, and report generation. All your data stays local — privacy guaranteed, analysis unlimited.
Start today: import your bank CSV and use DuckDB to see exactly where your money went this month.