Featured image of post DuckDB 个人财务自动化:用 SQL 打造智能理财助手

DuckDB 个人财务自动化:用 SQL 打造智能理财助手

用 DuckDB 搭建个人财务自动化分析系统,从银行流水导入到支出分类、预算监控、月度报告一键生成,附完整 Python + SQL 代码。

DuckDB 个人财务自动化:用 SQL 打造智能理财助手

场景引入:你的钱都去哪了?

很多人每个月发工资后都有一种迷茫——钱好像挣了不少,但月底一看余额,不知道怎么花光的。传统做法是用 Excel 记账,或者下载各种记账 App。但这些方案各有痛点:

  • Excel 记账:手动输入繁琐,容易放弃;多个月的数据难以聚合分析
  • 记账 App:数据存在别人服务器上,隐私担忧;想导出原始数据做自定义分析?门都没有
  • 银行 App:只能看单家银行,多账户数据分散,无法统一分析

DuckDB 提供了一个全新的思路:把银行流水 CSV 下载到本地,用 SQL 快速分析,所有数据完全私有。整个过程不需要写一行 Python,一条 SQL 就能完成从数据导入到报表生成的全流程。

个人财务自动化架构图


Step 1:导入银行流水数据

国内大部分银行都支持导出 CSV 格式的账单。以招商银行为例,导出格式通常包含:交易日期、交易时间、交易金额、余额、对方账户、交易摘要等字段。

import duckdb
from pathlib import Path
from datetime import datetime

# 连接内存数据库(无需安装,即开即用)
con = duckdb.connect(':memory:')

# 读取招商银行导出CSV
# 假设文件路径:~/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"招商银行卡记录数: {con.execute('SELECT COUNT(*) FROM cmb_transactions').fetchone()[0]}")

💡 技巧read_csv_auto 会自动推断列类型,但如果银行 CSV 格式不规范(比如日期格式混杂),可以通过 columns 参数手动指定。不同银行的导出格式略有差异,但 DuckDB 都能智能处理。

多银行数据合并

如果你有多个银行账户(招行、工行、支付宝、微信),可以分别导入后 UNION ALL 合并:

-- 读取工行的交易记录
CREATE TABLE icbc_transactions AS
SELECT * FROM read_csv_auto('~/Downloads/ICBC_202609.csv', header=true);

-- 读取支付宝账单
CREATE TABLE alipay_transactions AS
SELECT * FROM read_csv_auto('~/Downloads/ALIPAY_202609.csv', header=true);

-- 统一合并为一张总表
CREATE TABLE all_transactions AS
SELECT '招商银行' AS bank, * FROM cmb_transactions
UNION ALL
SELECT '工商银行' AS bank, * FROM icbc_transactions
UNION ALL
SELECT '支付宝' AS bank, * FROM alipay_transactions;

Step 2:智能支出分类

银行流水最大的问题是:交易摘要只告诉你"转出50元",但不会告诉你这是"餐饮"还是"交通"。我们需要建立一个分类规则引擎

-- 创建分类规则表
CREATE TABLE category_rules AS
SELECT * FROM (VALUES
    ('餐饮', '%外卖%'),
    ('餐饮', '%美团%'),
    ('餐饮', '%饿了么%'),
    ('餐饮', '%麦当劳%'),
    ('餐饮', '%肯德基%'),
    ('餐饮', '%星巴克%'),
    ('交通', '%滴滴%'),
    ('交通', '%地铁%'),
    ('交通', '%公交%'),
    ('购物', '%淘宝%'),
    ('购物', '%京东%'),
    ('购物', '%拼多多%'),
    ('购物', '%沃尔玛%'),
    ('娱乐', '%爱奇艺%'),
    ('娱乐', '% Netflix%'),
    ('娱乐', '%电影%'),
    ('娱乐', '%KTV%'),
    ('住房', '%房租%'),
    ('住房', '%物业%'),
    ('住房', '%水电%'),
    ('医疗', '%医院%'),
    ('医疗', '%药房%'),
    ('教育', '%学费%'),
    ('教育', '%培训%'),
    ('工资', '%工资%'),
    ('工资', '%奖金%'),
    ('投资', '%基金%'),
    ('投资', '%股票%'),
    ('转账', '%转账%'),
    ('退款', '%退款%'),
    ('退款', '%返还%')
) AS t(category_keyword, pattern);

