SQL 视图解释:创建、使用和避免陷阱(附示例)

  1. 首页
  2. 博客
  3. 数据库和 SQL

数据库与 SQL

SQL 视图解释:创建、使用和避免陷阱,附示例

视图是保存的查询,您可以像表一样使用它。了解如何创建报告、为什么视图使报告保持一致、它们对性能做了什么和不做什么,以及破坏它们的陷阱。

阅读约 8 分钟
SQL 视图解释:创建、使用和避免陷阱(附示例)

要点速览

  • 视图 是一个命名的存储查询,您可以像表一样从中进行选择。
  • 普通视图不存储数据,因此它始终显示当前结果。
  • 视图简化了复杂的查询,保持业务逻辑一致,并可以限制对列的访问。
  • 尽管物化视图可以,但它们本身并不能加快查询速度。

相同的二十行报表查询已复制到五个仪表板中,每个副本都有一点偏差。一个会计算已取消的订单,而另一个则不会。 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 也不允许这样做。

如果删除表,视图会发生什么情况?

这取决于数据库。有些视图在视图依赖表时拒绝删除表,而另一些则允许删除表,但视图在查询时会失败。

继续学习

与Stack Quora合作

需要测试、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%覆盖率仍然会错过错误,以及合理的目标。

阅读文章 →
数据库与 SQL

SQL 中的 DELETE、TRUNCATE 和 DROP:差异、回滚以及何时使用它们

DELETE、TRUNCATE 和 DROP 通过经过测试的 SQL 进行了解释:各自删除什么、回滚、触发器、身份重置、速度,以及 PostgreSQL、MySQL、SQL Server 和 SQLite 有何不同。

阅读文章 →
数据库与 SQL

SQL 视图解释:创建、使用和避免陷阱(附示例)

SQL 视图通过我们运行的查询进行解释:创建视图、实时结果、隐藏列、性能、可更新和物化视图、破坏的依赖关系和最佳实践。

阅读文章 →
软件开发

TypeScript 接口与类型:真正的差异,使用编译器进行测试

TypeScript 接口与类型,使用真实编译器进行检查:两者都可以做什么,只有一个可以做什么,声明合并,扩展与交集以及要遵循的简单规则。

阅读文章 →
软件开发

Python *args 和 **kwargs 解释:灵活的函数和清晰的示例

Python *args 和 **kwargs 用我们运行的代码进行了解释:打包和解包、参数顺序、仅关键字和仅位置参数、装饰器和常见错误。

阅读文章 →
软件开发

Python Lambda 函数:语法、实际用途以及何时不使用它们

Python lambda 函数用我们运行的代码进行了解释:语法、使用键排序、映射和过滤器、闭包、后期绑定陷阱,以及何时普通 def 是更好的选择。

阅读文章 →
人工智能

什么是人工智能代理?它们是如何工作的,只需运行一个简单的代理循环

什么是人工智能代理?关于代理循环、工具、内存和护栏的简单指南,以及我们运行的一个小型 Python 循环、真正的风险以及何时简单的聊天机器人是更好的选择。

阅读文章 →
DevOps

监控与可观察性:真正的区别,包括日志、指标和跟踪

监控与可观察性解释:已知和未知的故障、日志、指标和跟踪、检查事件演练、黄金信号、OpenTelemetry 和有用的警报。

阅读文章 →
DevOps

Docker 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 循环更合适。

阅读文章 →
数据库与 SQL

SQL JOIN 详解:附示例(INNER、LEFT、RIGHT、FULL)

通过示例表和查询,一步步讲解 SQL JOIN:INNER、LEFT、RIGHT、FULL、CROSS 和 SELF,并介绍常见错误与性能技巧。

阅读文章 →