Menu

SQLite 导入 CSV:用 .import 和 --csv 加载数据

如何用 .import 命令把 CSV 文件导入 SQLite:处理表头、导入已有的表、自定义分隔符,以及最常见的错误。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

CSV 导入在命令行工具里,不在 SQL 里

SQLite 的 SQL 方言中没有 IMPORT 语句。加载 CSV 是 sqlite3 命令行 shell 的功能:一个名为 .import 的点命令。如果你用过 MySQL 的 LOAD DATA INFILE 或 Postgres 的 COPY,这里需要转变一下思路:那些命令在服务器上运行,而 .import 是客户端工具替你完成的,它读取文件,并在底层执行 INSERT 语句。

所以本页的所有内容都假设你在 sqlite3 shell 中:

sqlite3 mydata.db

如果你需要在应用代码中导入(Python、Node、Go),就用你的语言读取 CSV,再使用参数化的 INSERT 语句。我们会在应用集成一章介绍这种方法。这里我们专注于命令行工具。

基本的 .import

最短的路径:告诉 SQLite 文件是 CSV 格式,然后让 .import 指向文件和表名。

.mode csv
.import people.csv people

根据 people 是否已经存在,会发生两种情况:

  • 表不存在:SQLite 会创建它,并把 CSV 的第一行用作列名。每一列都是 TEXT 亲和类型。
  • 表已存在:SQLite 把文件的每一行都作为数据插入。如果有表头,表头也会变成一行。

第二种情况是大多数人第一次尝试时踩的坑。如果你的 CSV 有表头,而表已经存在,就必须明确地跳过表头。

导入已有的表时跳过表头

用 --skip 1 告诉 .import 忽略开头的 N 行:

CREATE TABLE people (
    name TEXT,
    age  INTEGER,
    city TEXT
);

.import --csv --skip 1 people.csv people

--csv 是 .mode csv 的简写,只对这一条命令生效,所以你不必单独设置模式。--skip 1 会去掉表头。剩下的行按列的顺序插入到 people 中。

导入后快速检查一下:

SELECT count(*) FROM people;
SELECT * FROM people LIMIT 5;

文件中的列顺序必须和表中的列顺序一致。它不会根据表头来对应列:.import 只是把第 N 个字段对应到第 N 列。

让 SQLite 替你建表

做探索性工作时,最简单的方法是完全跳过 CREATE TABLE,让 .import 根据表头建表:

.mode csv
.import sales.csv sales

.schema sales

.schema sales 会显示类似这样的内容:

CREATE TABLE sales(
  "order_id" TEXT,
  "amount" TEXT,
  "ordered_at" TEXT
);

注意每一列都是 TEXT。这是有意为之:.import 不会尝试推断类型。如果你希望 amount 是实数、ordered_at 是真正的时间戳,就先自己用正确的类型建表,再用 --skip 1 导入。SQLite 的类型亲和性会在插入时把数字字符串转换成整数和实数。

自定义分隔符:TSV、竖线、分号

.mode csv 使用逗号。对于制表符分隔的文件,切换模式即可:

.mode tabs
.import data.tsv events

对于其他分隔符,先选好模式,再用 .separator:

.mode csv
.separator "|"
.import pipe_data.txt events

有一点需要知道:.mode csv 遵循 RFC 4180 的引号规则,只要字段用 " 正确地括起来,其中包含逗号或换行也没问题。.mode tabs 是更简单的按字符拆分模式,不处理引号。如果你的文件中有带引号且包含分隔符的字段,就保持 .mode csv,只修改分隔符。

一个真实的完整流程

假设 orders.csv 是这样的:

order_id,customer,amount,ordered_at
1001,Ada,49.99,2026-01-12
1002,Boris,12.50,2026-01-13
1003,"Chen, Wei",199.00,2026-01-14

注意第 3 行的一个带引号字段里有逗号。下面是完整的会话:

在真实的 shell 中,这段 INSERT 会换成一条 .import --csv --skip 1 orders.csv orders。"Chen, Wei" 这个字段保持完整,因为 CSV 模式会识别引号。得益于列类型,amount 最终是实数,order_id 是整数。

把导入包在事务中

.import 每一行执行一条 INSERT。几千行没问题。如果有一百万行,它会慢得让人痛苦,除非你把整个导入包在一个事务中,这样 SQLite 就不会每插入一行就提交一次:

BEGIN;
.import --csv --skip 1 big_file.csv events
COMMIT;

只改这一处,就能把几分钟的导入缩短到几秒钟。如果导入中途出了错,ROLLBACK 会撤销已加载的部分,重试时也很方便。

你还可以在导入前删除索引、导入后重建索引,进一步加快速度:每一行都要维护索引,累积起来开销不小。

常见错误及解决方法

Error: expected N columns but found M:某一行的字段数与表不一致。通常是因为:

  • 没有引号的字段中混进了逗号。用正确的 CSV 引号规则重新导出,或者改用 .mode csv(RFC 4180)而不是 .mode tabs。
  • 文件末尾有一个空行。编辑文件,或者灵活地使用 --skip。
  • 表的列比 CSV 多。要么在文件中补上缺少的列,要么先插入到结构合适的临时表中,再复制到真正的表里。

表头被当成了数据:你在已有的表上忘了加 --skip 1。删除那一行(DELETE FROM t WHERE rowid = 1),然后加上参数重新运行。

数字被存成了字符串:你让 .import 自动建表,所以每一列都是 TEXT。删除这张表,用 INTEGER/REAL 列明确定义它,再重新导入。

Error: no such file:路径是相对于你启动 sqlite3 时所在的目录,而不是相对于数据库文件。使用绝对路径,或者在打开 shell 之前 cd 到正确的目录。

命令行工具会在报错时打印行号,这是在大文件中找到问题行最快的方法。

快速回顾

  • .import 是命令行工具的点命令,不是 SQL。在 sqlite3 shell 中运行它。
  • 用 --csv 正确处理引号,用 --skip 1 忽略表头。
  • 如果表不存在,.import 会根据表头创建它,但每一列都会是 TEXT。想要正确的类型,就自己建表。
  • 大批量导入时用 BEGIN/COMMIT 包起来,避免每一行都是一个事务。
  • 文件中的列顺序必须和表中的列顺序一致。

下一篇:导出数据

导入只是故事的一半。同一个 shell 也能把查询结果或整张表导出为 CSV、JSON 或 SQL,适用于备份、数据管道,以及把数据交给其他工具。这就是接下来的导出数据。

常见问题

如何把 CSV 文件导入 SQLite?

用 sqlite3 命令行工具打开数据库,用 .mode csv 切换到 CSV 模式,然后运行 .import data.csv table_name。如果表还不存在,SQLite 会创建它,并把文件的第一行用作列名。如果表已经存在,文件的每一行都会作为数据插入,所以通常要用 .import --skip 1 跳过表头。

如何把带表头的 CSV 导入 SQLite 中已有的表?

使用 .import --csv --skip 1 data.csv table_name。--skip 1 参数告诉 SQLite 忽略第一行,这样表头就不会变成一行数据。不加它的话,你会得到一行内容正是列名本身的数据。

为什么 SQLite 导入 CSV 时报错“expected N columns but found M”?

文件中有些行的列数与表不一致,通常是因为字段里有逗号、引号没有转义,或者末尾有空行。使用 .mode csv(或 --csv)而不是 .mode tabs,让 SQLite 按 RFC 4180 处理引号,再用文本编辑器检查文件中多余的分隔符。命令行工具会打印出错的行号,这是找到问题行最快的方法。

Coddy 编程语言插图

和 Coddy 一起学编程

开始