使用视图
MySQL支持视图,包括可更新视图。视图是存储的查询,在调用时生成结果集。视图充当虚拟表。
下面的讨论描述了创建和删除视图的语法,并展示了如何使用它们的一些示例。
视图语法
CREATE VIEW语句创建一个新视图。要更改视图的定义或删除视图,请使用alter view或drop view。
可以用多种SELECT语句创建视图。它可以引用基表或其他视图。它可以使用联结、联合和子查询。SELECT甚至不需要引用任何表。下面的例子定 义了一个从另一个表中选择两列的视图,以及从这两列中计算出的表达式:
mysql> CREATE TABLE t (qty INT, price INT); ERROR 1050 (42S01): Table 't' already exists mysql> drop table t; Query OK, 0 rows affected (0.01 sec) mysql> CREATE TABLE t (qty INT, price INT); Query OK, 0 rows affected (0.00 sec) mysql> INSERT INTO t VALUES(3, 50), (5, 60); Query OK, 2 rows affected (0.00 sec) Records: 2 Duplicates: 0 Warnings: 0 mysql> CREATE VIEW v AS SELECT qty, price, qty*price AS value FROM t; Query OK, 0 rows affected (0.01 sec) mysql> SELECT * FROM v; +------+-------+-------+ | qty | price | value | +------+-------+-------+ | 3 | 50 | 150 | | 5 | 60 | 300 | +------+-------+-------+ 2 rows in set (0.00 sec) mysql> SELECT * FROM v WHERE qty = 5; +------+-------+-------+ | qty | price | value | +------+-------+-------+ | 5 | 60 | 300 | +------+-------+-------+ 1 row in set (0.00 sec)
视图处理算法
CREATE VIEW或ALTER VIEW的可选算法子句是标准SQL的MySQL扩展。它影响MySQL如何处理视图。算法有三个值:MERGE、TEMPTABLE和UNDEFINED 。
.对于MERGE,引用视图和视图定义的语句的文本被合并,以便视图定义的部分替换语句的相应部分。
.对于TEMPTABLE,将视图的结果检索到临时表中,然后使用临时表执行语句
.对于UNDEFINED, MySQL选择使用哪个算法。如果可能的话,它更倾向于MERGE而不是TEMPTABLE,因为MERGE通常更有效,而且如果使用临时表 ,则视图无法更新。
.
如果没有ALGORITHM子句存在,UNDEFINED是MySQL 5.7.6之前的默认算法。从5.7.6开始,默认算法由optimizer_switch系统变量的 derived_merge标志的值决定。
显式指定TEMPTABLE的一个原因是,在创建临时表之后,在使用临时表完成对语句的处理之前,可以释放底层表上的锁。这可能导致比MERGE算法 更快地释放锁,从而使使用该视图的其他客户端不会被阻塞太久。
视图算法可以被定义为UNDEFINED,原因有三:
.在CREATE VIEW语句中没有ALGORITHM子句。
.CREATE VIEW语句有一个显式的ALGORITHM = UNDEFINED子句。
.ALGORITHM = MERGE是为一个只能用临时表处理的视图指定的。在这种情况下,MySQL生成一个警告并将算法设置为UNDEFINED。
如前所述,合并是通过将视图定义的相应部分合并到引用视图的语句中来处理的。下面的例子简要说明了合并算法的工作原理。这些示例假设有 一个视图v_merge具有如下定义:
CREATE ALGORITHM = MERGE VIEW v_merge (vc1, vc2) AS SELECT c1, c2 FROM t WHERE c3 > 100;
例1:假设我们发出这样的语句:
SELECT * FROM v_merge;
MySQL对语句的处理如下:
.v_merge 变成 t
.*变成了vc1 vc2,对应于c1 c2
.添加了视图WHERE子句
要执行的结果语句变成:
SELECT c1, c2 FROM t WHERE c3 > 100;
例2:假设我们发出这样的语句:
SELECT * FROM v_merge WHERE vc1 < 100;
这条语句的处理与前一条类似,只是vc1 < 100变成了c1 < 100,并且使用and连接词将view WHERE子句添加到语句WHERE子句中(添加括号以确 保子句的各部分以正确的优先级执行)。执行的结果语句变成:
SELECT c1, c2 FROM t WHERE (c3 > 100) AND (c1 < 100);
实际上,要执行的语句有一个如下形式的WHERE子句:
WHERE (select WHERE) AND (view WHERE)
如果不能使用MERGE算法,则必须使用临时表。防止合并的构造与防止派生表中合并的构造相同。例如子查询中的SELECT DISTINCT或LIMIT。
可更新和可插入视图
有些视图是可更新的,对它们的引用可以用来指定要在data change语句中更新的表。也就是说,您可以在UPDATE、DELETE或INSERT等语句中使 用它们来更新底层表的内容。派生表也可以在多表UPDATE和DELETE语句中指定,但只能用于读取数据以指定要更新或删除的行。通常,视图引用 必须是可更新的,这意味着它们可以被合并而不是被物化。复合视图有更复杂的规则。
对于可更新的视图,视图中的行与底层表中的行之间必须存在一对一的关系。还有一些其他的构造使得视图不可更新。更具体地说,如果视图包 含以下任何内容,则视图不可更新:
.聚合函数(SUM(), MIN(), MAX(), COUNT(),等等)
.DISTINCT
.HAVING
.UNION 或UNION ALL
.选择列表中的子查询。在MySQL 5.7.11之前,select列表中的子查询对于INSERT会失败,但是对于UPDATE, DELETE是可以的。从MySQL 5.7.11 开始,对于非依赖子查询仍然如此。对于选择列表中的依赖子查询,不允许使用数据更改语句
.某些连接(请参阅本节后面的其他连接讨论)
.在FROM子句中引用不可更新视图
.仅引用文字值(在本例中,没有要更新的基础表)
.ALGORITHM = TEMPTABLE(使用临时表总是使视图不可更新)
.对基表任意列的多个引用(INSERT失败,UPDATE、DELETE正常)
视图中生成的列被认为是可更新的,因为可以对其进行分配。但是,如果显式更新这样的列,则唯一允许的值是DEFAULT。
假设可以用MERGE算法处理多表视图,那么有时多表视图可能是可更新的。为此,视图必须使用内部连接(而不是外部连接或UNION)。而且,视 图定义中只能更新一个表,因此SET子句必须只命名视图中一个表中的列。使用UNION ALL的视图是不允许的,即使它们理论上可能是可更新的。
关于可插入性(可通过INSERT语句进行更新),如果可更新视图还满足以下对视图列的附加要求,则可插入视图:
.不能有重复的视图列名。
.视图必须包含基表中没有默认值的所有列。
.视图列必须是简单的列引用。它们不能是表达式,例如:
3.14159
col1 + 3
UPPER(col2)
col3 / col4
(subquery)
MySQL在创建视图时设置了一个标志,称为视图更新标志。如果更新和删除(以及类似的操作)对视图是合法的,则该标志设置为YES (true) 。否则,该标志设置为NO (false)。INFORMATION_SCHEMA.VIEWS表中的IS_UPDATABLE列显示该标志的状态。
如果一个视图依赖于一个或多个其他视图,并且其中一个底层视图被更新,则IS_UPDATABLE标志可能不可靠。不管IS_UPDATABLE值是多少,服务 器都会跟踪视图的可更新性,并正确拒绝对不可更新视图的数据更改操作。如果视图的IS_UPDATABLE值由于底层视图的更改而变得不准确,可以 通过删除和重新创建视图来更新该值。
视图的可更新性可能会受到updatable_views_with_limit系统变量值的影响。
对于下面的讨论,假设存在这些表和视图:
mysql> CREATE TABLE t1 (x INTEGER); Query OK, 0 rows affected (0.00 sec) mysql> CREATE TABLE t2 (c INTEGER); Query OK, 0 rows affected (0.00 sec) mysql> CREATE VIEW vmat AS SELECT SUM(x) AS s FROM t1; Query OK, 0 rows affected (0.00 sec) mysql> CREATE VIEW vup AS SELECT * FROM t2; Query OK, 0 rows affected (0.00 sec) mysql> CREATE VIEW vjoin AS SELECT * FROM vmat JOIN vup ON vmat.s=vup.c; Query OK, 0 rows affected (0.01 sec)
INSERT, UPDATE, DELETE语句允许如下:
.INSERT: INSERT语句的插入表可能是合并的视图引用。如果视图是连接视图,则视图的所有组件都必须是可更新的(而不是具体化的)。对于 多表可更新视图,如果插入到单个表中,则INSERT可以工作。
这个语句是无效的,因为连接视图的一个组件是不可更新的:
mysql> INSERT INTO vjoin (c) VALUES (1); ERROR 1471 (HY000): The target table vjoin of the INSERT is not insertable-into
这个语句是有效的;视图不包含物化组件:
mysql> INSERT INTO vup (c) VALUES (1); Query OK, 1 row affected (0.00 sec)
.UPDATE:在UPDATE语句中要更新的表可能是合并的视图引用。如果视图是连接视图,则视图的至少一个组件必须是可更新的(这与插入不同) 。
在多表UPDATE语句中,语句的更新表引用必须是基表或可更新视图引用。未更新的表引用可能是物化视图或派生表。
这个语句是有效的;列c来自连接视图的可更新部分:
mysql> UPDATE vjoin SET c=c+1; Query OK, 0 rows affected (0.00 sec) Rows matched: 0 Changed: 0 Warnings: 0
这个语句是无效的;列x来自不可更新部分:
mysql> UPDATE vjoin SET x=x+1; ERROR 1054 (42S22): Unknown column 'x' in 'field list'
这个说法是有效的;多表UPDATE的更新表引用是一个可更新视图(vup):
UPDATE vup JOIN (SELECT SUM(x) AS s FROM t1) AS dt ON ... SET c=c+1;
这个语句是无效的;它尝试更新一个物化的派生表:
UPDATE vup JOIN (SELECT SUM(x) AS s FROM t1) AS dt ON ... SET s=s+1;
.DELETE:在DELETE语句中要删除的表必须是合并视图。不允许使用连接视图(这与INSERT和UPDATE不同)。
这个语句无效,因为视图是连接视图:
DELETE vjoin WHERE ...;
这个语句是有效的,因为视图是一个合并(可更新)视图:
DELETE vup WHERE ...;
这个语句是有效的,因为它从合并(可更新)视图中删除:
DELETE vup FROM vup JOIN (SELECT SUM(x) AS s FROM t1) AS dt ON ...;
接下来是更多的讨论和示例。
本节前面的讨论指出,如果不是所有列都是简单的列引用(例如,如果它包含的列是表达式或复合表达式),则视图是不可插入的。虽然这样的 视图是不可插入的,但如果只更新非表达式的列,则可以更新它。考虑下面的视图:
CREATE VIEW v AS SELECT col1, 1 AS col2 FROM t;
这个视图不能插入,因为col2是一个表达式。但是,如果更新不尝试更新col2,则它是可更新的。此更新是允许的:
UPDATE v SET col1 = 0;
这个更新是不允许的,因为它试图更新一个表达式列:
UPDATE v SET col2 = 0;
如果一个表包含AUTO_INCREMENT列,在不包含AUTO_INCREMENT列的表上插入一个insertable视图不会改变LAST_INSERT_ID()的值,因为将默认 值插入不属于视图的列的副作用不应该是可见的。
带有CHECK选项子句的视图
可以为可更新视图提供WITH CHECK选项子句,以防止插入到select_statement中的WHERE子句不为true的行。它还阻止对WHERE子句为true但更新 会导致它不为true的行进行更新(换句话说,它阻止了可见的行)
从更新到不可见行)。
在用于可更新视图的WITH CHECK选项子句中,当根据另一个视图定义视图时,LOCAL 和CASCADED确定检查测试的范围。当两个关键字都没有给出 时,默认值会CASCADED。
在MySQL 5.7.6之前,WITH CHECK OPTION测试是这样的:
.
使用LOCAL时,检查视图WHERE子句,但不检查底层视图。
.使用CASCADED,检查视图WHERE子句,然后检查递归到底层视图,为它们添加WITH CASCADED CHECK OPTION(为了检查的目的;它们的定义保持 不变),并应用相同的规则。
.如果没有检查选项,则不会检查视图WHERE子句,也不会检查底层视图。
从MySQL 5.7.6开始,WITH CHECK OPTION测试是标准兼容的(与以前的LOCAL和no check子句的语义不同):
.使用LOCAL,检查视图WHERE子句,然后检查递归到底层视图并应用相同的规则。
.使用CASCADED,检查视图WHERE子句,然后检查递归到底层视图,为它们添加WITH CASCADED CHECK OPTION(为了检查的目的;它们的定义保持 不变),并应用相同的规则。
.如果没有检查选项,则不检查视图WHERE子句,然后检查递归到底层视图,并应用相同的规则。
考虑以下表和一组视图的定义:
mysql> CREATE TABLE t1 (a INT);
Query OK, 0 rows affected (0.01 sec)
mysql> CREATE VIEW v1 AS SELECT * FROM t1 WHERE a < 2
-> WITH CHECK OPTION;
Query OK, 0 rows affected (0.01 sec)
mysql> CREATE VIEW v2 AS SELECT * FROM v1 WHERE a > 0
-> WITH LOCAL CHECK OPTION;
Query OK, 0 rows affected (0.00 sec)
mysql> CREATE VIEW v3 AS SELECT * FROM v1 WHERE a > 0
-> WITH CASCADED CHECK OPTION;
Query OK, 0 rows affected (0.00 sec)
这里v2和v3视图是根据另一个视图v1定义的。在MySQL 5.7.6之前,因为v2有一个LOCAL检查选项,插入只针对v2检查进行测试。v3有一个 cascade检查选项,因此插入不仅要根据v3检查进行测试,还要根据底层视图的检查进行测试。下面的语句说明了这些区别:
mysql> INSERT INTO v2 VALUES (2); Query OK, 1 row affected (0.00 sec) mysql> INSERT INTO v3 VALUES (2); ERROR 1369 (HY000): CHECK OPTION failed 'test.v3'
从MySQL 5.7.6开始,LOCAL的语义与以前不同:v2的插入是根据它来检查的LOCAL检查选项,然后(与5.7.6之前不同),检查递归到v1并再次应 用规则。v1的规则导致检查失败。检查v3和之前一样失败:
mysql> INSERT INTO v2 VALUES (2); ERROR 1369 (HY000): CHECK OPTION failed 'test.v2' mysql> INSERT INTO v3 VALUES (2); ERROR 1369 (HY000): CHECK OPTION failed 'test.v3'
视图元数据
视图元数据获取方式如下:
.查询INFORMATION_SCHEMA数据库的VIEWS表。
.使用SHOW CREATE VIEW语句。