-- 为每笔交易打上分类标签
CREATE TABLE classified_transactions AS
SELECT 
    t.*,
    COALESCE(
        (SELECT r.category_keyword 
         FROM category_rules r 
         WHERE t.交易摘要 ILIKE r.pattern 
         LIMIT 1),
        '其他'
    ) AS category
FROM all_transactions t;

扩展分类规则

随着使用深入,你可以不断补充分类规则。比如发现某家餐厅总是被误分类,就加一条规则:

-- 新增分类规则
INSERT INTO category_rules (category_keyword, pattern) VALUES
    ('餐饮', '%海底捞%'),
    ('餐饮', '%西贝%'),
    ('健身', '%Keep%'),
    ('健身', '%健身房%');

Step 3:月度支出分析

有了分类后的数据,就可以生成各种维度的分析报告:

-- 按类别汇总月度支出
SELECT 
    category,
    SUM(交易金额) AS total_spent,
    COUNT(*) AS transaction_count,
    AVG(交易金额) AS avg_amount,
    ROUND(100.0 * SUM(交易金额) / SUM(SUM(交易金额)) OVER(), 1) AS percentage
FROM classified_transactions
WHERE 交易金额 < 0  -- 支出为负数
GROUP BY category
ORDER BY total_spent DESC;

输出示例:

categorytotal_spenttransaction_countavg_amountpercentage
住房-4500.0014500.0045.0
餐饮-2800.508732.1928.0
交通-680.004515.116.8
购物-1200.002352.1712.0
娱乐-450.001237.504.5
其他-369.501524.633.7

同比分析:这个月比上个月多花了多少?

-- 按月分组,对比相邻月份支出变化
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;

预算管理:有没有超支?

-- 设定月度预算并检查超支情况
CREATE TABLE budgets AS
SELECT * FROM (VALUES
    ('餐饮', 3000.00),
    ('交通', 800.00),
    ('购物', 1500.00),
    ('娱乐', 500.00),
    ('住房', 5000.00),
    ('医疗', 500.00),
    ('其他', 500.00)
) AS t(category, budget_amount);

-- 检查本月各类别是否超支
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 '⚠️ 已超支'
        WHEN COALESCE(s.total_spent, 0) > b.budget_amount * 0.8 THEN '🔔 接近上限'
        ELSE '✅ 正常'
    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:自动生成月度财务报告

把上面的分析整合成一个完整的月度报告,可以用 Python 脚本自动执行:

import duckdb
from pathlib import Path
from datetime import datetime

def generate_monthly_report(year_month: str):
    """生成指定月份的财务报告"""
    con = duckdb.connect(':memory:')
    
    # 1. 导入数据
    con.execute(f"""
        CREATE TABLE all_transactions AS
        SELECT '招商银行' AS bank, * FROM read_csv_auto(
            '{Path.home()}/Downloads/CMB_{year_month}.csv', header=true)
        UNION ALL
        SELECT '支付宝' AS bank, * FROM read_csv_auto(
            '{Path.home()}/Downloads/ALIPAY_{year_month}.csv', header=true);
    """)
    
    # 2. 分类(复用上面的分类规则)
    con.execute("""
        CREATE TABLE category_rules AS
        SELECT * FROM (VALUES
            ('餐饮', '%外卖%'), ('餐饮', '%美团%'), ('餐饮', '%饿了么%'),
            ('交通', '%滴滴%'), ('交通', '%地铁%'),
            ('购物', '%淘宝%'), ('购物', '%京东%'),
            ('娱乐', '%爱奇艺%'), ('娱乐', '%电影%'),
            ('住房', '%房租%'), ('住房', '%物业%'),
            ('工资', '%工资%'), ('工资', '%奖金%'),
            ('投资', '%基金%'), ('投资', '%股票%'),
            ('退款', '%退款%'), ('退款', '%返还%')
        ) 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), '其他'
        ) AS category
        FROM all_transactions t;
    """)
    
    # 3. 生成报告
    print(f"{'='*50}")
    print(f"📊 {year_month} 月度财务分析报告")
    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💰 收入: ¥{overview[0]:,.2f}")
    print(f"💸 支出: ¥{overview[1]:,.2f}")
    print(f"📈 净储蓄: ¥{overview[2]:,.2f} ({overview[2]/overview[0]*100:.1f}%)")
    
    # 支出分类排行
    print(f"\n📋 支出分类 TOP 5:")
    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:")
    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}次, ¥{total:,.2f}")
    
    print(f"\n{'='*50}")
    
    con.close()

