40.4. 有关 INSERT,UPDATE 和 DELETE 的规则

INSERTUPDATEDELETE上定义的规则与上一节中描述的视图规则有很大不同。首先,他们的CREATE RULE命令允许更多功能:

  • 他们被允许不采取任何行动。

  • 他们可以采取多种行动。

  • 它们可以是INSTEADALSO(默认值)。

  • 伪关系NEWOLD变得有用。

  • 他们可以具有规则资格。

其次,他们不会修改查询树。相反,它们创建零个或多个新查询树,并且可以丢弃原始的一棵。

Caution

在许多情况下,可以通过触发器更好地完成可由INSERT/UPDATE/DELETE上的规则执行的任务。从概念上讲,触发器要复杂一些,但是它们的语义要简单得多。当原始查询包含易失性函数时,规则往往会产生令人惊讶的结果:易失性函数执行的次数可能比执行规则过程中预期的次数多。

此外,在某些情况下,这些类型的规则完全不支持,特别是原始查询中的WITH子句和UPDATE查询的SET列表中的多重分配子SELECT s。这是因为将这些构造复制到规则查询中将导致对子查询进行多次评估,这与查询作者的明确意图相反。

40 .4.1. 更新规则如何工作

保留语法:

CREATE [ OR REPLACE ] RULE name AS ON event
    TO table [ WHERE condition ]
    DO [ ALSO | INSTEAD ] { NOTHING | command | ( command ; command ... ) }

心里。在下文中,更新规则表示在INSERTUPDATEDELETE上定义的规则。

当结果关系和查询树的命令类型等于CREATE RULE命令中给定的对象和事件时,规则系统将应用更新规则。对于更新规则,规则系统创建查询树列表。最初,查询树列表为空。可以有零(NOTHING个关键字),一个或多个动作。为简化起见,我们将查看一个动作的规则。此规则可以具有限定条件,可以是INSTEADALSO(默认值)。

什么是规则资格?这是一个限制,它告诉何时应该执行规则的操作以及何时不应该执行规则的操作。该限定条件只能引用伪关系NEW和/或OLD,它们基本上表示作为对象给出的关系(但具有特殊含义)。

因此,我们有三种情况会为单动规则生成以下查询树。

  • ALSOINSTEAD没有资格

    • 规则操作中的查询树,并添加了原始查询树的资格
  • 已获得资格和ALSO

    • 带有规则限定条件和原始查询树限定条件的规则操作中的查询树
  • 已获得资格和INSTEAD

    • 来自具有规则限定条件和原始查询树限定条件的规则动作的查询树;并添加了带有否定的规则限定条件的原始查询树

最后,如果规则为ALSO,则未更改的原始查询树将添加到列表中。由于只有合格的INSTEAD规则已经添加了原始查询树,因此对于一个动作的规则,我们最终得到一到两个输出查询树。

对于ON INSERT规则,原始查询(如果未被INSTEAD禁止)将在规则添加的任何操作之前执行。这使操作可以看到插入的行。但是对于ON UPDATEON DELETE规则,原始查询是在规则添加的操作之后进行的。这样可以确保这些动作可以看到要更新或要删除的行;否则,这些动作可能什么也不做,因为它们找不到符合其条件的行。

从规则操作生成的查询树再次被扔到重写系统中,并且可能会应用更多的规则,从而导致或多或少的查询树。因此,规则的操作必须具有与规则本身不同的命令类型或结果关系,否则,此递归过程将最终陷入无限循环。 (将检测到规则的递归扩展并将其报告为错误.)

pg_rewrite系统目录的操作中找到的查询树仅是模板。由于它们可以引用NEWOLD的范围表条目,因此在使用它们之前必须进行一些替换。对于对NEW的任何引用,都会在原始查询的目标列表中搜索相应的条目。如果找到,该条目的表达式将替换引用。否则,NEW的含义与OLD相同(对于UPDATE),或者由空值替换(对于INSERT)。对OLD的任何引用都将替换对作为结果关系的范围表条目的引用。

系统完成应用更新规则后,会将视图规则应用于生成的查询树。视图无法插入新的更新操作,因此无需将更新规则应用于视图重写的输出。

40 .4.1.1. 第一步规则

假设我们要跟踪对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_shoelaceON 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子句只是为了表示newold的查询树中有范围表条目.这些是必需的,以便可以由它们中的变量引用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';

这些是按此 Sequences 执行的,而这正是规则的本意。

替代词和添加的限定词确保,如果原始查询为,请说:

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';

实际上会更新四行(sl1sl2sl3sl4)。但是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的任何行。

40 .4.2. 观点合作

保护视图关系免受提及的可能性的一种简单方法是,有人可以尝试对它们运行INSERTUPDATEDELETE,而这些查询树将被丢弃。因此,我们可以创建规则:

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);

请注意,此规则支持视图上的INSERTINSERT RETURNING查询-INSERT只会忽略RETURNING子句。

现在假设,偶尔会有一包鞋带到达 Store,并附带大量 Component 清单。但是您不想每次都手动更新shoelace视图。相反,我们构建了两个小表:一个可以在其中插入 Component 列表中的项目的表,另一个具有特殊技巧的表。这些的创建命令是:

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;

现在,您可以使用 Component 列表中的数据填充表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。但是规则系统尚未完成此步骤,因此它 continue 并在其上应用_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);

Voilà :

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 个嵌套/联接视图,其中一个本身具有包含视图的子查询限定条件,并使用计算的视图列,它们被重写为一个单个查询树,该查询树删除了从真实表中请求的数据。

在现实世界中,可能只有少数情况需要这种构造。但这使您感到自在。