PostgreSQL 中的視窗是使用規則系統實現的。視窗基本上是一個空的資料表(沒有實際儲存空間),帶有一個 ON SELECT DO INSTEAD
規則。按照慣例,該規則被命名為 _RETURN
。所以像這樣的視窗:
CREATE VIEW myview AS SELECT * FROM mytab;
與以下內容幾乎相同:
CREATE TABLE myview (same column list as mytab
);
CREATE RULE "_RETURN" AS ON SELECT TO myview DO INSTEAD
SELECT * FROM mytab;
雖然您實際上無法編寫它,因為不允許資料表具有 ON SELECT
規則。
視窗也可以有其他種類的 DO INSTEAD
規則,允許在視窗上執行 INSERT
、UPDATE
或 DELETE
命令,儘管它缺少底層儲存空間。這將在下方的第 39.2.4 節中進一步討論。
SELECT
規則如何運作 #ON SELECT
的規則會作為最後一個步驟應用於所有查詢,即使給出的命令是 INSERT
、UPDATE
或 DELETE
。它們與其他命令類型上的規則具有不同的語義,因為它們會直接修改查詢樹,而不是建立一個新的查詢樹。因此,首先描述 SELECT
規則。
目前,ON SELECT
規則中只能有一個動作,並且它必須是一個無條件的 SELECT
動作,該動作是 INSTEAD
。需要此限制才能使規則足夠安全,以便向普通使用者開放,並且它將 ON SELECT
規則限制為像視窗一樣運作。
本章的範例是兩個連接視窗,它們進行一些計算,還有一些使用它們的更多視窗。其中兩個第一個視窗稍後透過新增 INSERT
、UPDATE
和 DELETE
操作的規則進行自定義,以便最終結果將是一個行為類似於具有某些魔術功能的真實資料表的視窗。這不是一個簡單的範例,因此很難入門。但是,最好有一個範例逐步涵蓋所有討論的要點,而不是有許多不同的範例可能會混淆。
我們在前兩個規則系統描述中需要的真實資料表是這些
CREATE TABLE shoe_data ( shoename text, -- primary key sh_avail integer, -- available number of pairs slcolor text, -- preferred shoelace color slminlen real, -- minimum shoelace length slmaxlen real, -- maximum shoelace length slunit text -- length unit ); CREATE TABLE shoelace_data ( sl_name text, -- primary key sl_avail integer, -- available number of pairs sl_color text, -- shoelace color sl_len real, -- shoelace length sl_unit text -- length unit ); CREATE TABLE unit ( un_name text, -- primary key un_fact real -- factor to transform to cm );
如您所見,它們代表鞋店資料。
視窗建立為
CREATE VIEW shoe AS SELECT sh.shoename, sh.sh_avail, sh.slcolor, sh.slminlen, sh.slminlen * un.un_fact AS slminlen_cm, sh.slmaxlen, sh.slmaxlen * un.un_fact AS slmaxlen_cm, sh.slunit FROM shoe_data sh, unit un WHERE sh.slunit = un.un_name; CREATE VIEW shoelace AS SELECT s.sl_name, s.sl_avail, s.sl_color, s.sl_len, s.sl_unit, s.sl_len * u.un_fact AS sl_len_cm FROM shoelace_data s, unit u WHERE s.sl_unit = u.un_name; CREATE VIEW shoe_ready AS SELECT rsh.shoename, rsh.sh_avail, rsl.sl_name, rsl.sl_avail, least(rsh.sh_avail, rsl.sl_avail) AS total_avail FROM shoe rsh, shoelace rsl WHERE rsl.sl_color = rsh.slcolor AND rsl.sl_len_cm >= rsh.slminlen_cm AND rsl.sl_len_cm <= rsh.slmaxlen_cm;
用於 shoelace
視窗(這是我們擁有的最簡單的一個)的 CREATE VIEW
命令將建立一個關聯 shoelace
和 pg_rewrite
中的一個條目,該條目指示有一個重寫規則必須應用於每當查詢的範圍表中引用關聯 shoelace
時。該規則沒有規則資格(稍後會討論,包含非 SELECT
規則,因為 SELECT
規則目前不能擁有它們),並且它是 INSTEAD
。請注意,規則資格與查詢資格不同。我們規則的動作具有查詢資格。規則的動作是一個查詢樹,它是視窗建立命令中 SELECT
語句的副本。
您可以在 pg_rewrite
條目中看到的 NEW
和 OLD
的兩個額外範圍資料表條目對於 SELECT
規則沒有意義。
現在我們填充 unit
、shoe_data
和 shoelace_data
並且在視窗上執行一個簡單的查詢
INSERT INTO unit VALUES ('cm', 1.0); INSERT INTO unit VALUES ('m', 100.0); INSERT INTO unit VALUES ('inch', 2.54); INSERT INTO shoe_data VALUES ('sh1', 2, 'black', 70.0, 90.0, 'cm'); INSERT INTO shoe_data VALUES ('sh2', 0, 'black', 30.0, 40.0, 'inch'); INSERT INTO shoe_data VALUES ('sh3', 4, 'brown', 50.0, 65.0, 'cm'); INSERT INTO shoe_data VALUES ('sh4', 3, 'brown', 40.0, 50.0, 'inch'); INSERT INTO shoelace_data VALUES ('sl1', 5, 'black', 80.0, 'cm'); INSERT INTO shoelace_data VALUES ('sl2', 6, 'black', 100.0, 'cm'); INSERT INTO shoelace_data VALUES ('sl3', 0, 'black', 35.0 , 'inch'); INSERT INTO shoelace_data VALUES ('sl4', 8, 'black', 40.0 , 'inch'); INSERT INTO shoelace_data VALUES ('sl5', 4, 'brown', 1.0 , 'm'); INSERT INTO shoelace_data VALUES ('sl6', 0, 'brown', 0.9 , 'm'); INSERT INTO shoelace_data VALUES ('sl7', 7, 'brown', 60 , 'cm'); INSERT INTO shoelace_data VALUES ('sl8', 1, 'brown', 40 , 'inch'); 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 | 7 | 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)
這是您可以在我們的視窗上執行的最簡單的 SELECT
,因此我們藉此機會解釋視窗規則的基本知識。 SELECT * FROM shoelace
由剖析器解釋並產生查詢樹
SELECT shoelace.sl_name, shoelace.sl_avail, shoelace.sl_color, shoelace.sl_len, shoelace.sl_unit, shoelace.sl_len_cm FROM shoelace shoelace;
並將其提供給規則系統。規則系統遍歷範圍資料表並檢查是否存在任何關聯的規則。在處理 shoelace
的範圍資料表條目(目前只有一個)時,它會找到具有查詢樹的 _RETURN
規則
SELECT s.sl_name, s.sl_avail, s.sl_color, s.sl_len, s.sl_unit, s.sl_len * u.un_fact AS sl_len_cm FROM shoelace old, shoelace new, shoelace_data s, unit u WHERE s.sl_unit = u.un_name;
為了展開視窗,重寫器只需建立一個子查詢範圍資料表條目,其中包含規則的動作查詢樹,並將此範圍資料表條目替換為引用視窗的原始條目。產生的重寫查詢樹幾乎與您輸入以下內容時相同:
SELECT shoelace.sl_name, shoelace.sl_avail, shoelace.sl_color, shoelace.sl_len, shoelace.sl_unit, shoelace.sl_len_cm FROM (SELECT s.sl_name, s.sl_avail, s.sl_color, s.sl_len, s.sl_unit, s.sl_len * u.un_fact AS sl_len_cm FROM shoelace_data s, unit u WHERE s.sl_unit = u.un_name) shoelace;
然而,有一個不同之處:子查詢的範圍表有兩個額外的條目 shoelace old
和 shoelace new
。 這些條目不會直接參與查詢,因為它們沒有被子查詢的連接樹或目標列表引用。 重寫器使用它們來儲存最初存在於引用視圖的範圍表條目中的存取權限檢查資訊。 這樣,即使在重寫後的查詢中沒有直接使用視圖,執行器仍然會檢查使用者是否具有存取視圖的適當權限。
這是套用的第一個規則。 規則系統將繼續檢查頂層查詢中的剩餘範圍表條目(在本例中沒有更多條目),並且它將遞迴地檢查新增的子查詢中的範圍表條目,以查看它們中的任何一個是否引用視圖。(但它不會展開 old
或 new
— 否則我們會有無限遞迴!)在本例中,shoelace_data
或 unit
沒有重寫規則,因此重寫完成,以上是提供給規劃器的最終結果。
現在我們想要編寫一個查詢,找出目前商店中哪些鞋子有搭配的鞋帶(顏色和長度),並且完全匹配的鞋帶的總數大於或等於 2。
SELECT * FROM shoe_ready WHERE total_avail >= 2; shoename | sh_avail | sl_name | sl_avail | total_avail ----------+----------+---------+----------+------------- sh1 | 2 | sl1 | 5 | 2 sh3 | 4 | sl7 | 7 | 4 (2 rows)
這次剖析器的輸出是查詢樹
SELECT shoe_ready.shoename, shoe_ready.sh_avail, shoe_ready.sl_name, shoe_ready.sl_avail, shoe_ready.total_avail FROM shoe_ready shoe_ready WHERE shoe_ready.total_avail >= 2;
套用的第一個規則將是 shoe_ready
視圖的規則,它會產生查詢樹
SELECT shoe_ready.shoename, shoe_ready.sh_avail, shoe_ready.sl_name, shoe_ready.sl_avail, shoe_ready.total_avail FROM (SELECT rsh.shoename, rsh.sh_avail, rsl.sl_name, rsl.sl_avail, least(rsh.sh_avail, rsl.sl_avail) AS total_avail FROM shoe rsh, shoelace rsl WHERE rsl.sl_color = rsh.slcolor AND rsl.sl_len_cm >= rsh.slminlen_cm AND rsl.sl_len_cm <= rsh.slmaxlen_cm) shoe_ready WHERE shoe_ready.total_avail >= 2;
類似地,shoe
和 shoelace
的規則會被替換到子查詢的範圍表中,從而產生一個三層的最終查詢樹
SELECT shoe_ready.shoename, shoe_ready.sh_avail, shoe_ready.sl_name, shoe_ready.sl_avail, shoe_ready.total_avail FROM (SELECT rsh.shoename, rsh.sh_avail, rsl.sl_name, rsl.sl_avail, least(rsh.sh_avail, rsl.sl_avail) AS total_avail FROM (SELECT sh.shoename, sh.sh_avail, sh.slcolor, sh.slminlen, sh.slminlen * un.un_fact AS slminlen_cm, sh.slmaxlen, sh.slmaxlen * un.un_fact AS slmaxlen_cm, sh.slunit FROM shoe_data sh, unit un WHERE sh.slunit = un.un_name) rsh, (SELECT s.sl_name, s.sl_avail, s.sl_color, s.sl_len, s.sl_unit, s.sl_len * u.un_fact AS sl_len_cm FROM shoelace_data s, unit u WHERE s.sl_unit = u.un_name) rsl WHERE rsl.sl_color = rsh.slcolor AND rsl.sl_len_cm >= rsh.slminlen_cm AND rsl.sl_len_cm <= rsh.slmaxlen_cm) shoe_ready WHERE shoe_ready.total_avail > 2;
這可能看起來效率不高,但規劃器會通過「拉起(pulling up)」子查詢將其摺疊成單層查詢樹,然後它將規劃連接,就像我們手動寫出它們一樣。 因此,摺疊查詢樹是一種優化,重寫系統不必關心。
SELECT
語句中的視圖規則 #在上述視圖規則的描述中,查詢樹的兩個細節沒有被觸及。 這些是命令類型和結果關係。 實際上,視圖規則不需要命令類型,但結果關係可能會影響查詢重寫器的工作方式,因為如果結果關係是視圖,則需要特別注意。
SELECT
的查詢樹和任何其他命令的查詢樹之間只有一些差異。 顯然,它們具有不同的命令類型,並且對於 SELECT
以外的命令,結果關係指向結果應該去往的範圍表條目。 其他一切都是完全相同的。 因此,如果我們有兩個表 t1
和 t2
,它們具有欄位 a
和 b
,則以下兩個語句的查詢樹
SELECT t2.b FROM t1, t2 WHERE t1.a = t2.a; UPDATE t1 SET b = t2.b FROM t2 WHERE t1.a = t2.a;
幾乎相同。 特別是
範圍表包含表 t1
和 t2
的條目。
目標列表包含一個變數,該變數指向表 t2
的範圍表條目的欄位 b
。
條件運算式比較兩個範圍表條目的欄位 a
是否相等。
連接樹顯示 t1
和 t2
之間的簡單連接。
結論是,兩個查詢樹都會產生類似的執行計畫:它們都是在兩個表上的連接。 對於 UPDATE
,規劃器會將 t1
中缺失的欄位新增到目標列表中,最終的查詢樹將讀取為
UPDATE t1 SET a = t1.a, b = t2.b FROM t2 WHERE t1.a = t2.a;
因此,在連接上執行的執行器將產生與以下內容完全相同的結果集
SELECT t1.a, t2.b FROM t1, t2 WHERE t1.a = t2.a;
但是 UPDATE
中有一個小問題:執行器計畫中執行連接的部分並不關心連接的結果的用途。 它只是產生一組結果列。 其中一個是 SELECT
命令,另一個是 UPDATE
命令,這一事實由執行器中的更高層級處理,它知道這是一個 UPDATE
,並且它知道這個結果應該進入表 t1
。 但是,其中哪些列必須替換為新列?
為了解決這個問題,在 UPDATE
(以及 DELETE
)語句中,會將另一個條目新增到目標列表中:目前的 tuple ID(CTID)。 這是一個系統欄位,包含該列的檔案區塊編號和區塊中的位置。 知道表之後,就可以使用CTID來檢索要更新的 t1
的原始列。 在新增CTID到目標列表之後,查詢實際上看起來像
SELECT t1.a, t2.b, t1.ctid FROM t1, t2 WHERE t1.a = t2.a;
現在,PostgreSQL 的另一個細節進入了階段。 舊的表列不會被覆寫,這就是 ROLLBACK
快速的原因。 在 UPDATE
中,新的結果列會被插入到表中(在剝離CTID之後),並且在舊列的列標頭中,CTID指向的 cmax
和 xmax
條目會被設定為目前的命令計數器和目前的交易 ID。 因此,舊列會被隱藏,並且在交易提交後,清理程式最終可以移除該無效列。
了解了所有這些,我們可以簡單地以完全相同的方式將視圖規則應用於任何命令。 沒有區別。
以上展示了規則系統如何將視圖定義納入原始查詢樹。 在第二個範例中,從一個視圖的簡單 SELECT
建立了一個最終查詢樹,該查詢樹是 4 個表(unit
以不同的名稱使用了兩次)的連接。
使用規則系統實作視圖的好處是,規劃器擁有關於哪些表必須被掃描的所有資訊,加上這些表之間的關係,加上來自視圖的限制性條件,加上來自原始查詢的條件,都在一個單一的查詢樹中。 並且當原始查詢已經是跨視圖的連接時,情況仍然如此。 規劃器必須決定執行查詢的最佳路徑,並且規劃器擁有的資訊越多,這個決定就越好。 並且在 PostgreSQL 中實作的規則系統確保這是關於查詢的所有可用資訊,直到該點。
如果一個視圖被命名為 INSERT
、UPDATE
、DELETE
或 MERGE
的目標關係會發生什麼? 執行上述替換將產生一個查詢樹,其中結果關係指向一個子查詢範圍表條目,這將不起作用。 但是,PostgreSQL 可以通過幾種方式來支援更新視圖的表象。 按照使用者體驗的複雜程度排序,它們是:自動將基礎表替換為視圖,執行使用者定義的觸發程序,或者按照使用者定義的規則重寫查詢。 這些選項將在下面討論。
如果子查詢從單個基礎關係中選擇,並且足夠簡單,重寫器可以自動將子查詢替換為底層基礎關係,以便以適當的方式將 INSERT
、UPDATE
、DELETE
或 MERGE
應用於基礎關係。 對於此而言「足夠簡單」的視圖稱為自動可更新。 有關可以自動更新的視圖種類的詳細資訊,請參閱CREATE VIEW。
或者,這個操作可以由使用者提供的 view 上的 INSTEAD OF
觸發程序處理(請參閱CREATE TRIGGER)。在這種情況下,重寫的工作方式略有不同。對於 INSERT
,重寫器完全不處理 view,而是將其保留為查詢的結果關係。對於 UPDATE
、DELETE
和 MERGE
,仍然需要展開 view 查詢以產生命令將嘗試更新、刪除或合併的 “舊” 列。因此,view 會像平常一樣展開,但另一個未展開的範圍表條目會新增到查詢中,以表示 view 作為結果關係的能力。
現在出現的問題是如何識別 view 中要更新的列。回想一下,當結果關係是一個表時,會新增一個特殊的CTID條目到目標列表中,以識別要更新的列的物理位置。如果結果關係是一個 view,這就行不通了,因為 view 沒有任何CTID,因為它的列沒有實際的物理位置。相反,對於 UPDATE
、DELETE
或 MERGE
操作,一個特殊的 wholerow
條目會新增到目標列表中,它會展開以包含 view 中的所有列。執行器使用此值將 “舊” 列提供給 INSTEAD OF
觸發程序。觸發程序需要根據舊列和新列的值來確定要更新的內容。
另一種可能性是讓使用者定義 INSTEAD
規則,以指定在 view 上執行 INSERT
、UPDATE
和 DELETE
命令的替代動作。這些規則將重寫命令,通常是重寫成更新一個或多個表,而不是 view 的命令。這是第 39.4 節的主題。請注意,這不適用於 MERGE
,它目前不支援目標關係上的規則,除了 SELECT
規則。
請注意,規則會先進行評估,在規劃和執行原始查詢之前將其重寫。因此,如果 view 有 INSTEAD OF
觸發程序以及 INSERT
、UPDATE
或 DELETE
上的規則,則會先評估規則,並且根據結果,可能根本不會使用觸發程序。
總是最後嘗試自動重寫簡單 view 上的 INSERT
、UPDATE
、DELETE
或 MERGE
查詢。因此,如果 view 具有規則或觸發程序,它們將覆蓋自動可更新 view 的預設行為。
如果 view 沒有 INSTEAD
規則或 INSTEAD OF
觸發程序,並且重寫器無法自動將查詢重寫為對底層基本關係的更新,則會拋出錯誤,因為執行器無法更新這樣的 view。
如果您在文件中發現任何不正確、與您使用特定功能的經驗不符或需要進一步澄清的地方,請使用此表單來回報文件問題。