全部產品
Search
文件中心

PolarDB:INSERT、UPDATE和DELETE上的規則

更新時間:Jul 06, 2024

定義在INSERTUPDATEDELETE上的規則與前一節描述的視圖規則有明顯的不同。

首先,它們的CREATE RULE命令允許更多:

  • 它們可以沒有動作。

  • 它們可以有多個動作。

  • 它們可以是INSTEADALSO(預設)。

  • 偽關係NEWOLD變得有用了。

  • 它們可以有規則條件。

第二,它們不是就地修改查詢樹,而是建立零個或多個新查詢樹並且可能把原始的那個查詢樹扔掉。

說明

在很多情況下,由INSERT/UPDATE/DELETE上的規則執行的任務用觸發器能做得更好。觸發器在記法上要更複雜些,但是它們的語義理解起來更簡單些。當原始查詢包含不穩定函數時,規則容易產生令人驚訝的結果:在執行規則的過程中不穩定函數的執行次數可能比語氣中的更多。

還有,有些情況根本無法用這些類型的規則支援,典型的是在原始查詢中包括WITH子句以及在UPDATE查詢的SET列表中包括多個賦值的子SELECT。這是因為把這些結構複製到一個規則查詢中可能導致子查詢的多次計算,這與查詢作者表達的意圖相悖。

更新規則如何工作

記住以下文法:

    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的引用,都要先在原始查詢的目標列表中搜尋對應的項。如果找到,該項的運算式將會替換該引用。否則NEWOLD的含義一樣(對於UPDATE)或者被替換成一個空值(對於INSERT)。 任何對OLD的引用都用結果關係的範圍表項的引用替換。

在系統完成應用程式更新規則後,它再應用視圖規則到產生的查詢樹上。視圖無法插入新的更新動作,所以沒有必要向視圖重寫的輸出應用程式更新規則。

第一個規則循序漸進

假設我們想要跟蹤shoelace_data關係中的sl_avail列。所以我們建立一個日誌表和一條規則,這條規則每次在shoelace_data上執行UPDATE時有條件地寫入一個日誌項。

    CREATE TABLE shoelace_log (
        sl_name    text,          -- 改變的鞋帶
        sl_avail   integer,       -- 新的可用值
        log_who    text,          -- 誰做的
        log_when   timestamp      -- 何時做的
    );

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

這是一個帶有規則條件運算式的ON UPDATE規則log_shoelace ,條件是:

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

這些會按照這個順序被執行,並且這也正是規則要做的事情。做的替換和追加的條件用於確保對於下面這樣的原始查詢不會有日誌記錄被寫入:

    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的行。

與視圖合作

要保護一個視圖關係不被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子句。

現在假設有時一包鞋帶抵達了商店,並且隨著它有一個大的清單。但是你不想每次都手工更新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);

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,這個命令帶有一個總共使用了四個嵌套/串連視圖的子查詢條件, 這四個視圖之一本身有一個包含一個視圖的子查詢條件,該條件計算使用的視圖列; 這個命令被重寫成了一個查詢樹,該查詢樹從一個真正的表裡面把需要刪除的資料刪除。在現實世界裡只有很少的情況需要上面的這樣的構造。但這些東西能運轉肯定讓你感覺不錯。