# 使用示例
generate_monthly_report('202609')

运行结果示例:

==================================================
📊 202609 月度财务分析报告
==================================================

💰 收入: ¥15,000.00
💸 支出: ¥9,900.00
📈 净储蓄: ¥5,100.00 (34.0%)

📋 支出分类 TOP 5:
  1. 住房: ¥4,500.00
  2. 餐饮: ¥2,800.50
  3. 购物: ¥1,200.00
  4. 交通: ¥680.00
  5. 娱乐: ¥450.00

🏪 高频消费商户 TOP 5:
  1. 美团外卖: 32次, ¥1,280.00
  2. 滴滴出行: 18次, ¥540.00
  3. 淘宝商城: 8次, ¥890.00
  4. 饿了么: 15次, ¥620.00
  5. 京东商城: 5次, ¥310.00

==================================================

与传统工具对比

维度Excel 手工记账记账 AppDuckDB 自动化
数据导入手动输入或复制粘贴手动输入银行 CSV 一键导入
多账户聚合需要在多个表格间切换仅支持单一账户UNION ALL 多表合并
支出分类手工标记每笔消费App 自动分类但不可定制SQL 规则引擎,可自由扩展
隐私安全文件存本地,较安全数据上传云端,有泄露风险完全本地,数据不出门
分析灵活性受限于 Excel 功能只能看 App 提供的图表SQL 任意维度分析
自动化程度低,需要手动操作中,但分析受限高,脚本一键生成报告
学习成本中等(会基础 SQL 即可)
费用免费部分功能收费完全免费

核心价值:为什么用 DuckDB?

  1. 零安装pip install duckdb 即可,无需数据库服务器
  2. 极速:列式存储 + 向量化执行,百万级流水秒级分析
  3. 本地优先:所有数据在本地,不涉及任何网络传输,隐私无忧
  4. SQL 即战力:不需要学新的 API,标准 SQL 就能完成 90% 的分析需求
  5. 可扩展:随着数据积累,可以轻松扩展到年度分析、跨月对比、趋势预测

📈 变现建议

将这套个人财务分析系统产品化,有以下几种变现路径:

低门槛(免费/低成本启动)

  • 模板销售:将分类规则、SQL 查询模板打包成"财务分析模板包",在 Gumroad 或小红书售卖(¥19.9-49.9)
  • 教程变现:制作"DuckDB 个人财务自动化"系列视频教程,在 B站/YouTube 发布,通过广告和付费课程变现
  • 付费咨询:为不懂技术的用户提供"数据导入+分类配置"的一站式服务,单次收费 ¥100-300

中等投入(需要一些开发/运营)

  • SaaS 化财务仪表盘:基于 DuckDB + Streamlit 搭建 Web 应用,用户上传银行 CSV 即可自动生成可视化报告,订阅制 ¥29/月
  • 多用户版本:支持夫妻/家庭成员共享财务数据,实时同步分析结果,年费 ¥199/家庭
  • 企业版:为小微企业提供员工报销数据分析、团队预算管理等 SaaS 服务,客单价 ¥500-2000/月

高投入(需要团队/资金)

  • AI 智能财务顾问:接入 LLM,用自然语言回答问题(“我这个月餐饮花了多少?比上月多了吗?"),DuckDB 负责数据查询,LLM 负责解释结果
  • 财务数据平台:聚合多银行 API(通过 OAuth 授权),实现自动同步和实时分析,对标 MoneyWiz、YNAB 等商业产品
  • 白标解决方案:为理财顾问/财务规划师提供白标分析平台,按客户数收费 ¥50-200/人/月

总结

DuckDB 让个人财务分析从"手动记账"升级为"自动化智能分析”。你只需要每月下载一次银行 CSV,一条 SQL 就能完成从数据导入、智能分类到报告生成的全流程。所有数据留在本地,隐私有保障,分析无上限。

从今天开始,试着导入你的银行流水,用 DuckDB 看看这个月钱都花哪儿了。

📺 Watch video tutorials → Olap Studio YouTube

Subscribe for more DuckDB & AI automation tutorials

使用 Hugo 构建
主题 StackJimmy 设计