视图能当表用:聚合、排序、再 JOIN 都行,还能在视图上再建视图(别超过两层)。
WITH CHECK OPTION 是个防呆开关:拦住那些「插进去就看不见」的数据。
WITH ENCRYPTION 挡的是同事看你的 SQL,不是安全措施——有 sysadmin 权限的人照样能挖出来。
10.1 讲概念,10.2 讲建改删,这一节收尾:把视图真正用起来,以及两个不太常用但很有用的开关。
教材 10.3 分五小节:从视图内调取数据、使用视图的好处、修改视图中的数据、加密视图、小结。
「小结」和「实践与练习」按惯例并进本篇末尾。
本沙箱还是那两处绕法: WITH CHECK OPTION 没有,
用 INSTEAD OF 触发器里的 RAISE(ABORT, ...) 复刻;WITH ENCRYPTION 没有,打原码讲原理。
一、从视图里取数:跟表一样用
视图建好之后,你在 FROM 后面写它,就跟写表名一样。 聚合、排序、再 JOIN,都行:
SELECT uname, SUM(total) AS 消费合计
FROM v_member_order
GROUP BY uname ORDER BY 消费合计 DESC
注意这个视图本身就是 JOIN 出来的(会员表连订单表),
外面的查询照样能对它聚合——数据库会把两层合并成一条语句去执行。
还能在视图上再建视图。 演示里就做了一个「大额消费」视图,但建议别超过两层——三层以上,
改的时候得一层层往下翻,很容易改错地方还以为改对了。
二、用视图的四条好处
| 好处 | 具体怎么体现 |
|---|---|
| 简化查询 | 三表 JOIN 包成一个视图,前端只写 SELECT * FROM v |
| 隔离表结构变化 | 表改列名,只改视图,程序不用动 |
| 控制权限 | 只给视图权限,不给基表——看不到工资列 |
| 统一口径 | 「有效订单」的定义写一次,全公司都用它 |
第三条最实用,也最容易被忽略。 比如 tb_member 里有手机号,
报表只需要用户名和积分——建一个只含两列的视图给对方,他就看不到手机号。
而且他连改都改不了:只给了视图的 SELECT 权限,没给基表的写权限,
他想 UPDATE 也没门。 这比「叮嘱他别乱改」靠谱得多。
第四条是长期维护的关键。 「有效订单」到底算不算已取消的?
这种口径如果每个报表各写一遍,早晚不一致。 写进视图,全公司查同一个东西。
三、修改视图中的数据:多表视图为什么不行
UPDATE v_member_order SET points = points + 10
WHERE uname = 'member001';
GO
这条在 SQL Server 上会报错,因为 v_member_order 是会员表 JOIN 订单表出来的。
报错的大意是「视图或函数不可更新,因为修改会影响多个基表」。道理不复杂:
member001 在视图里有两行(他有两笔订单),points 是会员表上的字段,改它到底改哪一行?
这就是「保留表」概念的用处。 一对多 JOIN 里,
「一」那边是保留表,「多」那边不是——多那边的一行在视图里被复制了好几份。
四、WITH CHECK OPTION:别让数据从窗口溜走
CREATE VIEW v_on_sale2
AS SELECT id, gname, price, is_on FROM tb_goods WHERE is_on = 1
WITH CHECK OPTION;
GO
没有这个子句时会出这么一种怪事: 往视图里 INSERT 一条 is_on = 0 的记录——
成功了。但你再查视图,看不见它。
数据「从窗口溜走了」。 人还以为没插进去,于是又插一遍,表里就多了一堆看不见的脏数据。
WITH CHECK OPTION 就是拦住这个: 新数据必须满足视图的 WHERE 条件,
否则直接报错。 这是个防呆开关,值得默认加上。
本沙箱不支持这个子句,演示里用 INSTEAD OF 触发器加 RAISE(ABORT, ...) 复刻,
拦住的效果一样,实现方式不同。
五、加密视图:挡人不挡自己
CREATE VIEW v_secret
WITH ENCRYPTION
AS SELECT id, gname, price FROM tb_goods;
GO
加了之后 sp_helptext 'v_secret' 会说「对象已被加密」,看不到定义。
SQL Server 的做法是把定义文本混淆后存进 sys.sql_modules。
但这不是安全措施。 它挡的是「同事顺手看一眼你的 SQL」,
挡不住有 sysadmin 权限的人——那种权限本来就能看到一切。
而且它有个实际代价:加密之后逻辑改不了,只能删了重建。所以别到处加——
等真有人抱怨「这段代码看不懂又不能改」的时候,代价就出来了。
六、动手 Demo:视图当表用、CHECK OPTION 的效果与加密
这一段把视图真正用起来:在两个表连出来的视图上做聚合、再在视图上建视图。
然后用 INSTEAD OF 触发器复刻 WITH CHECK OPTION——插一条 is_on = 0 的记录,看它怎么被拦。
import sqlite3
c = sqlite3.connect(u":memory:")
# ... 建六表、装样例数据 ...
c.execute(u"CREATE VIEW v_member_order AS "
u"SELECT m.uname, m.points, o.order_no, o.total, o.state "
u"FROM tb_member m JOIN tb_order o ON o.member_id = m.id")
# 视图当表用:聚合
print(c.execute(u"SELECT uname, SUM(total) AS 消费合计 FROM v_member_order "
u"GROUP BY uname ORDER BY 消费合计 DESC").fetchall())
# 视图上再建视图
c.execute(u"CREATE VIEW v_big_spender AS "
u"SELECT uname, total FROM v_member_order WHERE total > 1000")
# 复刻 WITH CHECK OPTION:触发器里自己判断
c.execute(u"CREATE TRIGGER trg_v_on_sale3_ins INSTEAD OF INSERT ON v_on_sale3 "
u"BEGIN SELECT RAISE(ABORT, '新行必须满足 is_on = 1') "
u"WHERE NEW.is_on <> 1; "
u"INSERT INTO tb_goods (id, gname, price, is_on) "
u"VALUES (NEW.id, NEW.gname, NEW.price, NEW.is_on); END")
print(c.execute(u"INSERT INTO v_on_sale3 VALUES (2001, '测试下架品', 9.9, 0)"))
实跑输出:
--------------------------------------------------------------------
从视图取数、视图的好处、加密与检查选项
--------------------------------------------------------------------
① 从视图里取数:跟表一样用
--------------------------------------------------------------------
1> CREATE VIEW v_member_order AS
SELECT m.uname, m.points, o.order_no, o.total, o.state
FROM tb_member m JOIN tb_order o ON o.member_id = m.id
1> SELECT uname, SUM(total) AS 消费合计
FROM v_member_order
GROUP BY uname
ORDER BY 消费合计 DESC
uname 消费合计
-------------------
member001 5487.9
member002 1299
member003 977.9
member004 549
(4 行受影响)
视图可以当表用:能聚合、能排序、能再跟别的表 JOIN。
也可以在视图上再建视图——但别超过两层,改起来会找不到源头。
1> CREATE VIEW v_big_spender AS SELECT uname, total FROM v_member_order WHERE total > 1000
1> SELECT * FROM v_big_spender
uname total
-----------------
member001 5088.9
member002 1299
(2 行受影响)
② 用视图的好处:四条
--------------------------------------------------------------------
| 好处 | 具体怎么体现 |
|---|---|
| 简化查询 | 三表 JOIN 包成一个视图,前端只写 SELECT * FROM v |
| 隔离表结构变化 | 表改列名,只改视图,程序不用动 |
| 控制权限 | 只给视图权限,不给基表——看不到工资列 |
| 统一口径 | 「有效订单」的定义写一次,全公司都用它 |
第三条最实用。比如 tb_member 里有手机号,报表只需要用户名和积分——
建一个只含两列的视图给对方,他就看不到手机号,也不用担心他改错数据。
③ 修改视图中的数据(SQL Server 的写法)
--------------------------------------------------------------------
[教材原码 · 本沙箱不执行]
| UPDATE v_member_order SET points = points + 10
| WHERE uname = 'member001';
| GO
这条在 SQL Server 上会报错——因为 v_member_order 是 JOIN 出来的多表视图。
报错原话大意是「视图或函数不可更新,因为修改会影响多个基表」。
多表视图要写得动,得满足更严的条件(比如只改其中一张保留表的列)。
④ WITH CHECK OPTION:别让数据从窗口溜走
--------------------------------------------------------------------
[教材原码 · 本沙箱不执行]
| CREATE VIEW v_on_sale2
| AS SELECT id, gname, price, is_on FROM tb_goods WHERE is_on = 1
| WITH CHECK OPTION;
| GO
没有它的时候,往视图里插一条 is_on = 0 的记录会成功——插完再查视图,看不见它。
数据「从窗口溜走了」,人还以为没插进去。加了 WITH CHECK OPTION 就拦住:
新数据必须满足视图的 WHERE 条件,否则报错。这是个防呆开关。
SQLite 不支持这个子句,等价做法是在 INSTEAD OF 触发器里自己判断:
1> CREATE VIEW v_on_sale3 AS SELECT id, gname, price, is_on FROM tb_goods WHERE is_on = 1
1> CREATE TRIGGER trg_v_on_sale3_ins INSTEAD OF INSERT ON v_on_sale3 BEGIN
SELECT RAISE(ABORT, '新行必须满足 is_on = 1')
WHERE NEW.is_on <> 1;
INSERT INTO tb_goods (id, gname, price, is_on)
VALUES (NEW.id, NEW.gname, NEW.price, NEW.is_on); END
1> INSERT INTO v_on_sale3 (id, gname, price, is_on) VALUES (2001, '测试下架品', 9.9, 0)
消息 102,级别 15,状态 1
新行必须满足 is_on = 1
被拦住了。虽然实现方式不同(一个是子句,一个是触发器),拦住的效果一样。
⑤ 加密视图:让人看不到定义
--------------------------------------------------------------------
[教材原码 · 本沙箱不执行]
| CREATE VIEW v_secret
| WITH ENCRYPTION
| AS SELECT id, gname, price FROM tb_goods;
| GO
加了 WITH ENCRYPTION 之后,sp_helptext 'v_secret' 会告诉你「对象已被加密」。
SQL Server 的做法是把定义文本混淆后存进 sys.sql_modules,谁也读不出来。
! 这不是「安全」——它挡的是同事看你的 SQL,挡不住有 sysadmin 权限的人。
真正的权限控制要落在「谁能查哪些列」上,不是「谁能看 SQL 长什么样」。
还有一条:加密之后视图逻辑没法改,只能删了重建——所以别到处加。
第一段四个数:5487.9、1299、977.9、549。member001 有两笔订单(5088.9 和 399),
所以他的合计是加起来的——这正是「视图当表用」的意思:外层聚合看到的是视图给的行。
第二段那个视图只查出 2 行(member001 的 5088.9 和 member002 的 1299)。
member001 另一笔 399 不够 1000,被筛掉了。
最后那句 RAISE(ABORT, ...) 是关键。 它不是「插了再删」,
而是插之前就中止整个语句——所以基表里不会留下那条脏数据。
换成 SQL Server,这一整段就是 WITH CHECK OPTION 一个子句的事。演示这么绕,
只是为了让本沙箱也能看见「被拦住」这件事。
30 秒实验
- 把「大额消费」视图的阈值从 1000 改成 500,重跑。
member001那笔 399 还是进不来,599 才进得来——阈值这种东西,改一次就记住了。 - 把
RAISE那句的<> 1改成= 1,再插一次。 这次能插进去了——条件反了,防呆开关就变成了「只准插下架品」。 - 想一想:如果一个视图只给
SELECT权限、不给基表权限,对方还能不能绕过视图看到手机号? 不能——权限是落在对象上的,他连表名都查不到。
常见误区
- 「视图能当表用,所以可以随便写」。多表视图、带聚合的视图都写不了。能读不等于能写。
- 「
WITH CHECK OPTION可有可无」。没有它, 插进去的数据可能从视图里看不见——人以为没插,再插一遍,脏数据就堆起来了。 - 「
WITH ENCRYPTION是安全措施」。它只挡顺手一看,sysadmin照样能看到。 而且加密后改不了定义,代价不小。
自测三问(点开对答案)
1. 多表视图为什么一般不能写? —— **因为「多」的那边一行在视图里被复制了好几份**,改它不知道改哪一行。 **一对多 `JOIN` 里只有「一」那边是保留表。** 2. `WITH CHECK OPTION` 解决什么问题? —— **防止插进去的数据不满足视图的 `WHERE` 条件**——那种数据插成功了, **但查视图看不见,人以为没插,于是重复插,堆出一堆脏数据。** 3. 加密视图算安全措施吗? —— **不算。** 它挡的是同事顺手看你的 SQL,**有 `sysadmin` 权限的人照样能看到。** **真正的权限控制是「谁能查哪些列」,不是「谁能看 SQL」。**第 10 章到此结束。下一篇进第 11 章 11.1,讲存储过程:把一批 SQL 存进数据库里,起个名字,
以后一句 EXEC 就能跑——这是「把逻辑放进数据库」的第一步。
- ☐ (本篇完,下一篇 11.1 存储过程概述)