用 sqlite3 把散落的记账 CSV 并成一张表:五个坑我都踩了

举报
yd_231995061 发表于 2026/11/11 01:20:19 2026/11/11
【摘要】 三份 CSV 散在硬盘里,查一笔账要开三个文件。改成导入 sqlite3,一条命令出结果。中途踩了五个坑:路径写错它不报错而是静默建空库、中文列宽按字节算被截断、表头混进数据、金额全成文本 sum 出 0、并发写 database is locked。

三份 CSV,一份是记账 App 导的,一份是自己手抄的,还有一份从旧手机里抠出来的。想查「去年 12 月在吃饭上花了多少」,得挨个打开、筛选、加总。Excel 打开这三份要等十几秒,慢到让人不想查。

后来把它们并进一个 SQLite 库,一条命令出结果。整个过程没装任何服务,就是一个命令行工具。

先确认要做的三件事:把 CSV 变成表、把列类型调对(金额得是数字不是文本)、加索引让查询别全表扫。三步都在命令行里,不用写代码。

第一步:导入

sqlite3 expenses.db
.mode csv
.import 2023.csv expenses
.import 2024.csv expenses
.import 2025.csv expenses
.tables

.import 的第二个参数是表名,表不存在会按 CSV 首行自动建列。

坑一:路径写错,它不报错,直接给你建个空库

我第一次跑的是 sqlite3 expense.db "select * from expenses;",文件名少了个 s。它没抱怨,只回了:

Error: in prepare, no such table: expenses

看着像个正常的「表不存在」,我去查表名,查了十分钟。真相是:SQLite 打开不存在的文件时不报错,直接创建一个 0 字节的新库。后来一看目录,多出来一个 expense.db,0 字节。

判据很简单:报 no such table 的时候,先确认你连的是哪个文件。

sqlite3 expense.db ".databases"

它会打印主库的真实路径。路径不对,一眼就看出来。

坑二:中文列名在 -column 模式下被吞掉一半

想看得整齐点,加了 -header -column:

sqlite3 -header -column expenses.db "select * from expenses limit 2;"

输出是这样:

月  条  金额
--  --  ----
1月   午  23.5
1月   打  18

「月份」变成「月」,「午餐」变成「午」。数据没丢,是显示被截断了。sqlite3 算列宽按字节数,一个中文字 3 字节,它以为宽度够 2 个字符位。

.mode box 也一样:表头两个字符,下面一行只剩一个。要正常看中文,用默认的 list 模式,或者直接导成 CSV:

sqlite3 -csv expenses.db "select * from expenses limit 5;"

坑三:表头跑进了列名里

第一版导入后 count(*) 是 4,CSV 明明有 5 行(1 行表头 + 4 行数据)。查 .schema 才明白:表头被当成列名了,这是对的行为。但如果 CSV 没表头、或者表头行里有脏字符,列名会变成 column1、column2,之后想按列名查就得记这些名字。

保险做法:导入前自己建表、明确写类型,再跳过首行。

CREATE TABLE expenses(month TEXT, item TEXT, amount REAL);
.import --skip 1 expenses.csv expenses

--skip 1 别省,否则表头会作为一行数据混进去,后面 sum 的时候莫名多一条。

坑四:金额全是文本,sum 出来是 0

.import 自动建表时所有列都是 TEXT。看着没问题(值是 23.5),但 select sum(amount) 返回 0。

SQLite 会把 TEXT 里的「23.5」按数字规则转换,但列声明成 TEXT、里面又混了非数字内容时,聚合会退化成 0。我当时那列里混了一行表头(「金额」两个字),整列就都不认了。

已经导完也能救:

ALTER TABLE expenses ADD COLUMN amount_num REAL;
UPDATE expenses SET amount_num = CAST(amount AS REAL);

坑五:database is locked (5)

一边跑查询、一边另一个窗口在写,偶尔报:

Error: stepping, database is locked (5)

SQLite 是单写多读,写的时候拿排他锁。我那次是两个进程同时写(一个在导数据,一个在补一行)。(5) 就是忙,不是数据坏了。三种处理:等几秒重试;把所有写操作收在一个连接里;打开时设 busy timeout。

sqlite3 -cmd ".timeout 5000" expenses.db

最后:加索引

三份 CSV 合起来几万行,按月份查要扫全表。加个索引:

CREATE INDEX idx_month ON expenses(month);

然后用查询计划确认真的走了索引:

EXPLAIN QUERY PLAN SELECT sum(amount_num) FROM expenses WHERE month='12月';

看到 SEARCH expenses USING INDEX idx_month 就对了。看到 SCAN expenses 说明没走,常见原因是类型不匹配——两边都得是文本,或者两边都是数字。

收尾

这套东西我用了几个月,库里二十多万行,查询没超过 0.1 秒。没有服务、没有端口、没有账号,就是一个文件。备份就是复制那个 .db。

要注意的只有一条:它太安静了。路径写错不报错、表头混进数据不报错、类型不对只在你 sum 的时候给个 0。想少踩坑,每步之后用 select count(*) 和 .schema 各看一眼。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。