看清刚刚发生了什么
执行 INSERT、UPDATE 或 DELETE 时,SQLite 会告诉你改动了多少行,但不会告诉你是哪些行,也不会告诉你它们最终的值是什么。传统的变通办法是再跟一条 SELECT。这意味着两次往返、两条语句,还有一个小小的竞态窗口:在两条语句之间,别人可能已经改了这一行。
RETURNING 解决了这个问题。把它加在写入语句后面,列出你想取回的列,SQLite 就会把受影响的行交给你,就像你刚刚对它们执行了一次 SELECT:
一条语句,一次往返,你就拿到了生成的 id,以及数据库替你填上的默认 created_at 值。
RETURNING 是在 SQLite 3.35.0(2021 年 3 月)中加入的。如果你的语句被当作语法错误拒绝,用 SELECT sqlite_version(); 查看一下版本:旧版本不认识这个关键字。
取回生成的 ID
使用 RETURNING 最常见的原因,是在插入之后立刻拿到自动生成的主键:
在有 RETURNING 之前,你得先插入,再在同一个连接上调用 last_insert_rowid()(或者驱动提供的等价方法)。这仍然可行,但它依赖连接状态,在连接池或多线程下很容易出错。RETURNING id 是显式的,只作用于这条语句,不管连接由谁托管,行为都一样。
如果你的表没有声明显式的 INTEGER PRIMARY KEY,你仍然可以取回隐式的行标识符:
每张普通的 SQLite 表都有 rowid,RETURNING 会把它交给你。
多列与表达式
RETURNING 接受的写法和 SELECT 的列列表一样。可以列出列、使用 *、构造表达式,还能起别名:
当你想要所有内容(包括数据库填上的默认值),又不想逐个写列名时,RETURNING * 很方便:
你会看到新的 id、你传入的 name,以及 SQLite 计算出的时间戳。
在 UPDATE 中使用 RETURNING
在 UPDATE 中,RETURNING 给你的是更新后的值,也就是你的修改生效之后这一行的样子:
你拿到的是 Ada 的新余额 125,而不是旧的 100。这让 RETURNING 非常适合原子计数器和充值、扣款操作:你不需要先读取、计算、写入,再重新读取一遍。
如果 WHERE 匹配了多行,每一行受影响的行都会返回一行:
改了三行,就返回三行。返回顺序没有保证:如果你需要特定顺序,请在客户端对结果排序。
在 DELETE 中使用 RETURNING
在 DELETE 中,RETURNING 给你的是删除之前那一刻的行。这对归档、审计记录,或者只是确认删掉了什么都很有用:
你会取回两条已过期的会话,所有字段都完整保留,尽管它们已经不在表里了。如果你想把它们挪到别处,这正好可以配合一张归档表:在同一个事务中读取结果,再插入到别的地方。
在 UPSERT 中使用 RETURNING
RETURNING 也能和 INSERT ... ON CONFLICT ... DO UPDATE 一起使用。返回的行反映实际执行的是哪个分支:新插入,还是冲突时的更新:
把这条语句执行两次。第一次它插入并返回 ('visits', 1)。第二次触发冲突,值被加一,你拿到 ('visits', 2)。无论哪种情况,都是一条语句、返回一行,继续之前不需要再问“到底是插入了还是更新了?”
在 SQLite 中,要实现“给我当前值,没有就创建”而又不想多一次往返,这是最简洁的写法。
几点需要注意
有几个细节经常让人措手不及:
- 对于
INSERT和UPDATE,RETURNING看到的永远是修改之后的行;对于DELETE,看到的是修改之前的行。没有语法可以要另一边的值。 - 返回行的顺序没有保证。如果顺序重要,请在客户端加上
ORDER BY。 - 不能把
RETURNING放进子查询里。它是写入语句的顶层子句,而不是表达式。 RETURNING不会返回BEFORE触发器修改过的数据,它返回的是实际写入的值。AFTER触发器在写入之后、返回行之前执行。- 生成列和
DEFAULT值在结果中都可见。这正是RETURNING *能快速查看数据库替你填了什么的原因。
下一步:导入 CSV 数据
当你一次只写一行或几行,并想立刻看到结果时,RETURNING 非常好用。当你要从文件中加载成千上万行时,你会改用 SQLite 的 CSV 导入工具,这就是下一页的内容。
常见问题
SQLite 支持 RETURNING 子句吗?
支持,从 3.35.0 版本(2021 年 3 月发布)开始。你可以在 INSERT、UPDATE 和 DELETE 语句后面加上 RETURNING,取回受影响的行。如果你用的是更旧的 SQLite,解析器会拒绝它,可以用 SELECT sqlite_version(); 查看版本。
在 SQLite 中如何获取刚插入的行的 ID?
用 INSERT ... RETURNING id(如果表没有显式的主键,就用 RETURNING rowid)。它在同一条语句中把生成的值返回给你,所以不需要再用 last_insert_rowid() 之类的第二条查询。
RETURNING 可以返回多列吗?
可以。像 SELECT 一样列出你想要的列,用逗号分隔:RETURNING id, name, created_at。也可以用 RETURNING * 取回所有列,或者写表达式,比如 RETURNING id, price * quantity AS total。
RETURNING 能和 UPSERT、ON CONFLICT 一起用吗?
能。INSERT ... ON CONFLICT ... DO UPDATE ... RETURNING ... 无论这一行是新插入的,还是因冲突处理而被更新的,都会把它返回。这是在一次往返中完成 upsert 并读取结果状态最简洁的方式。