视图就是一句存起来的 SELECT,起个名字,之后当表用。
它不存数据、只存那句 SQL,每次查都重跑一遍。
Demo 把三行联结存成 vendor_products,之后一行就能查;
order_totals 把每张单的金额固化下来,五张单从 1000.00 到 25.98;
删掉视图之后,customers 那五行一个没少。
第 15 到 18 章的联结越写越长,这一章给它们起名字。 视图是把「复杂查询」变成「一个名字」的工具。 它也是第 16 章外联结那套知识的直接产物——你不会联结,也就写不出有用的视图。
一、视图:存起来的查询
CREATE VIEW 名字 AS SELECT ...:把一句查询存进库里,之后 SELECT * FROM 名字 就能用。
视图里不存数据。 它存的只是那句 SQL,
每次查视图,引擎都把 SQL 重跑一遍。 这意味着视图永远反映最新数据,
也意味着它不会让查询变快。
视图和表在清单里长得一样(sqlite_master 里 type 分别是 view 和 table),
所以用它的人甚至不知道自己在查视图。这正是它有价值的地方。
二、视图能干的四件事
一是把复杂的联结藏起来。 三行 SQL 存成 vendor_products,以后谁都只写一行,
也不用担心他写错联结条件。
二是把计算字段固化。 每张单的金额是 SUM(quantity * item_price),
存进 order_totals 之后,一句查询就有一列现成的金额。
三是把筛选条件固定下来。 customer_emails 只含有邮箱的客户,
用它的人不必记得写 IS NOT NULL——忘了写就漏数据,这种错很难被发现。
四是复用与口径统一。 同一个「有效订单」的定义写进视图,十个人查十次都是同一个口径;
写在十份报表里,早晚会有一个不一样。
三、视图的规则与限制
视图可以嵌套(视图里查视图),但别套太深:每套一层就多跑一次子查询,排查问题也难。 教材建议尽量少嵌套。 视图不能带索引(MySQL 的物化视图是另一回事),它的性能完全取决于底层那句 SQL 写得好不好。 ORDER BY 在视图里通常没意义:SQL 标准不保证视图的输出顺序, 要顺序就在查视图时自己写 ORDER BY。 本课这套引擎允许视图里写 ORDER BY,但别依赖它。 视图的另一个用处是「藏列」:只把该给人看的列放进视图, 敏感列(成本价、身份证号)根本不出现在视图里——这比在应用层过滤可靠。
四、视图能不能改数据
两个引擎态度不同。 SQLite 对视图一律不让改,报 cannot modify ... because it is a view;
MySQL 松一些:一列对一列、没有聚集的「简单视图」可以直接 UPDATE。
带聚集、GROUP BY、DISTINCT、UNION 的视图,两边都改不了。
原因很直白:一张单的金额是四行算出来的,你改「那一行」到底改哪一行?
SQLite 想改视图得挂 INSTEAD OF 触发器(下一章讲),由你自己写「改视图等于改哪张表」。
教材的态度很明确:视图主要用于检索,不用于更新。 能改也别改——你改的是视图,别人看的是表,
出问题很难追。
五、小结
教材小结:视图是存起来的查询,可简化联结、固化计算、限制可见列,主要用于检索,不用于更新。 本课补三条:一是视图不存数据也不提速;二是别嵌套太深; 三是 SQLite 一律不许改视图,MySQL 只许改简单视图。
六、动手 Demo:把三行 SQL 变成一个名字
Demo 六段:先建一个联结视图,再看它在清单里的样子,然后固化计算字段、固定筛选条件、给视图加条件,
最后试改数据、删视图。
注意每次 CREATE VIEW 都是「完成」而不是「影响 N 行」——它改的是结构,不是数据。
import sqlite3
con = sqlite3.connect(":memory:")
# 视图 = 存起来的一句查询
con.execute("CREATE VIEW vendor_products AS SELECT vend_name, prod_name, "
"prod_price FROM vendors, products "
"WHERE vendors.vend_id = products.vend_id")
con.execute("SELECT * FROM vendor_products WHERE prod_price <= 5")
# 表和视图都在同一张清单里,但视图里没有数据
con.execute("SELECT name, type FROM sqlite_master WHERE type IN ('table','view')")
# 把计算字段固化进视图
con.execute("CREATE VIEW order_totals AS SELECT order_num, "
"SUM(quantity * item_price) AS total FROM orderitems "
"GROUP BY order_num")
con.execute("SELECT * FROM order_totals ORDER BY total DESC")
# 把筛选条件固定下来
con.execute("CREATE VIEW customer_emails AS SELECT cust_id, cust_name, "
"cust_email FROM customers WHERE cust_email IS NOT NULL")
# 查视图能再加条件
con.execute("SELECT prod_name, prod_price FROM vendor_products "
"WHERE vend_name = 'ACME' AND prod_price >= 10")
# 视图改数据:SQLite 一律拒绝,MySQL 允许简单视图
con.execute("CREATE VIEW cheap_stuff AS SELECT prod_id, prod_name, "
"prod_price FROM products WHERE prod_price <= 5")
con.execute("UPDATE cheap_stuff SET prod_price = 4.99 WHERE prod_id = 'FC'")
# 删视图不删数据
con.execute("DROP VIEW customer_emails")
实跑输出:
--------------------------------------------------------------------
第二十二课:使用视图
--------------------------------------------------------------------
【一】视图就是一个存起来的查询
SQL> CREATE VIEW vendor_products AS SELECT vend_name, prod_name, prod_price FROM vendors,
products WHERE vendors.vend_id = products.vend_id
完成
SQL> SELECT * FROM vendor_products WHERE prod_price <= 5 ORDER BY prod_price
vend_name prod_name prod_price
---------------------------------------------
ACME Carrots 2.50
ACME TNT (1 stick) 2.50
LT Supplies Fuses 3.42
ACME Sling 4.49
(4 行)
**`CREATE VIEW 名字 AS SELECT ...`** 把这条查询存起来,**之后就能像表一样查它。**
**视图里不存数据**,只存那句 SQL。**每次查视图,引擎都把那条 SQL 重跑一遍。**
【二】它和表长得一样,但只是长得一样
SQL> SELECT name, type FROM sqlite_master WHERE type IN ('table', 'view') ORDER BY type, name
name type
--------------------------
customers table
orderitems table
orders table
productnotes table
products table
vendors table
vendor_products view
(7 行)
**六张表加一个视图,都在这张清单里。** 但表里有数据,视图里只有定义。
【三】视图能干的三件事
**第一,把复杂的联结藏起来。** 上面那句三行 SQL,以后只写 `SELECT * FROM vendor_products` 就行。
SQL> CREATE VIEW order_totals AS SELECT order_num,
SUM(quantity * item_price) AS total FROM orderitems GROUP BY order_num
完成
SQL> SELECT * FROM order_totals ORDER BY total DESC
order_num total
------------------------
20007 1000.00
20005 149.87
20008 125.00
20006 55.00
20009 25.98
(5 行)
**第二,把计算字段固化。** 每张单的金额,一句 `SELECT * FROM order_totals` 就有。
SQL> CREATE VIEW customer_emails AS SELECT cust_id, cust_name,
cust_email FROM customers WHERE cust_email IS NOT NULL
完成
SQL> SELECT * FROM customer_emails ORDER BY cust_id
cust_id cust_name cust_email
---------------------------------------------------
10001 Coyote Inc. [email protected]
10003 Wascals [email protected]
10004 Yosemite Place [email protected]
(3 行)
**第三,把筛选条件固定下来。** 这个视图只有三个有邮箱的客户,
**用得着的人不必每次都记得写 `IS NOT NULL`。**
【四】视图能加条件、能再过滤
SQL> SELECT prod_name,
prod_price FROM vendor_products WHERE vend_name = 'ACME' AND prod_price >= 10
prod_name prod_price
------------------------------
Detonator 13.00
Bird seed 10.00
TNT (5 sticks) 10.00
(3 行)
**查视图和查表一样能写 WHERE、ORDER BY、GROUP BY。** 底层的 SQL 会跟你写的条件合在一起执行。
【五】视图能不能改数据:两个引擎态度不同
SQL> CREATE VIEW cheap_stuff AS SELECT prod_id, prod_name,
prod_price FROM products WHERE prod_price <= 5
完成
SQL> SELECT * FROM cheap_stuff ORDER BY prod_price
prod_id prod_name prod_price
-----------------------------------------
FC Carrots 2.50
TNT1 TNT (1 stick) 2.50
FU1 Fuses 3.42
SLING Sling 4.49
(4 行)
**改不动,报错:cannot modify cheap_stuff because it is a view**
**SQLite 对视图一律不让改**,想改得挂一个 INSTEAD OF 触发器(下一章)。
**MySQL 松一些:像这种「一列对一列、没有聚集」的简单视图是能直接 UPDATE 的。**
**带聚集的视图在两边都改不了,报错:cannot modify order_totals because it is a view**
**原因很简单**:一张单的金额是四行算出来的,**改「一行」等于改哪一行?引擎答不上来。**
**教材的态度很明确:视图主要用于检索,不用于更新。** **能改也别改**——你改的是视图,
别人看到的是表,出问题很难追。
【六】删视图不影响数据
SQL> DROP VIEW customer_emails
完成
SQL> SELECT COUNT(*) AS n FROM customers
n
--
5
(1 行)
**视图删了,customers 里那五行一个没少。** **删视图删的是那句 SQL,不是数据。**
第一段建视图。 三行联结存成 vendor_products,查它只要一行,结果四行便宜货。 第二段看清单。 六张表加一个视图,type 那列分出谁是表谁是视图。 第三段固化计算。 五张单的金额:1000.00、149.87、125.00、55.00、25.98。 第四段固定筛选。 只剩三个有邮箱的客户,条件焊死在视图里。 第五段给视图加条件。 ACME 里价格不低于 10 的三行。 第六段改与删。 改视图被拒,删视图之后 customers 还是五行。
30 秒实验
- 给
vendor_products再加一个条件建第二个视图,只留价格低于 10 的,看有几行。 - 查一下
sqlite_master里order_totals的 sql 字段,看到的是什么? - (先想再跑)把
customer_emails删掉之后,customers 里那三个邮箱还在吗?
常见误区
- 以为视图能提速。它只是把 SQL 存起来,每次查还是重跑一遍。
- 以为视图里存了数据。删视图不删数据,改底层表视图立刻跟着变。
- 想更新带聚集的视图。两边引擎都不让,因为「改哪一行」无法确定。
自测三问(点开对答案)
1. 视图里存的是什么? —— 存的是那句 SELECT 的定义,不存数据;每次查视图时把这条 SQL 重新执行一遍。 2. 视图能带来什么实际好处? —— 简化复杂联结、固化计算字段、固定筛选条件、限制可见列、统一口径。 3. 为什么带 GROUP BY 的视图不能更新? —— 因为它的每一行是聚合出来的,无法对应到基础表的具体行,引擎不知道改哪里。视图把查询封装起来了, 但它只能封装一句 SELECT。 下一章封装一整套逻辑——存储过程,连 IF、循环、参数都给你。
- ☐ (本章完,下一章 第23章 使用存储过程)