?
? ????? PHP ??? ???? ??? ?? ??
Rules that are defined on INSERT, UPDATE, and DELETE are significantly different from the view rules described in the previous section. First, their CREATE RULE command allows more: 定義在INSERT,UPDATE,DELETE上的規(guī)則與前一章描述的視圖規(guī)則完全不同。 首先,他們的CREATE RULE命令允許更多:
它們可以沒有動作。
它們可以有多個動作。
他們可以是INSTEAD或ALSO(缺省)。
偽關系NEW和OLD變得有用了。
它們可以有規(guī)則資格條件。
第二,它們不是就地修改查詢樹,而是創(chuàng)建零個或多個新查詢樹并且可能把原始的那個仍掉。
保持其語法如下:
CREATE [ OR REPLACE ] RULE name AS ON event TO table [ WHERE condition ] DO [ ALSO | INSTEAD ] { NOTHING | command | ( command ; command ... ) }
牢牢記住,在隨后的內(nèi)容里,update rules(更新規(guī)則)意思是定義在INSERT,UPDATE,DELETE上的規(guī)則。
如果查詢樹的結(jié)果關系和命令類型與 CREATE RULE命令里給出的對象和事件一樣的話, 規(guī)則系統(tǒng)就把更新規(guī)則應用上去。對于更新規(guī)則,規(guī)則系統(tǒng)創(chuàng)建一個查詢樹列表。一開始查詢樹是空的 ,這里可以有零個(NOTHING 關鍵字)、一個、或多個動作。為簡單起見,先看一個只有一個動作的規(guī)則。 這個規(guī)則可以有零個或一個條件并且它可以是INSTEAD 或 ALSO(缺省)。
何為規(guī)則條件?它是一個限制條件,告訴規(guī)則動作什么時候要做,什么時候不要做。 這個條件可以只引用NEW和/或OLD偽關系, 它們基本上是代表以對象形式給出的基本關系(但是有著特殊含義)。
所以,對這個單動作的規(guī)則生成查詢樹,有下面三種情況。
來自規(guī)則動作的查詢樹,附加了原始查詢樹的條件
來自規(guī)則動作的帶有規(guī)則條件的查詢樹并且附加了原始查詢樹的條件
來自規(guī)則動作帶有規(guī)則條件的查詢樹以及原始查詢樹的條件;以及附加了相反規(guī)則條件的原始查詢樹。
最后,如果規(guī)則是ALSO,那么最初未修改的查詢樹被加入到列表。 因為只有合格的INSTEAD規(guī)則已經(jīng)在初始的查詢樹里面,所以對于單動作規(guī)則最終得到一個或者兩個查詢樹。
對于ON INSERT規(guī)則,原來的查詢(如果沒有被INSERT取代)是在任何規(guī)則增加的動作之前完成的。 這樣就允許動作看到插入的行。但是對ON UPDATE和ON DELETE規(guī)則,原來的查詢是在規(guī)則增加的動作之后完成的。 這樣就確保動作可以看到將要更新或者將要刪除的行; 否則,動作可能什么也不做,因為它們發(fā)現(xiàn)沒有符合它們要求的行。
從規(guī)則動作生成的查詢樹被再次送到重寫系統(tǒng),并且可能附加更多的規(guī)則,結(jié)果是更多的或更少的查詢樹。 所以規(guī)則動作必須是另一個命令類型或者和規(guī)則所在的關系不同的另一個結(jié)果關系。 否則這樣的遞歸過程就會沒完沒了(規(guī)則的遞規(guī)展開會被檢測到,并當作一個錯誤報告)。
在pg_rewrite系統(tǒng)表中 action 里的查詢樹只是模板。 因為他們可以引用范圍表的 NEW 和 OLD,在使用它們之前必須做一些調(diào)整。對于任何對 NEW 的引用, 都要先在初始查詢的目標列中搜索對應的條目。如果找到,把該條目表達式放到引用里。 否則NEW和OLD的含義一樣(UPDATE)或者被 NULL 替代(INSERT)。 任何對 OLD 的引用都用結(jié)果關系的范圍表的引用替換。
在系統(tǒng)完成更新規(guī)則的附加之后,它再附加視圖規(guī)則到生成的查詢樹上。 視圖無法插入新的更新動作,所以沒有必要向視圖重寫的輸出附加更新規(guī)則。
假設希望跟蹤 shoelace_data 關系中的 sl_avail 字段。 所以設置一個日志表和一條規(guī)則,這條規(guī)則每次在用UPDATE更新 shoelace_data表時都要往數(shù)據(jù)庫里寫一條記錄。
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 );
現(xiàn)在有人鍵入:
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)
這是想要的,后端發(fā)生的事情如下。分析器創(chuàng)建查詢樹
UPDATE shoelace_data SET sl_avail = 6 FROM shoelace_data shoelace_data WHERE shoelace_data.sl_name = 'sl7';
這里是一個帶有條件表達式的 ON UPDATE 規(guī)則 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 子句只是表示查詢樹里有用于new 和old的范圍表記錄。 這些東西的存在是因為這樣一來它們就可以被INSERT命令的查詢樹里的變量引用。
該規(guī)則是一個有條件的ALSO規(guī)則,所以規(guī)則系統(tǒng)必須返回兩個查詢樹:更改過的規(guī)則動作和原始查詢樹。 在第一步里,原始查詢的范圍表集成到規(guī)則動作查詢樹里。生成:
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;
第二步把規(guī)則條件增加進去,所以結(jié)果集限制為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子句,不過規(guī)劃器和執(zhí)行器對此并不在意。 它們畢竟還要為INSERT ... SELECT支持這種功能。
第三步把原始查詢樹的條件加進去,把結(jié)果集進一步限制成只有被初始查詢樹改變的行:
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';
第四步把NEW引用替換為從原始查詢樹的目標列來的或從結(jié)果關系來的相匹配的變量引用:
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';
第五步,用結(jié)果關系引用把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';
這就成了。因為規(guī)則ALSO還輸出原始查詢樹。 簡而言之,從規(guī)則系統(tǒng)輸出的是一個兩個查詢樹的列表,與下面語句相同:
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';
這就是執(zhí)行的順序以及規(guī)則要做的事情。
做的替換和追加的條件用于確保如果原始的查詢是下面這樣
UPDATE shoelace_data SET sl_color = 'green' WHERE sl_name = 'sl7';
就不會有日期記錄寫到表里。 因為這回原始查詢樹不包含有關sl_avail的目標列表, NEW.sl_avail將被shoelace_data.sl_avail代替, 所以,規(guī)則生成的額外命令是:
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已經(jīng)是sl_avail = 0。 這回,原始的查詢樹條件已經(jīng)不一樣了,結(jié)果是規(guī)則生成下面的額外查詢樹
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';
這個查詢樹將肯定插入三個新的日志記錄。這也是完全正確的。
到這里就明白為什么原始查詢樹最后執(zhí)行非常重要。 如果UPDATE 將先被執(zhí)行,所有的行都已經(jīng)設為零, 所以記日志的INSERT將不能找到任何符合0 <> shoelace_data.sl_avail條件的行。
一個保護視圖關系,使其避免有人可以在其中INSERT, UPDATE, DELETE不可見數(shù)據(jù)的簡單方法是讓那些查詢樹被丟棄。 創(chuàng)建下面規(guī)則
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;
如果現(xiàn)在任何人試圖對視圖關系shoe做上面的任何操作,規(guī)則系統(tǒng)將應用這些規(guī)則。 因為這些規(guī)則沒有動作而且是INSTEAD,結(jié)果是生成的查詢樹將是空的并且整個查詢將變得空空如也, 因為經(jīng)過規(guī)則系統(tǒng)處理后沒有什么東西剩下來用于優(yōu)化或執(zhí)行了。
一個更復雜的使用規(guī)則系統(tǒng)的方法是用規(guī)則系統(tǒng)創(chuàng)建一個重寫查詢樹的規(guī)則,使查詢樹對真實的表進行正確的操作。 要在視圖shoelace上做這個工作,創(chuàng)建下面規(guī)則:
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查詢,就要讓規(guī)則包含RETURNING計算視圖行數(shù)的子句。 這對于基于單個表的視圖來說通常非?,嵥?,但是連接諸如shoelace之類的視圖很單調(diào)乏味。 一個插入情況的例子如下:
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);
注意,這個規(guī)則同時支持該視圖上的INSERT和 INSERT RETURNING查詢,INSERT RETURNING將簡單的忽略RETURNING子句。
假設現(xiàn)在有一包鞋帶到達商店,而且這是一大筆到貨。 但是不想每次都手工更新shoelace視圖。 取而代之的是創(chuàng)建了兩個小表:一個是可以從到貨清單中插入東西,另一個是一個特殊的技巧。 創(chuàng)建這些的命令如下:
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;
現(xiàn)在你可以用來自部件列表的數(shù)據(jù)填充表shoelace_arrive了:
SELECT * FROM shoelace_arrive; arr_name | arr_quant ----------+----------- sl3 | 10 sl6 | 20 sl8 | 20 (3 rows)
讓我們迅速地看一眼當前的數(shù)據(jù),
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)
把到貨鞋帶移到(shoelace_ok)中:
INSERT INTO shoelace_ok SELECT * FROM shoelace_arrive;
然后檢查結(jié)果
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語句到這個結(jié)果經(jīng)過了長長的一段過程。 而且對它的描述將是本文檔的最后。 首先是生成分析器輸出:
INSERT INTO shoelace_ok SELECT shoelace_arrive.arr_name, shoelace_arrive.arr_quant FROM shoelace_arrive shoelace_arrive, shoelace_ok shoelace_ok;
現(xiàn)在應用第一條規(guī)則shoelace_ok_ins把它轉(zhuǎn)換成
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丟棄掉。 這樣重寫后的查詢再次傳入規(guī)則系統(tǒng)并且第二次應用了規(guī)則shoelace_upd生成
sting> 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 規(guī)則并且前一個查詢樹被丟棄掉。 注意這個查詢?nèi)匀皇鞘褂靡晥Dshoelace, 但是規(guī)則系統(tǒng)還沒有完成這一步,所以它繼續(xù)在這上面應用規(guī)則_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;
最后,應用規(guī)則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;
這樣,在規(guī)則系統(tǒng)用完所有的規(guī)則后返回生成的查詢樹。
所以最終得到兩個等效于下面 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;
結(jié)果是從一個關系來的數(shù)據(jù)插入到另一個中,到了第三個中變成更新, 在到第四個中變成更新加上記日志,最后在第五個規(guī)則中縮減為兩個查詢。
有一個小細節(jié)有點讓人難受。 看看生成的兩個查詢,會發(fā)現(xiàn)shoelace_data關系在范圍表中出現(xiàn)了兩次而實際上絕對可以縮為一次。 因為規(guī)劃器不處理這些,所以對規(guī)則系統(tǒng)輸出的INSERT的執(zhí)行規(guī)劃會是
Nested Loop -> Merge Join -> Seq Scan -> Sort -> Seq Scan on s -> Seq Scan -> Sort -> Seq Scan on shoelace_arrive -> Seq Scan on shoelace_data
在省略多余的范圍表后的結(jié)果將是
Merge Join -> Seq Scan -> Sort -> Seq Scan on s -> Seq Scan -> Sort -> Seq Scan on shoelace_arrive
這也會在日志關系中生成完全一樣的記錄。 因此,規(guī)則系統(tǒng)導致對表shoelace_data的一次多余的掃描,而且同樣多余的掃描會在UPDATE里也一樣多做一次。 不過要想把這些不足去掉是一樣太困難的活了。
最后對PostgreSQL規(guī)則系統(tǒng)及其功能做一個演示。 假設你向你的數(shù)據(jù)庫中添加一些比較罕見的鞋帶:
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
現(xiàn)在想這樣設置:沒有庫存的不匹配的鞋帶都從數(shù)據(jù)庫中刪除 為了讓這事對PostgreSQL有點難度,不直接刪除它們。 取而代之的是再創(chuàng)建一個視圖:
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,這個視圖帶有一個總共使用了四個嵌套/連接的視圖的子查詢條件, 這四個視圖之一本身有一個擁有對一個視圖的子查詢條件,該條件計算使用的視圖的列; 最后重寫成了一個查詢樹,該查詢樹從一個真正的表里面把需要刪除的數(shù)據(jù)刪除。
我想在現(xiàn)實世界里只有很少的機會需要上面的這樣的構造。但這些東西能運轉(zhuǎn)肯定讓你舒服。