INSERT
、UPDATE
和 DELETE
的规则 #在 INSERT
、UPDATE
和 DELETE
上定义的规则与前面章节中描述的视图规则有显著不同。首先,它们的 CREATE RULE
命令允许更多
它们可以没有操作。
它们可以有多个操作。
它们可以是 INSTEAD
或 ALSO
(默认值)。
伪关系 NEW
和 OLD
变得有用。
它们可以有规则限定。
其次,它们不会就地修改查询树。相反,它们会创建零个或多个新的查询树,并且可以丢弃原始查询树。
在许多情况下,可以使用 INSERT
/UPDATE
/DELETE
规则执行的任务,最好使用触发器来完成。触发器的表示法稍微复杂一些,但其语义更容易理解。当原始查询包含易失函数时,规则往往会产生令人惊讶的结果:在执行规则的过程中,易失函数可能会被执行多次,次数超出预期。
此外,某些情况根本不支持这些类型的规则,特别是包括原始查询中的 WITH
子句和 UPDATE
查询的 SET
列表中的多重赋值子SELECT
。这是因为将这些结构复制到规则查询中会导致对子查询进行多次评估,这与查询作者的明确意图相反。
请记住语法
CREATE [ OR REPLACE ] RULEname
AS ONevent
TOtable
[ WHEREcondition
] DO [ ALSO | INSTEAD ] { NOTHING |command
| (command
;command
... ) }
。在下文中,更新规则 指的是在 INSERT
、UPDATE
或 DELETE
上定义的规则。
当查询树的结果关系和命令类型与 CREATE RULE
命令中给定的对象和事件相等时,规则系统会应用更新规则。对于更新规则,规则系统会创建一个查询树列表。最初,查询树列表为空。可以有零个(NOTHING
关键字)、一个或多个操作。为了简化,我们将查看具有一个操作的规则。此规则可以具有限定,也可以不具有限定,并且可以是 INSTEAD
或 ALSO
(默认值)。
什么是规则限定?它是一种限制,指示何时应执行规则的操作,何时不应执行。此限定只能引用伪关系 NEW
和/或 OLD
,它们基本上表示作为对象给定的关系(但具有特殊含义)。
因此,我们有三种情况,为单操作规则生成以下查询树。
ALSO
或 INSTEAD
来自规则操作的查询树,并添加了原始查询树的限定
ALSO
来自规则操作的查询树,并添加了规则限定和原始查询树的限定
INSTEAD
来自规则操作的查询树,并添加了规则限定和原始查询树的限定;以及原始查询树,并添加了取反的规则限定
最后,如果规则是 ALSO
,则将未更改的原始查询树添加到列表中。由于只有限定的 INSTEAD
规则已经添加了原始查询树,因此我们最终会得到一个或两个输出查询树,用于具有一个操作的规则。
对于 ON INSERT
规则,原始查询(如果未被 INSTEAD
抑制)在规则添加的任何操作之前完成。这允许操作查看插入的行。但是对于 ON UPDATE
和 ON DELETE
规则,原始查询在规则添加的操作之后完成。这确保了操作可以查看要更新或删除的行;否则,操作可能不会执行任何操作,因为它们找不到与其限定匹配的行。
从规则操作生成的查询树会再次被抛入重写系统,并且可能会应用更多规则,从而导致生成更多或更少的查询树。因此,规则的操作必须具有与规则本身不同的命令类型或不同的结果关系,否则此递归过程将最终陷入无限循环。(将检测到规则的递归展开并报告为错误。)
在 pg_rewrite
系统目录中找到的规则操作中的查询树只是模板。由于它们可以引用 NEW
和 OLD
的范围表条目,因此在可以使用它们之前必须进行一些替换。对于对 NEW
的任何引用,都会在原始查询的目标列表中搜索相应的条目。如果找到,则该条目的表达式将替换该引用。否则,NEW
的含义与 OLD
相同(对于 UPDATE
),或者被空值替换(对于 INSERT
)。对 OLD
的任何引用都将替换为对作为结果关系的范围表条目的引用。
在系统完成应用更新规则后,它会将视图规则应用于生成的查询树。视图无法插入新的更新操作,因此无需对视图重写的输出应用更新规则。
假设我们要跟踪 shoelace_data
关系中 sl_avail
列的更改。因此,我们设置一个日志表和一个规则,该规则在对 shoelace_data
执行 UPDATE
时有条件地写入日志条目。
CREATE TABLE shoelace_log ( sl_name text, -- shoelace changed sl_avail integer, -- new available value log_who text, -- who did it log_when timestamp -- when ); CREATE RULE log_shoelace AS ON UPDATE TO shoelace_data WHERE NEW.sl_avail <> OLD.sl_avail DO INSERT INTO shoelace_log VALUES ( NEW.sl_name, NEW.sl_avail, current_user, current_timestamp );
现在有人执行
UPDATE shoelace_data SET sl_avail = 6 WHERE sl_name = 'sl7';
我们看一下日志表
SELECT * FROM shoelace_log; sl_name | sl_avail | log_who | log_when ---------+----------+---------+---------------------------------- sl7 | 6 | Al | Tue Oct 20 16:14:45 1998 MET DST (1 row)
这是我们所期望的。后台发生的事情如下。解析器创建了查询树
UPDATE shoelace_data SET sl_avail = 6 FROM shoelace_data shoelace_data WHERE shoelace_data.sl_name = 'sl7';
有一个规则 log_shoelace
,它是 ON UPDATE
,具有规则限定表达式
NEW.sl_avail <> OLD.sl_avail
和操作
INSERT INTO shoelace_log VALUES ( new.sl_name, new.sl_avail, current_user, current_timestamp ) FROM shoelace_data new, shoelace_data old;
(这看起来有点奇怪,因为您通常不能编写 INSERT ... VALUES ... FROM
。FROM
子句在这里只是为了指示查询树中存在 new
和 old
的范围表条目。这些是必需的,以便 INSERT
命令的查询树中的变量可以引用它们。)
该规则是限定的 ALSO
规则,因此规则系统必须返回两个查询树:修改后的规则操作和原始查询树。在步骤 1 中,原始查询的范围表被合并到规则的操作查询树中。这将导致
INSERT INTO shoelace_log VALUES (
new.sl_name, new.sl_avail,
current_user, current_timestamp )
FROM shoelace_data new, shoelace_data old,
shoelace_data shoelace_data;
在步骤 2 中,将规则限定添加到其中,因此结果集被限制为 sl_avail
更改的行
INSERT INTO shoelace_log VALUES (
new.sl_name, new.sl_avail,
current_user, current_timestamp )
FROM shoelace_data new, shoelace_data old,
shoelace_data shoelace_data
WHERE new.sl_avail <> old.sl_avail;
(这看起来更奇怪,因为 INSERT ... VALUES
也没有 WHERE
子句,但是规划器和执行器处理它不会有任何困难。无论如何,它们都需要支持 INSERT ... SELECT
的相同功能。)
在步骤 3 中,添加了原始查询树的限定,将结果集进一步限制为仅原始查询会触及的行
INSERT INTO shoelace_log VALUES (
new.sl_name, new.sl_avail,
current_user, current_timestamp )
FROM shoelace_data new, shoelace_data old,
shoelace_data shoelace_data
WHERE new.sl_avail <> old.sl_avail
AND shoelace_data.sl_name = 'sl7';
步骤 4 将对 NEW
的引用替换为原始查询树中的目标列表条目或结果关系中匹配的变量引用
INSERT INTO shoelace_log VALUES ( shoelace_data.sl_name, 6, current_user, current_timestamp ) FROM shoelace_data new, shoelace_data old, shoelace_data shoelace_data WHERE 6 <> old.sl_avail AND shoelace_data.sl_name = 'sl7';
步骤 5 将 OLD
引用更改为结果关系引用
INSERT INTO shoelace_log VALUES (
shoelace_data.sl_name, 6,
current_user, current_timestamp )
FROM shoelace_data new, shoelace_data old,
shoelace_data shoelace_data
WHERE 6 <> shoelace_data.sl_avail
AND shoelace_data.sl_name = 'sl7';
就是这样。由于该规则是 ALSO
,因此我们还输出原始查询树。简而言之,规则系统的输出是与以下语句相对应的两个查询树列表
INSERT INTO shoelace_log VALUES ( shoelace_data.sl_name, 6, current_user, current_timestamp ) FROM shoelace_data WHERE 6 <> shoelace_data.sl_avail AND shoelace_data.sl_name = 'sl7'; UPDATE shoelace_data SET sl_avail = 6 WHERE sl_name = 'sl7';
这些操作会按照这个顺序执行,而这正是规则的本意。
替换和添加的限定条件确保,如果原始查询是这样的,比如
UPDATE shoelace_data SET sl_color = 'green' WHERE sl_name = 'sl7';
则不会写入任何日志条目。在这种情况下,原始查询树不包含 sl_avail
的目标列表条目,因此 NEW.sl_avail
将被替换为 shoelace_data.sl_avail
。因此,规则生成的额外命令是
INSERT INTO shoelace_log VALUES ( shoelace_data.sl_name, shoelace_data.sl_avail, current_user, current_timestamp ) FROM shoelace_data WHERE shoelace_data.sl_avail <> shoelace_data.sl_avail AND shoelace_data.sl_name = 'sl7';
并且这个限定条件永远不会成立。
如果原始查询修改了多行,它也会正常工作。因此,如果有人发出以下命令
UPDATE shoelace_data SET sl_avail = 0 WHERE sl_color = 'black';
实际上更新了四行(sl1
,sl2
,sl3
和 sl4
)。但是 sl3
已经有 sl_avail = 0
。在这种情况下,原始查询树的限定条件是不同的,这导致了额外的查询树
INSERT INTO shoelace_log
SELECT shoelace_data.sl_name, 0,
current_user, current_timestamp
FROM shoelace_data
WHERE 0 <> shoelace_data.sl_avail
AND shoelace_data.sl_color = 'black';
由规则生成。这个查询树肯定会插入三个新的日志条目。这绝对是正确的。
在这里我们可以看到为什么原始查询树最后执行很重要。如果 UPDATE
先执行,所有行都将被设置为零,因此日志 INSERT
将找不到任何 0 <> shoelace_data.sl_avail
的行。
保护视图关系免受有人尝试在其上运行 INSERT
、UPDATE
或 DELETE
的简单方法是让这些查询树被丢弃。因此,我们可以创建规则
CREATE RULE shoe_ins_protect AS ON INSERT TO shoe DO INSTEAD NOTHING; CREATE RULE shoe_upd_protect AS ON UPDATE TO shoe DO INSTEAD NOTHING; CREATE RULE shoe_del_protect AS ON DELETE TO shoe DO INSTEAD NOTHING;
如果现在有人尝试对视图关系 shoe
执行任何这些操作,规则系统将应用这些规则。由于这些规则没有操作,并且是 INSTEAD
,因此生成的查询树列表将为空,并且整个查询将变成空,因为在规则系统完成后,没有什么可以优化或执行的了。
使用规则系统更复杂的方法是创建将查询树重写为对真实表执行正确操作的规则。要在 shoelace
视图上执行此操作,我们创建以下规则
CREATE RULE shoelace_ins AS ON INSERT TO shoelace DO INSTEAD INSERT INTO shoelace_data VALUES ( NEW.sl_name, NEW.sl_avail, NEW.sl_color, NEW.sl_len, NEW.sl_unit ); CREATE RULE shoelace_upd AS ON UPDATE TO shoelace DO INSTEAD UPDATE shoelace_data SET sl_name = NEW.sl_name, sl_avail = NEW.sl_avail, sl_color = NEW.sl_color, sl_len = NEW.sl_len, sl_unit = NEW.sl_unit WHERE sl_name = OLD.sl_name; CREATE RULE shoelace_del AS ON DELETE TO shoelace DO INSTEAD DELETE FROM shoelace_data WHERE sl_name = OLD.sl_name;
如果要支持视图上的 RETURNING
查询,则需要使规则包含计算视图行的 RETURNING
子句。对于单个表上的视图来说,这通常非常简单,但是对于像 shoelace
这样的连接视图来说,这有点繁琐。插入情况的示例是
CREATE RULE shoelace_ins AS ON INSERT TO shoelace DO INSTEAD INSERT INTO shoelace_data VALUES ( NEW.sl_name, NEW.sl_avail, NEW.sl_color, NEW.sl_len, NEW.sl_unit ) RETURNING shoelace_data.*, (SELECT shoelace_data.sl_len * u.un_fact FROM unit u WHERE shoelace_data.sl_unit = u.un_name);
请注意,此规则支持视图上的 INSERT
和 INSERT RETURNING
查询 — 对于 INSERT
,RETURNING
子句只是被忽略。
现在假设偶尔有一包鞋带到达商店,并附带一份大型零件清单。但是您不想每次都手动更新 shoelace
视图。相反,我们设置两个小表:一个您可以从零件清单中插入项目,另一个带有特殊技巧。它们的创建命令是
CREATE TABLE shoelace_arrive ( arr_name text, arr_quant integer ); CREATE TABLE shoelace_ok ( ok_name text, ok_quant integer ); CREATE RULE shoelace_ok_ins AS ON INSERT TO shoelace_ok DO INSTEAD UPDATE shoelace SET sl_avail = sl_avail + NEW.ok_quant WHERE sl_name = NEW.ok_name;
现在您可以使用零件清单中的数据填充表 shoelace_arrive
SELECT * FROM shoelace_arrive; arr_name | arr_quant ----------+----------- sl3 | 10 sl6 | 20 sl8 | 20 (3 rows)
快速查看当前数据
SELECT * FROM shoelace; sl_name | sl_avail | sl_color | sl_len | sl_unit | sl_len_cm ----------+----------+----------+--------+---------+----------- sl1 | 5 | black | 80 | cm | 80 sl2 | 6 | black | 100 | cm | 100 sl7 | 6 | brown | 60 | cm | 60 sl3 | 0 | black | 35 | inch | 88.9 sl4 | 8 | black | 40 | inch | 101.6 sl8 | 1 | brown | 40 | inch | 101.6 sl5 | 4 | brown | 1 | m | 100 sl6 | 0 | brown | 0.9 | m | 90 (8 rows)
现在将到达的鞋带移入
INSERT INTO shoelace_ok SELECT * FROM shoelace_arrive;
并检查结果
SELECT * FROM shoelace ORDER BY sl_name; sl_name | sl_avail | sl_color | sl_len | sl_unit | sl_len_cm ----------+----------+----------+--------+---------+----------- sl1 | 5 | black | 80 | cm | 80 sl2 | 6 | black | 100 | cm | 100 sl7 | 6 | brown | 60 | cm | 60 sl4 | 8 | black | 40 | inch | 101.6 sl3 | 10 | black | 35 | inch | 88.9 sl8 | 21 | brown | 40 | inch | 101.6 sl5 | 4 | brown | 1 | m | 100 sl6 | 20 | brown | 0.9 | m | 90 (8 rows) SELECT * FROM shoelace_log; sl_name | sl_avail | log_who| log_when ---------+----------+--------+---------------------------------- sl7 | 6 | Al | Tue Oct 20 19:14:45 1998 MET DST sl3 | 10 | Al | Tue Oct 20 19:25:16 1998 MET DST sl6 | 20 | Al | Tue Oct 20 19:25:16 1998 MET DST sl8 | 21 | Al | Tue Oct 20 19:25:16 1998 MET DST (4 rows)
从一个 INSERT ... SELECT
到这些结果,路途遥远。查询树转换的描述将是本章的最后一个。首先,是解析器的输出
INSERT INTO shoelace_ok SELECT shoelace_arrive.arr_name, shoelace_arrive.arr_quant FROM shoelace_arrive shoelace_arrive, shoelace_ok shoelace_ok;
现在应用第一个规则 shoelace_ok_ins
,将其转换为
UPDATE shoelace SET sl_avail = shoelace.sl_avail + shoelace_arrive.arr_quant FROM shoelace_arrive shoelace_arrive, shoelace_ok shoelace_ok, shoelace_ok old, shoelace_ok new, shoelace shoelace WHERE shoelace.sl_name = shoelace_arrive.arr_name;
并丢弃原始的在 shoelace_ok
上的 INSERT
。这个重写的查询再次传递给规则系统,第二个应用的规则 shoelace_upd
生成
UPDATE shoelace_data SET sl_name = shoelace.sl_name, sl_avail = shoelace.sl_avail + shoelace_arrive.arr_quant, sl_color = shoelace.sl_color, sl_len = shoelace.sl_len, sl_unit = shoelace.sl_unit FROM shoelace_arrive shoelace_arrive, shoelace_ok shoelace_ok, shoelace_ok old, shoelace_ok new, shoelace shoelace, shoelace old, shoelace new, shoelace_data shoelace_data WHERE shoelace.sl_name = shoelace_arrive.arr_name AND shoelace_data.sl_name = shoelace.sl_name;
同样,它是一个 INSTEAD
规则,并且之前的查询树被废弃。请注意,此查询仍使用视图 shoelace
。但是规则系统还没有完成这一步,因此它会继续并对其应用 _RETURN
规则,我们得到
UPDATE shoelace_data SET sl_name = s.sl_name, sl_avail = s.sl_avail + shoelace_arrive.arr_quant, sl_color = s.sl_color, sl_len = s.sl_len, sl_unit = s.sl_unit FROM shoelace_arrive shoelace_arrive, shoelace_ok shoelace_ok, shoelace_ok old, shoelace_ok new, shoelace shoelace, shoelace old, shoelace new, shoelace_data shoelace_data, shoelace old, shoelace new, shoelace_data s, unit u WHERE s.sl_name = shoelace_arrive.arr_name AND shoelace_data.sl_name = s.sl_name;
最后,应用规则 log_shoelace
,生成额外的查询树
INSERT INTO shoelace_log SELECT s.sl_name, s.sl_avail + shoelace_arrive.arr_quant, current_user, current_timestamp FROM shoelace_arrive shoelace_arrive, shoelace_ok shoelace_ok, shoelace_ok old, shoelace_ok new, shoelace shoelace, shoelace old, shoelace new, shoelace_data shoelace_data, shoelace old, shoelace new, shoelace_data s, unit u, shoelace_data old, shoelace_data new shoelace_log shoelace_log WHERE s.sl_name = shoelace_arrive.arr_name AND shoelace_data.sl_name = s.sl_name AND (s.sl_avail + shoelace_arrive.arr_quant) <> s.sl_avail;
之后,规则系统用完了规则并返回生成的查询树。
因此,我们最终得到两个最终的查询树,它们等效于SQL语句
INSERT INTO shoelace_log SELECT s.sl_name, s.sl_avail + shoelace_arrive.arr_quant, current_user, current_timestamp FROM shoelace_arrive shoelace_arrive, shoelace_data shoelace_data, shoelace_data s WHERE s.sl_name = shoelace_arrive.arr_name AND shoelace_data.sl_name = s.sl_name AND s.sl_avail + shoelace_arrive.arr_quant <> s.sl_avail; UPDATE shoelace_data SET sl_avail = shoelace_data.sl_avail + shoelace_arrive.arr_quant FROM shoelace_arrive shoelace_arrive, shoelace_data shoelace_data, shoelace_data s WHERE s.sl_name = shoelace_arrive.sl_name AND shoelace_data.sl_name = s.sl_name;
结果是,来自一个关系的数据被插入到另一个关系中,变为对第三个关系的更新,变为更新第四个关系,再加上在第五个关系中记录最终更新,被简化为两个查询。
有一个小细节有点丑陋。查看这两个查询,发现 shoelace_data
关系在范围表中出现了两次,它肯定可以减少到一次。计划器不处理它,因此 INSERT
的规则系统输出的执行计划将是
Nested Loop -> Merge Join -> Seq Scan -> Sort -> Seq Scan on s -> Seq Scan -> Sort -> Seq Scan on shoelace_arrive -> Seq Scan on shoelace_data
而省略额外的范围表条目将导致
Merge Join -> Seq Scan -> Sort -> Seq Scan on s -> Seq Scan -> Sort -> Seq Scan on shoelace_arrive
这会在日志表中产生完全相同的条目。因此,规则系统导致对表 shoelace_data
进行一次额外的扫描,这是完全没有必要的。并且在 UPDATE
中又执行了一次相同的冗余扫描。但是让这一切成为可能确实是一项艰巨的任务。
现在我们对 PostgreSQL 规则系统及其功能进行最后演示。假设您在数据库中添加了一些颜色不寻常的鞋带
INSERT INTO shoelace VALUES ('sl9', 0, 'pink', 35.0, 'inch', 0.0); INSERT INTO shoelace VALUES ('sl10', 1000, 'magenta', 40.0, 'inch', 0.0);
我们想创建一个视图来检查哪些 shoelace
条目在颜色上与任何鞋子都不匹配。此视图为
CREATE VIEW shoelace_mismatch AS SELECT * FROM shoelace WHERE NOT EXISTS (SELECT shoename FROM shoe WHERE slcolor = sl_color);
其输出为
SELECT * FROM shoelace_mismatch; sl_name | sl_avail | sl_color | sl_len | sl_unit | sl_len_cm ---------+----------+----------+--------+---------+----------- sl9 | 0 | pink | 35 | inch | 88.9 sl10 | 1000 | magenta | 40 | inch | 101.6
现在我们要设置它,以便从数据库中删除库存中没有的不匹配鞋带。为了使 PostgreSQL 更困难一点,我们不直接删除它。相反,我们再创建一个视图
CREATE VIEW shoelace_can_delete AS SELECT * FROM shoelace_mismatch WHERE sl_avail = 0;
并以这种方式执行
DELETE FROM shoelace WHERE EXISTS (SELECT * FROM shoelace_can_delete WHERE sl_name = shoelace.sl_name);
结果是
SELECT * FROM shoelace; sl_name | sl_avail | sl_color | sl_len | sl_unit | sl_len_cm ---------+----------+----------+--------+---------+----------- sl1 | 5 | black | 80 | cm | 80 sl2 | 6 | black | 100 | cm | 100 sl7 | 6 | brown | 60 | cm | 60 sl4 | 8 | black | 40 | inch | 101.6 sl3 | 10 | black | 35 | inch | 88.9 sl8 | 21 | brown | 40 | inch | 101.6 sl10 | 1000 | magenta | 40 | inch | 101.6 sl5 | 4 | brown | 1 | m | 100 sl6 | 20 | brown | 0.9 | m | 90 (9 rows)
对视图的 DELETE
,带有总共使用 4 个嵌套/连接视图的子查询限定条件,其中一个本身具有包含视图的子查询限定条件,并且使用了计算的视图列,被重写为单个查询树,该查询树从真实表中删除请求的数据。
在现实世界中,可能只有少数几种情况需要这种构造。但这让您感觉它工作得很好。
如果您在文档中发现任何不正确、与您对特定功能的体验不符或需要进一步澄清的内容,请使用此表格报告文档问题。