原創文章,轉載請務必將下面這段話置於文章開頭處。
本文轉發自Jason’s Blog,原文連接 http://www.jasongj.com/sql/cte/ sql
WITH語句一般被稱爲通用表表達式(Common Table Expressions)或者CTEs。 數組
WITH語句做爲一個輔助語句依附於主語句,WITH語句和主語句均可以是SELECT,INSERT,UPDATE,DELETE中的任何一種語句。 oop
WITH語句最基本的功能是把複雜查詢語句拆分紅多個簡單的部分,以下例所示 優化
WITH regional_sales AS ( SELECT region, SUM(amount) AS total_sales FROM orders GROUP BY region ), top_regions AS ( SELECT region FROM regional_sales WHERE total_sales > (SELECT SUM(total_sales)/10 FROM regional_sales ) SELECT region, product, SUM(quantity) AS product_units, SUM(amount) AS product_sales FROM orders WHERE region IN (SELECT region FROM top_regions) GROUP BY region, product;
該例中,定義了兩個WITH輔助語句,regional_sales和top_regions。前者算出每一個區域的總銷售量,後者了查出全部銷售量佔全部地區總銷售裏10%以上的區域。主語句經過將這個CTEs及訂單表關聯,算出了頂級區域每件商品的銷售量和銷售額。 spa
固然,本例也能夠不使用CTEs而使用兩層嵌套子查詢來實現,但使用CTEs更簡單,更清晰,可讀性更強。 code
文章開頭處提到,WITH中能夠不只可使用SELECT語句,同時還能使用DELETE,UPDATE,INSERT語句。所以,可使用WITH,在一條SQL語句中進行不一樣的操做,以下例所示。 遞歸
WITH moved_rows AS ( DELETE FROM products WHERE "date" >= '2010-10-01' AND "date" < '2010-11-01' RETURNING * ) INSERT INTO products_log SELECT * FROM moved_rows;
本例經過WITH中的DELETE語句從products表中刪除了一個月的數據,並經過RETURNING子句將刪除的數據集賦給moved_rows這一CTE,最後在主語句中經過INSERT將刪除的商品插入products_log中。 事務
若是WITH裏面使用的不是SELECT語句,而且沒有經過RETURNING子句返回結果集,則主查詢中不能夠引用該CTE,但主查詢和WITH語句仍然能夠繼續執行。這種狀況能夠實現將多個不相關的語句放在一個SQL語句裏,實現了在不顯式使用事務的狀況下保證WITH語句和主語句的事務性,以下例所示。 get
WITH d AS ( DELETE FROM foo ), u as ( UPDATE foo SET a = 1 WHERE b = 2 ) DELETE FROM bar;
WITH語句還能夠經過增長RECURSIVE修飾符來引入它本身,從而實現遞歸 it
WITH RECURSIVE通常用於處理邏輯上層次化或樹狀結構的數據,典型的使用場景是尋找直接及間接子結點。
定義下面這樣的表,存儲每一個區域(省、市、區)的id,名字及上級區域的id
CREATE TABLE chinamap ( id INTEGER, pid INTEGER, name TEXT );
須要查出某個省,好比湖北省,管轄的全部市及市轄地區,能夠經過WITH RECURSIVE來實現,以下
WITH RECURSIVE result AS
(
SELECCT
id,
name
FROM chinamap
WHERE id = 11
UNION ALL SELECT origin.id, result.name || ' > ' || origin.name FROM result JOIN chinamap origin ON origin.pid = result.id ) SELECT id, name FROM result;
結果以下
id | name -----+-------------------------- 11 | 湖北省 110 | 湖北省 > 武漢市 120 | 湖北省 > 孝感市 130 | 湖北省 > 宜昌市 140 | 湖北省 > 隨州市 150 | 湖北省 > 仙桃市 160 | 湖北省 > 荊門市 170 | 湖北省 > 枝江市 180 | 湖北省 > 神農架市 111 | 湖北省 > 武漢市 > 武昌區 112 | 湖北省 > 武漢市 > 下城區 113 | 湖北省 > 武漢市 > 江岸區 114 | 湖北省 > 武漢市 > 江漢區 115 | 湖北省 > 武漢市 > 漢陽區 116 | 湖北省 > 武漢市 > 洪山區 117 | 湖北省 > 武漢市 > 青山區 (16 rows)
從上面的例子能夠看出,WITH RECURSIVE語句包含了兩個部分
- non-recursive term(非遞歸部分),即上例中的union all前面部分
- recursive term(遞歸部分),即上例中union all後面部分
執行步驟以下
1. 執行non-recursive term。(若是使用的是union而非union all,則需對結果去重)其結果做爲recursive term中對result的引用,同時將這部分結果放入臨時的working table中
2. 重複執行以下步驟,直到working table爲空:用working table的內容替換遞歸的自引用,執行recursive term,(若是使用union而非union all,去除重複數據),並用該結果(若是使用union而非union all,則是去重後的結果)替換working table
以上面的query爲例,來看看具體過程
1.執行
SELECT id, name FROM chinamap WHERE id = 11
結果集和working table爲
11 | 湖北
2.執行
SELECT origin.id, result.name || ' > ' || origin.name FROM result JOIN chinamap origin ON origin.pid = result.id
結果集和working table爲
110 | 湖北省 > 武漢市 120 | 湖北省 > 孝感市 130 | 湖北省 > 宜昌市 140 | 湖北省 > 隨州市 150 | 湖北省 > 仙桃市 160 | 湖北省 > 荊門市 170 | 湖北省 > 枝江市 180 | 湖北省 > 神農架市
3.再次執行recursive query,結果集和working table爲
111 | 湖北省 > 武漢市 > 武昌區 112 | 湖北省 > 武漢市 > 下城區 113 | 湖北省 > 武漢市 > 江岸區 114 | 湖北省 > 武漢市 > 江漢區 115 | 湖北省 > 武漢市 > 漢陽區 116 | 湖北省 > 武漢市 > 洪山區 117 | 湖北省 > 武漢市 > 青山區
4.繼續執行recursive query,結果集和working table爲空
5.結束遞歸,將前三個步驟的結果集合並,即獲得最終的WITH RECURSIVE的結果集
嚴格來說,這個過程實現上是一個迭代的過程而非遞歸,不過RECURSIVE這個關鍵詞是SQL標準委員會定立的,因此PostgreSQL也延用了RECURSIVE這一關鍵詞。
從上一節中能夠看到,決定是否繼續迭代的working table是否爲空,若是它永不爲空,則該CTE將陷入無限循環中。
對於自己並不會造成循環引用的數據集,無段做特別處理。而對於自己可能造成循環引用的數據集,則須經過SQL處理。
一種方式是使用UNION而非UNION ALL,從而每次recursive term的計算結果都會將已經存在的數據清除後再存入working table,使得working table最終會爲空,從而結束迭代。
然而,這種方法並不老是有效的,由於有時可能須要這些重複數據。同時UNION只能去除那些全部字段都徹底同樣的記錄,而頗有可能特定字段集相同的記錄即應該被刪除。此時能夠經過數組(單字段)或者ROW(多字段)記錄已經訪問過的記錄,從而實現去重的目的。
定義無向有環圖以下圖所示
定義以下表並存入每條邊的權重
CREATE TABLE graph ( id char, neighbor char, value integer ); INSERT INTO graph VALUES('A', 'B', 3), ('A', 'C', 5), ('A', 'D', 4), ('B', 'E', 8), ('B', 'C', 4), ('E', 'C', 7), ('E','F', 10), ('C', 'D', 3), ('C', 'F', 6), ('F','D', 5);
計算思路以下:
- 由於是無向圖,因此首先要將各條邊的id和neighbor交換一次以方便後續計算。
- 利用WITH RECURSIVE算出全部可能的路徑並計算其總權重。
- 由於該圖有環,爲避免無限循環,同時爲了計算路徑,將通過的結點存於數據中,當下一個結點已經在數據中時,說明該結點已被計算。
- 最終可算出全部可能的路徑及其總權重
實現以下
WITH RECURSIVE edges AS ( SELECT id, neighbor, value FROM graph UNION ALL SELECT neighbor, id, value FROM graph ), all_path (id, neighbor, value, path, depth, cycle) AS ( SELECT id, neighbor, value, ARRAY[id], 1, 'f'::BOOLEAN FROM edges WHERE id = 'A' UNION ALL SELECT all_path.id, edges.neighbor, edges.value + all_path.value, all_path.path || ARRAY[edges.id], depth + 1, edges.id = ANY(all_path.path) FROM edges JOIN all_path ON all_path.neighbor = edges.id AND NOT cycle ), a_f AS ( SELECT rank() over(order by value) AS rank, path || neighbor AS path, value, depth FROM all_path WHERE neighbor = 'F' ) SELECT path, value, depth FROM a_f WHERE rank = 1;