简介
视图通常用来区分逻辑数据库模式和物理模式。遗憾的是,在 UPDATE、DELETE 或 INSERT 操作中通常都达不到预期的透明度,因为除了最简单的视图之外所有的视图都不可更新。本文评估哪些视图自己是可更新的,然后引入 INSTEAD OF 触发器 - 这是 DB2® Universal Database™ V8.1 for Linux、UNIX® 和 Windows® 的新功能,它使所有视图都可以更新。
先决条件
本文中的许多示例都使用 SQL 过程化语言(SQL Procedural Language,SQL PL)。由于 SQL PL 在其主体中使用分号( ;)来分隔各个语句,所以对 CLP 或命令中心(Command Center)必须使用另一个定界符。在本篇文章中我将使用美元符号( $)来给语句定界。
要启动 CLP 会话以使用本文中的示例,请从 shell 输入: db2 -td$ 。对于命令中心,通过单击 Tools -> ToolsSettings -> Use statement termination character来设置并激活定界符。
纵览可更新视图
在我们向您完整地介绍可更新视图之前,我们需要基本了解一下“可更新”是什么意思以及 DB2(或就这一点而言的任何数据库管理系统)必须解决哪些基本问题才能通过视图更新数据库中的行。首先,缺省情况下 DB2 所拥有的全部视图信息就是视图的定义,即指定由该视图派生出的表的查询。
为了精确起见,我们需要区别三个级别的可更新能力:
可删除:为了从视图中删除一行,DB2 必须能够将视图中指定的那一行映射到基本表中有且仅有的一行。
可更新:为了更新视图中的列,DB2 必须不仅能够将视图中指定的一行映射到基本表中的一行,它还必须能够将要更新的一列映射到基本表中的一列。因此,根据定义所有可更新的视图必须是可删除的。
可插入:为了将行插入到视图中,DB2 必须能将新的行映射到一张表中并能够将指定的所有列映射到该表中的列。因此所有可插入的视图要定义成是可更新的,从而也是可删除的。
现在让我们定义一些表,然后研究这些表的各种视图的可更新能力:
CREATE TABLE T1(c1 INT, c2 FLOAT)$
INSERT INTO T1 VALUES (5, 6.0),
(6, 7.0),
(5, 6.0)$
CREATE TABLE T2(c1 INT, c2 FLOAT)$
INSERT INTO T2 VALUES (5, 9.0),
(5, 4.0),
(7, 5.0)$
对于本文中的所有示例,让我们假设提供了下列数据。示例不应该相互影响。可以通过在 CLP 中使用 +c option或从命令中心取消激活 Options -> Execution -> Automatically Commit 来关闭(OFF)自动提交,从而使它们不相互影响。在每个示例结束时使用 ROLLBACK 命令。
示例 1:
CREATE VIEW V1(c1)
AS SELECT c1 FROM T1 WHERE c2 > 0$
这是一个非常简单的视图。派生的表包含了 T1 的行子集和列子集。这个视图是可删除的,因为 DB2 可以跟踪每一行的起源并在基本表中删除各自的行。
DELETE FROM V1 WHERE c1 = 6$
但是请注意,如果有人执行了定位删除并删除 V1.c1 等于 5 的那一行,那么就看不出将删除 T1.c1 等于 5 的那两行中的哪一行。
视图也是可更新的:
UPDATE V1 SET c1 = c1 + 5 WHERE c1 = 5$
可以直接将 V1.c1 映射到 T1.c1。
INSERT 会怎么样呢?显然,应当将插入到 V1 中的任意行插入到 T1 中。为 V1.c1 指定的值应当用于 T1.c1。但是 T1.c2 呢?将使用哪些值?毕竟,视图并不知道这一列,因此用户不能为它指定值。
INSERT INTO V1 VALUES (8)$
这里需要一个定义,而 SQL 标准已经提供了该定义:将每个未提供的列值初始化成该列的隐式缺省值或显式缺省值。由于没有为 T1.c2 指定显式缺省值,所以选择值 NULL 并将(8, NULL)插入到 T1 中。
示例 2:
当有表达式时会是怎样?
CREATE VIEW V2(c1, c2)
AS SELECT c1, c2 * c2 FROM T1$
与第一个示例相比较,在可删除性方面没什么改变。DB2 仍然知道视图中的某一行是由基本表中的哪一行生成的。V2.c1 列是可更新的,因此该视图也是可更新的。
但是,V2.c2 是不可更新的。原因是从任何给定的 V2.c2 无法确定 T1.c2 的值。DB2 可以设法使用除法操作来查找匹配值;但是,这个简单的示例恰恰阐明这样的数学方法只能在用在最为简单的情形中。这个小小的尝试用来说明这样的算法的功能和限制,显示出这个算法缺乏可用性。
那么对于可插入性这到底又意味着什么呢?在 DB2 V8 之前,为使视图可插入,就要求视图的所有列都是可更新的。在 DB2 V8 中,只要有一个可更新的列就足够了。只要没有指定其它列,那么就可以忽略它们。
INSERT INTO V2(c1) VALUES (7)$
系统将把(7, NULL)插入到 T1 中。请注意,即使没有一列是可更新的,视图也是可删除的。同理,反对将不可更新的列插入到视图中的唯一理由就是在不是单一列的情况下既没有定义 VALUES 也没有定义 SELECT。
示例 3:
CREATE VIEW V3(c1, c2, c3)
AS SELECT T1.c1, T1.c2, T2.c2
FROM T1, T2 WHERE T1.c1 = T2.c1$
这个视图是从连接(join)派生出的。在本例中,其结果是:
SELECT * FROM V3 ORDER BY c1, c2, c3$
C1 C2 C3
-- --- ---
5 6.0 4.0
5 6.0 4.0
5 6.0 9.0
5 6.0 9.0
这个视图是不可删除的。该视图中的每一行都可以追溯到表 T1 和 T2 中的某一行,通过删除 T1 和 T2 中的各自行来删除第一行(5, 6.0, 4.0),那么也会间接删除第二行(5, 6.0, 4.0)和两个(5, 6.0, 9.0)行中的一行。这个行为不是很直观,对于不知道视图查询的用户尤为如此。
有的时候在视图中删除一行导致基本表中也删除一行,同时并没有对视图造成不希望出现的影响。例如,如果 T1.c1 和 T2.c1 都是唯一的,就属于上述情况。DB2 现在不考虑这种特殊情况。
由于 V3 是不可删除的,因此它也是不可更新的。再次声明,如果您想更新视图中的某一行,会发生令人奇怪的事情;如果想更新 V1.c1,以使视图中不同行拥有不同的值,那么情况会更糟糕。
同样,INSERT 的语义不清楚。根据为基本表插入操作选择的语义,添加另一行(5, 6.0, 9.0)会产生不同的结果。这是否意味着可更新视图不能引用另一张表?并非如此。如 示例 2所讨论的那样,带有表达式的视图具有很好的可更新性。因此标量子查询就非常适合。
示例 4:
CREATE VIEW V4(c1, c2)
AS SELECT c1, c2 FROM T1
UNION ALL
SELECT c1, c2 FROM T2$
V4 中的每一行明显源自特定表中的某一行。因此,基于 UNION ALL 的视图是可删除的。如果列没有基于表达式,那么该列也是可更新的。但是,该视图是不可插入的,原因很明显:它不能确定任何给定的行应当插入到哪一张基本表中。允许将某一行同时插入到两张基本表中并不合适,因为随后从视图中进行选择将两次显示该行。为允许通过 UNION ALL 进行 INSERT,需要对基本表进行约束:只能将任一给定行分派给一张表。
请参阅 http://www.ibm.com/developerworks/cn/dmdd/library/techarticles/0209rielau/0209rielau.shtml以获取有关通过 UNION ALL 视图进行 INSERT 的更多详细情况。
对通过 UNION ALL 得到的视图有一些限制,应当加以注意。UNION ALL 视图的列是不可更新的,即使 DB2 自身已插入了表达式也是如此。
CREATE VIEW V5(c1, c2)
AS SELECT c2, c1 FROM T1
UNION ALL
SELECT c1, c2 FROM T2$
DB2 必须同时将 T2.c1 和 T1.c1 的数据类型转换为 FLOAT。V5.c1 和 V5.c2 现在都基于表达式,因此它们是不可更新的。但是,V5 仍然是可删除的。
CREATE VIEW V6(c1, c2)
AS SELECT c1, c2 FROM T1
UNION ALL
SELECT c1, c2 FROM T1$
象 V6 这样的视图也可称为“菱形”,因为处理从一个源分散为两个操作(SELECT),然后再返回到一起(UNION ALL)。菱形是只读的。不能删除行。原因在于 T1 中的每一行在 V6 中被表示了两次。因此在 V6 中不可能只删除一行。另外也不可能只更新一行。
当使用 UNION(或 DISTINCT)时会出现相反的问题。现在可以将视图中的每一行映射到基本表中可能的许多行。应当只删除基本表中的一行还是删除基本表中的所有匹配行?
我们已经讨论了一些不同类型视图的可更新性背后的想法。其它视图是不可更新的;但是,让我们将目光转向视图可更新性的重要“武器”:INSTEAD OF 触发器。
INSTEAD OF 触发器
视图是由其主体定义的。对于 SELECT 查询而言这一点非常明显。对于我们在上面所讨论的 UPDATE、DELETE 和 INSERT 而言,尽管对于某些类有可能假设视图如何定义明显的语义,而在大多数情况下无法做到这一点。这就是 INSTEAD OF 触发器的突破口。INSTEAD OF 触发器阻止 DB2 去尝试解释更新操作的视图定义。与此“相反”,它将执行触发器主体,依靠定义者来得出有意义的语义。
例如:
CREATE VIEW V7(c1, c2)
AS SELECT DISTINCT c1, c2 FROM T1$
可以将 INSTEAD OF 触发器定义成删除 T1 中与 V7 中给定行相匹配的所有行或者根据某条预定规则仅删除