Menu

SQLite 视图:CREATE VIEW、临时视图与 INSTEAD OF

SQLite 中视图如何工作:把查询保存成虚拟表,什么时候用临时视图,以及为什么 SQLite 的视图默认是只读的。

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

视图就是保存起来的查询

视图是一条有名字的 SELECT 语句。创建之后,你可以像查询表一样查询它,但它并不存储任何东西。每次读取视图时,SQLite 都会重新执行底层的查询。

paid_orders 看起来像一张表,用起来也像一张表。它有列,你可以从中 SELECT,也可以连接它。但在底层,每次查询都会展开成原来的 WHERE status = 'paid' 过滤条件。

这就是全部的思维模型:视图是一条查询的别名。

视图为什么有用

最大的好处是命名。一条复杂的查询有了一个简短、达意的名字,其余的代码也就保持可读:

没有视图的话,每个调用方都要自己写 GROUP BY,其中任何一个都可能把过滤条件写错。有了视图,聚合逻辑只定义一次。调用方只需要查询 customer_totals,再在上面加上各自需要的过滤条件。

视图还可以充当一种类似权限的边界。如果某个查询不应该暴露 password_hash 列,就建一个选取除该列以外所有列的视图,让应用代码使用这个视图。

CREATE VIEW 语法

完整形式如下:

CREATE [TEMPORARY] VIEW [IF NOT EXISTS] view_name [(column_aliases)] AS
SELECT ...;

有几点值得了解:

  • IF NOT EXISTS:如果视图已经存在,就静默跳过创建。
  • TEMPORARY(或 TEMP):创建一个在连接关闭时消失的视图。
  • 括号中的列别名可以让你重命名视图的列,而不必改动底层的 SELECT。

这个视图对外提供了更友好的列名(item、dollars),而源表中的列名保持不变。

替换和删除视图

SQLite 没有 CREATE OR REPLACE VIEW,也没有 ALTER VIEW。要修改视图的定义,就先删除再重新创建:

DROP VIEW IF EXISTS active_orders; 是稳妥的写法,视图不存在时也不会报错。删除视图永远不会影响底层的表,你删掉的只是保存的那条查询。

临时视图

TEMP VIEW 只在当前数据库连接中存在。连接关闭后,视图就没了。在临时的分析会话中,不想留下多余的定义时很有用:

临时视图还能让你遮蔽某个查询名,而不必把它固定到表结构中,在探索数据时很方便。

视图默认是只读的

这是最重要的一个坑。你不能直接通过视图执行 INSERT、UPDATE 或 DELETE:

sqlite> INSERT INTO paid_orders (customer, amount) VALUES ('Eve', 50);
Runtime error: cannot modify paid_orders because it is a view

解决办法是 INSTEAD OF 触发器。你写一个触发器,让它代替这次写操作执行,并把它转换成对底层表的真实操作:

视图仍然是视图,但对它的写入现在有了去处。下一页会详细介绍触发器。

没有物化视图,自己动手实现

有些数据库允许把视图的结果缓存到磁盘上,按需刷新。SQLite 不行。每次读取视图都会重新执行底层的查询。对大多数场景来说这没问题,SQLite 很快,查询规划器也很出色。对于会被反复查询、开销又很大的聚合,就建一张真正的表,自己让它保持同步:

之后你可以定时刷新这个缓存,或者在 orders 上设置触发器让它保持最新。这需要手动处理,但在 SQLite 中也是唯一的选择。

列出视图

视图的元数据和表、索引一起保存在 sqlite_master 中:

sql 列会返回原始的 CREATE VIEW 语句,当你忘了某个视图是做什么的时候很有用。在命令行工具中,.schema view_name 会更清楚地打印出同样的内容。

什么时候该用视图

在这些情况下,视图物有所值:

  • 一条不简单的查询在三处或更多地方重复使用。命名一次胜过到处复制粘贴。
  • 你想向应用的某一部分只开放精选的一部分列或行。
  • 某个聚合在概念上是一个整体(monthly_sales、active_users),调用方应该把它当作一个名词来使用。

在这些情况下,不用视图:

  • 查询只在一个地方使用。直接写在那里就好。
  • 性能很重要,而底层查询开销很大:每次读取你都要付出这个代价。改为缓存到一张真正的表中。
  • 视图依赖另一个视图,而那个视图又依赖另一个视图。SQLite 处理嵌套没有问题,但三四层的视图链会让调试时很难看清实际执行的 SQL。

下一步:触发器

视图和触发器经常一起出现:让视图可写的 INSTEAD OF 模式,正是触发器存在的主要原因之一。触发器本身在审计日志、级联更新和维护数据不变量方面也很有用。这就是下一页的内容。

常见问题

SQLite 中的视图是什么?

视图是一条保存起来的 SELECT 语句,你可以像查询表一样查询它。它不存储任何数据,每次读取视图时,SQLite 都会重新执行底层的查询。视图适合给复杂的查询起一次名字、到处复用,或者隐藏调用方不应该看到的列。

SQLite 中能通过视图执行 INSERT 或 UPDATE 吗?

不能直接执行。SQLite 中的视图是只读的,对视图执行 INSERT、UPDATE 和 DELETE 都会失败。你可以给视图挂上 INSTEAD OF 触发器,把写操作转换成对底层表的操作,从而让视图可写。

SQLite 支持物化视图吗?

不支持。SQLite 只有普通的(虚拟)视图,每次读取视图时都会执行查询。如果需要缓存结果,就创建一张真正的表并自己刷新它,或者用触发器让它和源表保持同步。

如何列出 SQLite 数据库中的所有视图?

查询 sqlite_master:SELECT name FROM sqlite_master WHERE type = 'view';。在命令行工具中,.schema 会显示 CREATE VIEW 语句,.tables 会把视图和表一起列出来。

Coddy 编程语言插图

和 Coddy 一起学编程

开始