- 视图 是一个命名的存储查询,您可以像表一样从中进行选择。
- 普通视图不存储数据,因此它始终显示当前结果。
- 视图简化了复杂的查询,保持业务逻辑一致,并可以限制对列的访问。
- 尽管物化视图可以,但它们本身并不能加快查询速度。
相同的二十行报表查询已复制到五个仪表板中,每个副本都有一点偏差。一个会计算已取消的订单,而另一个则不会。 SQL 视图 通过在数据库中以明确的名称定义一次逻辑来解决此问题。
我们在 SQLite 3.50 中运行了下面的每个示例,显示的结果来自该运行。视图是标准 SQL,但通过视图更新、物化视图和依赖关系处理等细节因数据库而异,我们会指出这些。
什么是视图?
视图是一个以名称保存在数据库中的 SELECT 语句。当您查询视图时,数据库会运行已保存的查询。它的作用就像一个用于读取的表,但普通视图不保存自己的数据副本。
想想电子表格中保存的过滤器。过滤器不是工作表的第二个副本。这是查看相同数据的一种方式,这些数据始终反映最新值。
样本表
CREATE TABLE customers (
id INT PRIMARY KEY, name TEXT, city TEXT, email TEXT, credit_limit INT
);
CREATE TABLE orders (
id INT PRIMARY KEY, customer_id INT, total INT, status TEXT
);
INSERT INTO customers VALUES
(1,'Asha','Pune','asha@example.com',50000),
(2,'Ben','Delhi','ben@example.com',20000),
(3,'Chitra','Pune','chitra@example.com',80000);
INSERT INTO orders VALUES
(101,1,1200,'paid'), (102,1,800,'paid'),
(103,2,300,'pending'), (104,3,2500,'paid');
创建视图
该视图汇总了每个客户的订单。它将 join、分组和 CASE 表达式 隐藏在一个简单的名称后面:
CREATE VIEW customer_sales AS
SELECT c.id, c.name, c.city,
COUNT(o.id) AS orders,
COALESCE(SUM(CASE WHEN o.status = 'paid' THEN o.total END), 0) AS paid_total
FROM customers c
LEFT JOIN orders o ON o.customer_id = c.id
GROUP BY c.id, c.name, c.city;
现在任何人都可以使用它,而无需知道它是如何工作的:
SELECT * FROM customer_sales ORDER BY paid_total DESC;
-- 3 | Chitra | Pune | 1 | 2500
-- 1 | Asha | Pune | 2 | 2000
-- 2 | Ben | Delhi | 1 | 0
SELECT name, paid_total FROM customer_sales WHERE city = 'Pune';
-- Asha | 2000
-- Chitra | 2500
Ben 显示为零,因为视图使用 LEFT JOIN 和 COALESCE。像这样的决策,只要在视图中做出一次,就可以保持报告的一致性。要刷新您对 NULL 处理的理解,请参阅 SQL NULL 值。
视图始终是最新的
由于普通视图每次都会运行查询,因此新数据会立即出现。我们为Ben添加了一个付费订单,并再次查询视图:
INSERT INTO orders VALUES (105, 2, 900, 'paid');
SELECT name, paid_total FROM customer_sales WHERE name = 'Ben';
-- Ben | 900
没有刷新步骤,也没有要保持同步的副本。这是与将查询结果保存到单独的表中的主要区别。
隐藏列
视图通常用于仅公开个人或应用程序需要的列。此处省略了电子邮件地址和信用额度:
CREATE VIEW public_customers AS
SELECT id, name, city FROM customers;
-- columns: id, name, city
在具有权限的数据库中,例如 PostgreSQL、SQL Server 和 MySQL,您可以授予报告用户对视图的访问权限,但不能授予基础表的访问权限。 SQLite 没有用户帐户,因此我们只能显示列限制本身。将视图视为一层保护。敏感数据仍然需要适当的访问控制,并且某些数据库提供行级安全性以按用户过滤行。
性能:浏览量并不能提高速度
一个常见的误解是视图使查询更快。当您查询普通视图时,它会被其定义所取代,因此数据库的工作与您自己编写完整查询相同。我们的执行计划显示视图的连接和分组作为查询的一部分运行:
EXPLAIN QUERY PLAN SELECT * FROM customer_sales WHERE city = 'Pune';
-- CO-ROUTINE customer_sales
-- SCAN c ...
-- SEARCH o USING AUTOMATIC COVERING INDEX (customer_id=?) LEFT-JOIN
-- USE TEMP B-TREE FOR GROUP BY
-- SCAN customer_sales
所以慢查询的视图仍然是慢查询。速度来自索引和良好的查询设计,如 SQL 查询优化 和 SQL 索引解释 中所述。还要注意建立在其他视图之上的视图。多层嵌套使得在不经意间多次连接同一个大表变得很容易。
物化视图
物化视图确实存储其结果,就像缓存表一样。读取速度很快,但数据仅与上次刷新一样新鲜。
-- PostgreSQL
CREATE MATERIALIZED VIEW daily_sales AS
SELECT order_date, SUM(total) AS revenue FROM orders GROUP BY order_date;
REFRESH MATERIALIZED VIEW daily_sales;
| 特点 | 普通视图 | 物化视图 |
|---|---|---|
| 存储数据 | 否 | 是的 |
| 新鲜度 | 始终最新 | 截至上次刷新 |
| 读取速度 | 与查询相同 | 快 |
| 仓储费用 | 无 | 是的 |
| 适合 | 简化、一致性 | 大量报告和仪表板 |
支持情况各不相同。 PostgreSQL 和 Oracle 具有物化视图,而 SQL Server 则提供具有自己规则的索引视图。 SQLite 和 MySQL 缺乏它们,因此人们使用按计划刷新的定期汇总表。我们没有在本文中运行 PostgreSQL 示例。
提示: 在创建物化视图之前,请确定数据允许的陈旧程度。 “每小时刷新一次”必须是阅读报告的人可以接受的,所以告诉他们。
您可以更新视图吗?
这取决于数据库和视图。单个表的简单视图(没有分组或 DISTINCT)通常可以在 PostgreSQL、MySQL 和 SQL Server 中更新,并且更改会传递到表。具有联接或聚合的视图通常不能,因为数据库无法判断要更改哪一行。
SQLite 根本不允许直接写入视图。我们的尝试失败了:
INSERT INTO customer_sales (name) VALUES ('X');
-- cannot modify customer_sales because it is a view
一些数据库支持 INSTEAD OF 触发器来使视图可写,并且许多数据库提供 WITH CHECK OPTION,它会停止通过视图不可见的写入。在实践中,大多数团队将视图保持为只读并写入表。
依赖性和破坏
视图取决于它所读取的表。我们在表上创建了一个视图,然后删除了该表:
CREATE TABLE ghost AS SELECT 1 AS x;
CREATE VIEW v_ghost AS SELECT x FROM ghost;
DROP TABLE ghost;
SELECT * FROM v_ghost;
-- no such table: main.ghost
SQLite 允许删除,但仅在稍后使用视图时失败。其他数据库(例如 PostgreSQL)拒绝删除视图所依赖的表,除非您添加 CASCADE,这也会删除视图。无论哪种方式,重命名或删除列都可能会破坏视图,因此在架构更改后,请在回归测试中检查它们。
视图中的 SELECT * 还存在一个微妙的陷阱。我们创建了 CREATE VIEW star AS SELECT * FROM customers,然后向表中添加了一列。在 SQLite 中,视图选取了新列。其他数据库(包括 PostgreSQL)会在创建视图时展开列列表,因此不会出现新列。因此明确列出列。它记录了视图,并且在任何地方的行为都是相同的。
管理视图
| 任务 | 常用语法 |
|---|---|
| 创建 | 创建视图名称作为选择... |
| 更换 | CREATE OR REPLACE VIEW(PostgreSQL、MySQL、Oracle) |
| 在 SQL Server 中替换 | 创建或更改视图 |
| 删除 | 下拉视图名称 |
| 安全移除 | 如果存在则删除视图名称 |
删除视图只会删除已保存的查询。在我们的测试中,删除 public_customers 后,customers 表仍然包含所有三行。将视图定义与迁移一起保留在版本控制中,以便像任何其他代码一样审查更改。
视图、CTE 与表格
| 选项 | 终身 | 存储数据 | 最适合 |
|---|---|---|---|
| 查看 | 直到掉落 | 否 | 许多查询使用的共享逻辑 |
| 热膨胀系数 | 一份声明 | 否 | 组织单个复杂查询 |
| 物化视图 | 直到掉落 | 是的 | 快速读取昂贵的结果 |
| 表 | 直到掉落 | 是的 | 实际数据 |
常见错误
- 期望速度提升。普通视图每次都会运行其查询。
- 在视图中使用
SELECT *。 当表更改时,数据库之间的行为有所不同。 - 深度嵌套。 基于视图构建的视图隐藏了昂贵的连接。
- 在视图内使用 ORDER BY。 查询时不能保证顺序,因此请在外部查询中排序。
- 不明确的名称。 前缀(例如
v_)或明确的名词(例如customer_sales)会有所帮助。 - 迁移期间忘记视图。在每次架构更改后测试它们。
最后的话
视图是一种将重要逻辑放在数据库中的简单方法。使用它们来保持报告的一致性并隐藏不需要的列,而不是作为性能技巧。首先,找到出现在代码中多个位置的查询,将其转换为具有显式列的视图,并将每个调用者指向它。
常见问题
SQL 中的视图是什么?
视图是一个已保存的带有名称的 SELECT 查询。您可以像查询表一样查询它,数据库每次都会运行基础查询,因此结果始终是最新的。
SQL视图是否存储数据?
普通视图不存储数据,只存储查询。物化视图确实存储结果,并且必须刷新才能保持最新。
视图比表快吗?
不会。普通视图与其基础查询执行相同的工作。物化视图可以加快读取速度,但会牺牲存储和新鲜度。
可以插入到视图中吗?
有时。简单的单表视图通常可以在 PostgreSQL、MySQL 和 SQL Server 中更新。具有连接或分组的视图通常不是这样,SQLite 也不允许这样做。
如果删除表,视图会发生什么情况?
这取决于数据库。有些视图在视图依赖表时拒绝删除表,而另一些则允许删除表,但视图在查询时会失败。
继续学习
需要测试、WordPress 或 SEO 方面的帮助?
我们帮助团队交付更好的软件并发展他们的网站。告诉我们您正在做什么,我们将建议明确的下一步。
更多指南 测试、人工智能和现代开发
喜欢这篇文章吗?选择一个主题并直接跳至我们最新的教程。
人工智能幻觉解释:语言模型为何会产生幻觉以及如何减少它
人工智能幻觉解释:它们是什么,为什么语言模型产生它们,常见类型,如何通过基础和提示来减少它们,以及我们进行的简单检查。
阅读文章 →DevOps环境变量解释:.env 文件、秘密和配置正确完成
使用我们运行的 Python 示例解释环境变量:读取和设置它们、为什么每个值都是字符串、.env 文件、进程继承、秘密和验证。
阅读文章 →软件开发API 速率限制解释:算法、标头和处理 429 错误
API 速率限制通过我们运行的模拟进行了解释:令牌桶、固定窗口、滑动窗口、429 响应、速率限制标头、带回退的重试和测试限制。
阅读文章 →测试自动化Python 中的模拟:unittest.mock、补丁以及导致测试谎言的错误
使用unittest.mock 在Python 中进行模拟,经过测试:模拟和return_value、side_effect、修补使用名称的位置、spec 和autospec、模拟日期以及常见错误。
阅读文章 →软件测试代码覆盖率解释:行覆盖率与分支覆盖率以及 100% 的真正含义
用真实的coverage.py运行来解释代码覆盖率:行、分支和条件覆盖率,阅读报告,为什么100%覆盖率仍然会错过错误,以及合理的目标。
阅读文章 →数据库与 SQLSQL 中的 DELETE、TRUNCATE 和 DROP:差异、回滚以及何时使用它们
DELETE、TRUNCATE 和 DROP 通过经过测试的 SQL 进行了解释:各自删除什么、回滚、触发器、身份重置、速度,以及 PostgreSQL、MySQL、SQL Server 和 SQLite 有何不同。
阅读文章 →数据库与 SQLSQL 视图解释:创建、使用和避免陷阱(附示例)
SQL 视图通过我们运行的查询进行解释:创建视图、实时结果、隐藏列、性能、可更新和物化视图、破坏的依赖关系和最佳实践。
阅读文章 →软件开发TypeScript 接口与类型:真正的差异,使用编译器进行测试
TypeScript 接口与类型,使用真实编译器进行检查:两者都可以做什么,只有一个可以做什么,声明合并,扩展与交集以及要遵循的简单规则。
阅读文章 →软件开发Python *args 和 **kwargs 解释:灵活的函数和清晰的示例
Python *args 和 **kwargs 用我们运行的代码进行了解释:打包和解包、参数顺序、仅关键字和仅位置参数、装饰器和常见错误。
阅读文章 →软件开发Python Lambda 函数:语法、实际用途以及何时不使用它们
Python lambda 函数用我们运行的代码进行了解释:语法、使用键排序、映射和过滤器、闭包、后期绑定陷阱,以及何时普通 def 是更好的选择。
阅读文章 →人工智能什么是人工智能代理?它们是如何工作的,只需运行一个简单的代理循环
什么是人工智能代理?关于代理循环、工具、内存和护栏的简单指南,以及我们运行的一个小型 Python 循环、真正的风险以及何时简单的聊天机器人是更好的选择。
阅读文章 →DevOps监控与可观察性:真正的区别,包括日志、指标和跟踪
监控与可观察性解释:已知和未知的故障、日志、指标和跟踪、检查事件演练、黄金信号、OpenTelemetry 和有用的警报。
阅读文章 →DevOpsDocker Volumes 与 Bind Mounts:以正确的方式保留容器数据
Docker 卷与绑定挂载解释:为什么容器数据消失、命名卷、绑定挂载、tmpfs、Compose 示例、备份、权限以及何时使用它们。
阅读文章 →测试自动化TDD 与 BDD:主要区别、以两种方式构建的一个功能以及何时使用每种方式
TDD 与 BDD 的解释是通过一种双向构建的折扣功能:使用我们运行的代码进行红绿重构、Given-When-Then 场景、工具、权衡以及何时使用每种方案。
阅读文章 →软件开发Git merge 与 rebase 的区别:示例及如何选择
用真实的 log 输出、对比表、rebase 黄金法则和安全的团队工作流,讲清 Git merge 与 rebase 的区别。
阅读文章 →软件开发Python 列表推导式:语法、示例与何时避免使用
Python 列表推导式详解:语法、过滤、if-else、嵌套循环、实测耗时,以及何时普通 for 循环更合适。
阅读文章 →数据库与 SQLSQL JOIN 详解:附示例(INNER、LEFT、RIGHT、FULL)
通过示例表和查询,一步步讲解 SQL JOIN:INNER、LEFT、RIGHT、FULL、CROSS 和 SELF,并介绍常见错误与性能技巧。
阅读文章 →