Menu

SQLite INSERT:插入行、批量插入与 OR IGNORE

SQLite 中 INSERT 的用法:插入单行、一次插入多行、INSERT...SELECT、默认值,以及 OR IGNORE / OR REPLACE 冲突处理修饰符。

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

INSERT 向表中添加行

INSERT 是用来把新行放进表中的语句。它的结构简短而固定:

需要注意三个部分:

  • INSERT INTO books:目标表。
  • (title, author, year):你要提供值的列。
  • VALUES (...):值本身,顺序与列名列表一致。

id 不在列名列表中,所以 SQLite 会自动为它赋值(它是 INTEGER PRIMARY KEY,会取得 rowid)。你跳过的任何列都会使用它的默认值;如果没有默认值,就是 NULL。

始终列出列名

你可以省略列名列表,按声明顺序为每一列提供值:

-- Works, but fragile:
INSERT INTO books VALUES (NULL, 'Dune', 'Frank Herbert', 1965);

但不要这样做。一旦有人给 books 添加了一列,所有这样的语句要么出错,要么开始把值插进错误的列。把列名写清楚:

明确的列名列表也是一种文档:语句本身就能读懂,不用再去翻找表的定义。

插入多行

列出更多的值元组,就能在一条语句中插入多行:

这比写三条单独的 INSERT 语句更简洁,SQLite 也会把整个操作当作一条语句处理。不过,批量加载时真正的性能提升来自把插入包在事务中,下面就来讲这个。

批量插入:用事务包起来

默认情况下,每条 INSERT 都是一个独立的事务。SQLite 在每个事务结束时都会执行一次 fsync,这才是简单循环插入很慢的原因,而不是插入本身。

把它们组合起来:

一次 fsync,而不是五次。对于成千上万行,差距可能达到两到三个数量级。如果中间有任何一步失败,ROLLBACK 会撤销整批操作。

这就是批量插入的标准做法。无论你是从 Python、Node 还是 Rust 调用 SQLite,都要用 BEGIN / COMMIT 把循环包起来。

INSERT ... SELECT:从另一张表复制

你可以用查询结果而不是字面值来填充表:

SELECT 的列按位置与 INSERT 的列名列表对应。名字不必相同,顺序必须一致。这是归档行、构建报表用的表,或在迁移时复制部分数据的标准做法。

DEFAULT VALUES 与跳过的列

如果某一列有 DEFAULT 子句,你可以把它从列名列表中省略,SQLite 会填入默认值:

created_at 得到了当前时间戳,因为我们没有提供它的值。如果你想要一整行都由默认值组成(适合用作占位行),就用 DEFAULT VALUES 的形式:

新增了两行,value 都是 0,id 也都是自动分配的。

INSERT OR IGNORE:跳过重复行

当一行会违反 UNIQUE 或 PRIMARY KEY 约束时,默认行为是中止语句并报错:

Error: UNIQUE constraint failed: users.email

INSERT OR IGNORE 把这种行为换成“悄悄跳过有问题的那一行”:

留下了三行。重复的那一行被丢弃,没有报错。对于简单的初始化数据,这是在 SQLite 中表达“不存在才插入”的惯用写法:不需要先单独 SELECT 检查,也不需要异常处理。

INSERT OR REPLACE:覆盖重复行

INSERT OR REPLACE 会删除冲突的行,并在原处插入新行:

有一点要小心:REPLACE 是 DELETE 加 INSERT,而不是 UPDATE。如果被删除的行有设置了 ON DELETE CASCADE 的外键指向它,这些子行也会被删除。而且新 INSERT 中没有列出的列都会重置为默认值,不会保留原来的值。

对于大多数“存在就更新,不存在就插入”的情况,你真正需要的是用 ON CONFLICT ... DO UPDATE 实现的 upsert。它有单独的一页介绍。

快速回顾

  • INSERT INTO table (cols) VALUES (...):基本形式。始终列出列名。
  • 一次插入多行时,在 VALUES 后面写用逗号分隔的元组。
  • 真正的批量加载,要用 BEGIN / COMMIT 把插入包起来。
  • INSERT INTO ... SELECT ... 从查询结果中复制行。
  • DEFAULT VALUES 只用默认值生成一行;被跳过的列也会使用各自的默认值。
  • INSERT OR IGNORE 跳过冲突的行;INSERT OR REPLACE 覆盖它们(通过先删除再插入)。

下一篇:UPDATE

插入行只是故事的一半。另一半是修改已经存在的行:给计数器加一、修正错别字、把订单标记为已发货。这就是 UPDATE,它也有一套值得养成的好习惯(尤其是 WHERE 子句)。下一篇就会讲到。

常见问题

SQLite 中如何插入一行?

使用 INSERT INTO table (col1, col2) VALUES (val1, val2);。列出列名是可选的,但强烈推荐:这样即使表以后新增了列,语句也能照常工作。不写列名列表时,你必须按声明顺序为每一列提供值。

SQLite 中如何一次插入多行?

在 VALUES 后面写多个用括号括起来的元组,用逗号分隔:INSERT INTO t (a, b) VALUES (1, 2), (3, 4), (5, 6);。真正的批量加载(成千上万行)要用 BEGIN 和 COMMIT 把插入包在一个事务中:提速来自事务,而不是多行语法本身。

SQLite 中 INSERT OR IGNORE 有什么作用?

INSERT OR IGNORE 会跳过违反 UNIQUE、PRIMARY KEY 或 NOT NULL 约束的行,而不是报错。冲突的行被悄悄丢弃,语句的其余部分继续执行。当你想要“不存在才插入”的效果,又不想单独检查是否存在时,就用它。

为什么 INSERT 时会报“UNIQUE constraint failed”?

SQLite 发现 UNIQUE 或 PRIMARY KEY 列中已经有一行的值与之相同。要么这个值确实重复了,要么你在重复运行初始化数据的脚本。改用 INSERT OR IGNORE 跳过重复行,用 INSERT OR REPLACE 覆盖它们,或者用 ON CONFLICT ... DO UPDATE(upsert)进行更精细的控制。

Coddy 编程语言插图

和 Coddy 一起学编程

开始