表述式會計算出一個表。表述式包含一個 FROM
子句,其後可選擇性地接續 WHERE
、GROUP BY
和 HAVING
子句。簡單的表述式僅僅參考磁碟上的表,也就是所謂的基底表,但更複雜的表示式可以用於以各種方式修改或組合基底表。
表述式中可選的 WHERE
、GROUP BY
和 HAVING
子句指定了對 FROM
子句中衍生的表執行的一系列連續轉換。所有這些轉換都會產生一個虛擬表,該表提供傳遞到選取清單的列,以計算查詢的輸出列。
FROM
子句 #FROM
子句從以逗號分隔的表參考清單中給定的一個或多個其他表衍生出一個表。
FROMtable_reference
[,table_reference
[, ...]]
表參考可以是表名稱(可能具有 schema 限定詞),或衍生的表,例如子查詢、JOIN
建構,或這些的複雜組合。如果在 FROM
子句中列出多個表參考,則這些表會交叉聯結(也就是形成其列的笛卡爾積;請參閱下文)。FROM
清單的結果是一個中間虛擬表,然後可以通過 WHERE
、GROUP BY
和 HAVING
子句進行轉換,最終成為整個表述式的結果。
當表參考命名一個表,該表是表繼承階層的父表時,表參考會產生不僅該表的列,還會產生其所有後代表的列,除非關鍵字 ONLY
位於表名稱之前。但是,參考僅產生出現在命名表中的列 — 在子表中新增的任何列都會被忽略。
您可以編寫 *
在表名稱之後,而不是在表名稱之前編寫 ONLY
,以明確指定包含後代表。現在沒有真正的理由再使用此語法,因為搜尋後代表現在始終是預設行為。但是,為了與舊版本相容,仍然支援它。
聯結表是根據特定聯結類型的規則從另外兩個(真實或衍生的)表衍生而來的表。可以使用內部聯結、外部聯結和交叉聯結。聯結表的一般語法為
T1
join_type
T2
[join_condition
]
所有類型的聯結都可以鏈結在一起或巢狀:T1
和 T2
中的任一或兩者都可以是聯結表。可以使用括號圍繞 JOIN
子句來控制聯結順序。在沒有括號的情況下,JOIN
子句會從左到右巢狀。
聯結類型
T1
CROSS JOINT2
對於 T1
和 T2
中列的每個可能組合(即笛卡爾積),聯結表將包含一個列,該列由 T1
中的所有列,後接 T2
中的所有列組成。如果表分別有 N 和 M 列,則聯結表將有 N * M 列。
FROM
等同於 T1
CROSS JOIN T2
FROM
(請參閱下文)。它也等同於 T1
INNER JOIN T2
ON TRUEFROM
。T1
, T2
當出現兩個以上的表時,後一種等價性並不完全成立,因為 JOIN
比逗號的約束力更強。例如,FROM
與 T1
CROSS JOIN T2
INNER JOIN T3
ON condition
FROM
不同,因為 T1
, T2
INNER JOIN T3
ON condition
condition
可以在第一種情況下參考 T1
,但不能在第二種情況下參考。
T1
{ [INNER] | { LEFT | RIGHT | FULL } [OUTER] } JOINT2
ONboolean_expression
T1
{ [INNER] | { LEFT | RIGHT | FULL } [OUTER] } JOINT2
USING (join column list
)T1
NATURAL { [INNER] | { LEFT | RIGHT | FULL } [OUTER] } JOINT2
在所有形式中,INNER
和 OUTER
這兩個詞是可選的。INNER
是預設值;LEFT
、RIGHT
和 FULL
表示外部聯結。
聯結條件是在 ON
或 USING
子句中指定,或透過 NATURAL
這個詞彙隱含指定。聯結條件決定了兩個來源表格中哪些列會被視為“符合”,詳細說明如下。
合格聯結的可能類型有:
INNER JOIN(內部聯結)
對於 T1 的每一列 R1,聯結後的表格會包含 T2 中每一列與 R1 滿足聯結條件的列。
LEFT OUTER JOIN
首先,執行內部聯結。然後,對於 T1 中任何不滿足與 T2 中任何列聯結條件的列,會新增一列聯結後的列,並在 T2 的欄位中填入 null 值。因此,聯結後的表格對於 T1 的每一列,至少會有一列。
RIGHT OUTER JOIN
首先,執行內部聯結。然後,對於 T2 中任何不滿足與 T1 中任何列聯結條件的列,會新增一列聯結後的列,並在 T1 的欄位中填入 null 值。這是左聯結的反向操作:結果表格對於 T2 的每一列,都會有一列。
FULL OUTER JOIN(完整外部聯結)
首先,執行內部聯結。然後,對於 T1 中任何不滿足與 T2 中任何列聯結條件的列,會新增一列聯結後的列,並在 T2 的欄位中填入 null 值。此外,對於 T2 中任何不滿足與 T1 中任何列聯結條件的列,會新增一列聯結後的列,並在 T1 的欄位中填入 null 值。
ON
子句是最通用的聯結條件類型:它接受一個布林值運算式,與 WHERE
子句中使用的類型相同。如果 ON
運算式求值為 true,則來自 T1
和 T2
的一對列即符合。
USING
子句是一種簡寫,可讓您利用聯結的兩側對聯結欄位使用相同名稱的特定情況。 它接受以逗號分隔的共享欄位名稱清單,並形成一個聯結條件,其中包含每個欄位的相等性比較。 例如,使用 USING (a, b)
聯結 T1
和 T2
會產生聯結條件 ON
。T1
.a = T2
.a AND T1
.b = T2
.b
此外,JOIN USING
的輸出會抑制冗餘欄位:由於它們必須具有相等的值,因此無需列印兩個符合的欄位。 雖然 JOIN ON
產生來自 T1
的所有欄位,然後是來自 T2
的所有欄位,但 JOIN USING
為每個列出的欄位對(按列出的順序)產生一個輸出欄位,然後是來自 T1
的任何剩餘欄位,然後是來自 T2
的任何剩餘欄位。
最後,NATURAL
是 USING
的簡寫形式:它形成一個 USING
清單,其中包含兩個輸入表格中出現的所有欄位名稱。 與 USING
一樣,這些欄位在輸出表格中僅出現一次。 如果沒有共同的欄位名稱,NATURAL JOIN
的行為類似於 CROSS JOIN
。
由於僅組合列出的欄位,因此 USING
對於聯結關係中的欄位更改而言相當安全。 NATURAL
的風險要高得多,因為對任一關係的任何 schema 更改,導致出現新的匹配欄位名稱,都將導致聯結也組合該新欄位。
為了將其放在一起,假設我們有表格 t1
num | name -----+------ 1 | a 2 | b 3 | c
和 t2
num | value -----+------- 1 | xxx 3 | yyy 5 | zzz
那麼我們得到以下各種聯結的結果
=>
SELECT * FROM t1 CROSS JOIN t2;
num | name | num | value -----+------+-----+------- 1 | a | 1 | xxx 1 | a | 3 | yyy 1 | a | 5 | zzz 2 | b | 1 | xxx 2 | b | 3 | yyy 2 | b | 5 | zzz 3 | c | 1 | xxx 3 | c | 3 | yyy 3 | c | 5 | zzz (9 rows)=>
SELECT * FROM t1 INNER JOIN t2 ON t1.num = t2.num;
num | name | num | value -----+------+-----+------- 1 | a | 1 | xxx 3 | c | 3 | yyy (2 rows)=>
SELECT * FROM t1 INNER JOIN t2 USING (num);
num | name | value -----+------+------- 1 | a | xxx 3 | c | yyy (2 rows)=>
SELECT * FROM t1 NATURAL INNER JOIN t2;
num | name | value -----+------+------- 1 | a | xxx 3 | c | yyy (2 rows)=>
SELECT * FROM t1 LEFT JOIN t2 ON t1.num = t2.num;
num | name | num | value -----+------+-----+------- 1 | a | 1 | xxx 2 | b | | 3 | c | 3 | yyy (3 rows)=>
SELECT * FROM t1 LEFT JOIN t2 USING (num);
num | name | value -----+------+------- 1 | a | xxx 2 | b | 3 | c | yyy (3 rows)=>
SELECT * FROM t1 RIGHT JOIN t2 ON t1.num = t2.num;
num | name | num | value -----+------+-----+------- 1 | a | 1 | xxx 3 | c | 3 | yyy | | 5 | zzz (3 rows)=>
SELECT * FROM t1 FULL JOIN t2 ON t1.num = t2.num;
num | name | num | value -----+------+-----+------- 1 | a | 1 | xxx 2 | b | | 3 | c | 3 | yyy | | 5 | zzz (4 rows)
以 ON
指定的聯結條件也可以包含與聯結沒有直接關係的條件。 這對於某些查詢可能很有用,但需要仔細考慮。 例如
=>
SELECT * FROM t1 LEFT JOIN t2 ON t1.num = t2.num AND t2.value = 'xxx';
num | name | num | value -----+------+-----+------- 1 | a | 1 | xxx 2 | b | | 3 | c | | (3 rows)
請注意,將限制放置在 WHERE
子句中會產生不同的結果
=>
SELECT * FROM t1 LEFT JOIN t2 ON t1.num = t2.num WHERE t2.value = 'xxx';
num | name | num | value -----+------+-----+------- 1 | a | 1 | xxx (1 row)
這是因為放置在 ON
子句中的限制在聯結之前處理,而放置在 WHERE
子句中的限制在聯結之後處理。 這對於內部聯結沒有影響,但對於外部聯結而言影響很大。
可以為表格和複雜表格引用指定一個臨時名稱,以用於在查詢的其餘部分中引用衍生的表格。 這稱為表格別名。
要建立表格別名,請寫入
FROMtable_reference
ASalias
或
FROMtable_reference
alias
AS
關鍵字是可選的。 alias
可以是任何識別符號。
表格別名的典型應用是為長的表格名稱指定短識別符號,以保持聯結子句的可讀性。 例如
SELECT * FROM some_very_long_table_name s JOIN another_fairly_long_name a ON s.id = a.num;
就目前的查詢而言,別名成為表格引用的新名稱 — 不允許在查詢中的其他地方使用原始名稱引用表格。 因此,這無效
SELECT * FROM my_table AS m WHERE my_table.a > 5; -- wrong
表格別名主要用於符號的便利性,但在將表格聯結到自身時必須使用它們,例如
SELECT * FROM people AS mother JOIN people AS child ON mother.id = child.mother_id;
括號用於消除歧義。 在以下範例中,第一個語句將別名 b
分配給 my_table
的第二個實例,但第二個語句將別名分配給聯結的結果
SELECT * FROM my_table AS a CROSS JOIN my_table AS b ... SELECT * FROM (my_table AS a CROSS JOIN my_table) AS b ...
表格別名的另一種形式是為表格的欄位以及表格本身提供臨時名稱
FROMtable_reference
[AS]alias
(column1
[,column2
[, ...]] )
如果指定的欄位別名少於表格實際擁有的欄位數,則不會重新命名剩餘的欄位。 此語法對於自我聯結或子查詢特別有用。
當別名應用於 JOIN
子句的輸出時,別名會隱藏 JOIN
中的原始名稱。 例如
SELECT a.* FROM my_table AS a JOIN your_table AS b ON ...
是有效的 SQL,但是
SELECT a.* FROM (my_table AS a JOIN your_table AS b ON ...) AS c
無效;表格別名 a
在別名 c
之外不可見。
指定衍生表格的子查詢必須用括號括起來。 可以為它們分配表格別名名稱,並且可以選擇性地分配欄位別名名稱(如 第 7.2.1.2 節中所示)。 例如
FROM (SELECT * FROM table1) AS alias_name
此範例等效於 FROM table1 AS alias_name
。 當子查詢涉及分組或彙總時,會出現更令人感興趣的情況,這些情況無法簡化為簡單的聯結。
子查詢也可以是 VALUES
清單
FROM (VALUES ('anne', 'smith'), ('bob', 'jones'), ('joe', 'blow')) AS names(first, last)
同樣,表格別名是可選的。 將別名分配給 VALUES
清單的欄位是可選的,但這是一個好的做法。 有關更多資訊,請參閱 第 7.7 節。
根據 SQL 標準,子查詢必須提供表格別名。PostgreSQL 允許省略 AS
和別名,但編寫別名是在可能移植到其他系統的 SQL 程式碼中一個良好的習慣。
表格函數是產生一組列的函數,這些列由基本資料類型(純量類型)或複合資料類型(表格列)組成。它們像表格、檢視或子查詢一樣,用於查詢的 FROM
子句中。表格函數傳回的欄位可以包含在 SELECT
、JOIN
或 WHERE
子句中,就像表格、檢視或子查詢的欄位一樣。
表格函數也可以使用 ROWS FROM
語法組合,結果以平行欄位傳回;在這種情況下,結果列的數量是最大函數結果的數量,較小的結果會以 null 值填充以匹配。
function_call
[WITH ORDINALITY] [[AS]table_alias
[(column_alias
[, ... ])]] ROWS FROM(function_call
[, ... ] ) [WITH ORDINALITY] [[AS]table_alias
[(column_alias
[, ... ])]]
如果指定了 WITH ORDINALITY
子句,則會在函數結果欄位中新增一個 bigint
類型的額外欄位。此欄位會對函數結果集中的列進行編號,從 1 開始。(這是 SQL 標準語法 UNNEST ... WITH ORDINALITY
的推廣。)預設情況下,序數欄位稱為 ordinality
,但可以使用 AS
子句為其分配不同的欄位名稱。
特殊表格函數 UNNEST
可以使用任意數量的陣列參數呼叫,它會傳回相應數量的欄位,如同對每個參數分別呼叫 UNNEST
(第 9.19 節)並使用 ROWS FROM
建構組合一樣。
UNNEST(array_expression
[, ... ] ) [WITH ORDINALITY] [[AS]table_alias
[(column_alias
[, ... ])]]
如果沒有指定 table_alias
,則函數名稱將用作表格名稱;在 ROWS FROM()
建構的情況下,將使用第一個函數的名稱。
如果未提供欄位別名,則對於傳回基本資料類型的函數,欄位名稱也與函數名稱相同。對於傳回複合類型的函數,結果欄位會取得該類型各個屬性的名稱。
一些例子
CREATE TABLE foo (fooid int, foosubid int, fooname text); CREATE FUNCTION getfoo(int) RETURNS SETOF foo AS $$ SELECT * FROM foo WHERE fooid = $1; $$ LANGUAGE SQL; SELECT * FROM getfoo(1) AS t1; SELECT * FROM foo WHERE foosubid IN ( SELECT foosubid FROM getfoo(foo.fooid) z WHERE z.fooid = foo.fooid ); CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1); SELECT * FROM vw_getfoo;
在某些情況下,定義可以根據呼叫方式傳回不同欄位集的表格函數很有用。為了支援這一點,可以將表格函數宣告為傳回偽類型 record
,並且沒有 OUT
參數。當在查詢中使用此類函數時,必須在查詢本身中指定預期的列結構,以便系統可以知道如何解析和規劃查詢。此語法如下所示:
function_call
[AS]alias
(column_definition
[, ... ])function_call
AS [alias
] (column_definition
[, ... ]) ROWS FROM( ...function_call
AS (column_definition
[, ... ]) [, ... ] )
當不使用 ROWS FROM()
語法時,column_definition
列表會取代可以附加到 FROM
項目的欄位別名列表;欄位定義中的名稱用作欄位別名。當使用 ROWS FROM()
語法時,可以將 column_definition
列表分別附加到每個成員函數;或者,如果只有一個成員函數且沒有 WITH ORDINALITY
子句,則可以將 column_definition
列表寫入到 ROWS FROM()
後面的欄位別名列表的位置。
考慮以下範例
SELECT * FROM dblink('dbname=mydb', 'SELECT proname, prosrc FROM pg_proc') AS t1(proname name, prosrc text) WHERE proname LIKE 'bytea%';
dblink 函數(dblink 模組的一部分)執行遠端查詢。它被宣告為傳回 record
,因為它可以被用於任何類型的查詢。實際的欄位集必須在呼叫查詢中指定,以便解析器知道,例如,*
應該展開成什麼。
此範例使用 ROWS FROM
SELECT * FROM ROWS FROM ( json_to_recordset('[{"a":40,"b":"foo"},{"a":"100","b":"bar"}]') AS (a INTEGER, b TEXT), generate_series(1, 3) ) AS x (p, q, s) ORDER BY p; p | q | s -----+-----+--- 40 | foo | 1 100 | bar | 2 | | 3
它將兩個函數合併到單個 FROM
目標中。json_to_recordset()
被指示傳回兩個欄位,第一個是 integer
,第二個是 text
。generate_series()
的結果被直接使用。ORDER BY
子句將欄位值排序為整數。
LATERAL
子查詢 #出現在 FROM
中的子查詢可以加上關鍵字 LATERAL
。這允許它們引用前面 FROM
項目提供的欄位。(如果沒有 LATERAL
,則每個子查詢都會獨立評估,因此無法交叉引用任何其他 FROM
項目。)
出現在 FROM
中的表格函數也可以加上關鍵字 LATERAL
,但對於函數而言,該關鍵字是可選的;在任何情況下,函數的引數都可以包含對前面 FROM
項目提供的欄位的引用。
LATERAL
項目可以出現在 FROM
列表的頂層,也可以出現在 JOIN
樹中。在後一種情況下,它也可以引用位於 JOIN
左側的任何項目,它位於該 JOIN
的右側。
當 FROM
項目包含 LATERAL
交叉引用時,評估按如下方式進行:對於提供交叉引用欄位的 FROM
項目中的每一列,或提供欄位的多個 FROM
項目中的每一組列,使用該列或列集合的欄位值評估 LATERAL
項目。結果列以通常的方式與它們計算所依據的列連接。對於來自欄位來源表格的每一列或列集合,都會重複此過程。
LATERAL
的一個簡單範例是
SELECT * FROM foo, LATERAL (SELECT * FROM bar WHERE bar.id = foo.bar_id) ss;
這不是特別有用,因為它與更傳統的方式具有完全相同的結果
SELECT * FROM foo, bar WHERE bar.id = foo.bar_id;
當計算要連接的列時需要交叉引用的欄位時,LATERAL
主要是非常有用的。一個常見的應用是為集合傳回函數提供引數值。例如,假設 vertices(polygon)
傳回多邊形的頂點集合,我們可以識別表格中儲存的多邊形的緊密相鄰的頂點,如下所示
SELECT p1.id, p2.id, v1, v2 FROM polygons p1, polygons p2, LATERAL vertices(p1.poly) v1, LATERAL vertices(p2.poly) v2 WHERE (v1 <-> v2) < 10 AND p1.id != p2.id;
此查詢也可以寫成
SELECT p1.id, p2.id, v1, v2 FROM polygons p1 CROSS JOIN LATERAL vertices(p1.poly) v1, polygons p2 CROSS JOIN LATERAL vertices(p2.poly) v2 WHERE (v1 <-> v2) < 10 AND p1.id != p2.id;
或幾種其他等效的公式。(如前所述,在此範例中,LATERAL
關鍵字是不必要的,但我們為了清楚起見使用了它。)
將 LEFT JOIN
連接到 LATERAL
子查詢通常特別方便,以便即使 LATERAL
子查詢沒有為它們產生列,來源列也會出現在結果中。例如,如果 get_product_names()
傳回製造商生產的產品名稱,但我們表格中的某些製造商目前沒有生產任何產品,我們可以找到哪些製造商是這樣的,如下所示
SELECT m.name FROM manufacturers m LEFT JOIN LATERAL get_product_names(m.id) pname ON true WHERE pname IS NULL;
WHERE
子句 #WHERE
子句的語法如下:
WHERE search_condition
其中 search_condition
是任何傳回 boolean
類型的值的值表達式(請參閱 第 4.2 節)。
在 FROM
子句處理完成後,衍生虛擬表格中的每一列都會根據搜尋條件進行檢查。如果條件的結果為 true,則該列會保留在輸出表格中,否則(即,如果結果為 false 或 null)會被捨棄。搜尋條件通常會參考 FROM
子句中產生的表格的至少一個欄位;雖然這不是必需的,但如果沒有參考欄位,WHERE
子句將會相當無用。
內部聯結的聯結條件可以寫在 WHERE
子句或 JOIN
子句中。例如,以下表格運算式是等效的:
FROM a, b WHERE a.id = b.id AND b.val > 5
以及
FROM a INNER JOIN b ON (a.id = b.id) WHERE b.val > 5
或者甚至
FROM a NATURAL JOIN b WHERE b.val > 5
您使用哪一種方法主要是一種風格問題。即使 FROM
子句中的 JOIN
語法是 SQL 標準的一部分,它可能不那麼容易移植到其他 SQL 資料庫管理系統。對於外部聯結,沒有其他選擇:它們必須在 FROM
子句中完成。外部聯結的 ON
或 USING
子句不等同於 WHERE
條件,因為它會導致新增列(對於不匹配的輸入列)以及從最終結果中移除列。
以下是一些 WHERE
子句的範例:
SELECT ... FROM fdt WHERE c1 > 5 SELECT ... FROM fdt WHERE c1 IN (1, 2, 3) SELECT ... FROM fdt WHERE c1 IN (SELECT c1 FROM t2) SELECT ... FROM fdt WHERE c1 IN (SELECT c3 FROM t2 WHERE c2 = fdt.c1 + 10) SELECT ... FROM fdt WHERE c1 BETWEEN (SELECT c3 FROM t2 WHERE c2 = fdt.c1 + 10) AND 100 SELECT ... FROM fdt WHERE EXISTS (SELECT c1 FROM t2 WHERE c2 > fdt.c1)
fdt
是在 FROM
子句中衍生的表格。不符合 WHERE
子句搜尋條件的列會從 fdt
中移除。請注意如何使用純量子查詢作為數值運算式。與任何其他查詢一樣,子查詢可以使用複雜的表格運算式。另請注意 fdt
如何在子查詢中被參考。只有當 c1
也是子查詢的衍生輸入表格中的欄位名稱時,才需要將 c1
限定為 fdt.c1
。但是,即使不需要限定欄位名稱,也能增加清晰度。此範例說明外部查詢的欄位命名範圍如何擴展到其內部查詢中。
GROUP BY
和 HAVING
子句 #在通過 WHERE
篩選器後,衍生的輸入表格可能會受到分組的影響,使用 GROUP BY
子句,並使用 HAVING
子句來刪除群組列。
SELECTselect_list
FROM ... [WHERE ...] GROUP BYgrouping_column_reference
[,grouping_column_reference
]...
GROUP BY
子句用於將表格中所有列出的欄位中具有相同值的列組合在一起。列出的欄位的順序並不重要。其作用是將每組具有相同值的列組合成一個群組列,該群組列代表該群組中的所有列。這樣做是為了消除輸出中的冗餘,以及/或計算應用於這些群組的彙總。例如:
=>
SELECT * FROM test1;
x | y ---+--- a | 3 c | 2 b | 5 a | 1 (4 rows)=>
SELECT x FROM test1 GROUP BY x;
x --- a b c (3 rows)
在第二個查詢中,我們不能寫成 SELECT * FROM test1 GROUP BY x
,因為沒有單個值可以將欄位 y
與每個群組關聯起來。分組依據的欄位可以在選取清單中被參考,因為它們在每個群組中都有一個單個值。
一般來說,如果表格已分組,則除非在彙總運算式中,否則無法參考未在 GROUP BY
中列出的欄位。以下是一個包含彙總運算式的範例:
=>
SELECT x, sum(y) FROM test1 GROUP BY x;
x | sum ---+----- a | 4 b | 5 c | 2 (3 rows)
這裡 sum
是一個彙總函數,它計算整個群組的單個值。有關可用彙總函數的更多資訊,請參閱 第 9.21 節。
在沒有彙總運算式的情況下進行分組可以有效地計算欄位中相異值的集合。這也可以使用 DISTINCT
子句來實現(請參閱 第 7.3.3 節)。
以下是另一個範例:它計算每個產品的總銷售額(而不是所有產品的總銷售額):
SELECT product_id, p.name, (sum(s.units) * p.price) AS sales FROM products p LEFT JOIN sales s USING (product_id) GROUP BY product_id, p.name, p.price;
在本範例中,欄位 product_id
、p.name
和 p.price
必須位於 GROUP BY
子句中,因為它們在查詢選取清單中被參考(但請參閱下文)。欄位 s.units
不必位於 GROUP BY
清單中,因為它僅在彙總運算式 (sum(...)
) 中使用,該運算式表示產品的銷售額。對於每個產品,查詢會傳回有關該產品所有銷售額的摘要列。
如果產品表格的設定方式是,例如,product_id
是主索引鍵,那麼在上面的範例中僅按 product_id
分組就足夠了,因為名稱和價格將在功能上依賴於產品 ID,因此對於每個產品 ID 群組要傳回哪個名稱和價格值都不會存在歧義。
在嚴格的 SQL 中,GROUP BY
只能按來源表格的欄位分組,但 PostgreSQL 擴展了這一點,允許 GROUP BY
也按選取清單中的欄位分組。也允許按數值運算式而不是簡單的欄位名稱分組。
如果表格已使用 GROUP BY
進行分組,但只有某些群組是感興趣的,則可以使用 HAVING
子句(很像 WHERE
子句)從結果中刪除群組。語法為:
SELECTselect_list
FROM ... [WHERE ...] GROUP BY ... HAVINGboolean_expression
HAVING
子句中的運算式可以參考分組運算式和未分組運算式(必然包含彙總函數)。
範例
=>
SELECT x, sum(y) FROM test1 GROUP BY x HAVING sum(y) > 3;
x | sum ---+----- a | 4 b | 5 (2 rows)=>
SELECT x, sum(y) FROM test1 GROUP BY x HAVING x < 'c';
x | sum ---+----- a | 4 b | 5 (2 rows)
再次,一個更實際的範例:
SELECT product_id, p.name, (sum(s.units) * (p.price - p.cost)) AS profit FROM products p LEFT JOIN sales s USING (product_id) WHERE s.date > CURRENT_DATE - INTERVAL '4 weeks' GROUP BY product_id, p.name, p.price, p.cost HAVING sum(p.price * s.units) > 5000;
在上面的範例中,WHERE
子句是按未分組的欄位選取列(該運算式僅適用於過去四週的銷售額),而 HAVING
子句將輸出限制為總銷售額超過 5000 的群組。請注意,彙總運算式不一定需要在查詢的所有部分都相同。
如果查詢包含彙總函數呼叫,但沒有 GROUP BY
子句,則仍然會發生分組:結果是單個群組列(或者如果該單個列隨後被 HAVING
刪除,則可能根本沒有列)。如果它包含 HAVING
子句,即使沒有任何彙總函數呼叫或 GROUP BY
子句,也是如此。
GROUPING SETS
、CUBE
和 ROLLUP
#使用分組集合的概念,可以實現比上述更複雜的分組操作。由 FROM
和 WHERE
子句選取的資料會由每個指定的分組集合分別分組,就像簡單的 GROUP BY
子句一樣計算每個群組的彙總,然後傳回結果。例如:
=>
SELECT * FROM items_sold;
brand | size | sales -------+------+------- Foo | L | 10 Foo | M | 20 Bar | M | 15 Bar | L | 5 (4 rows)=>
SELECT brand, size, sum(sales) FROM items_sold GROUP BY GROUPING SETS ((brand), (size), ());
brand | size | sum -------+------+----- Foo | | 30 Bar | | 20 | L | 15 | M | 35 | | 50 (5 rows)
GROUPING SETS
的每個子清單可以指定零個或多個欄位或運算式,並以與直接位於 GROUP BY
子句中相同的方式進行解釋。空分組集合意味著所有列都被彙總為單個群組(即使不存在任何輸入列也會輸出),如上文所述的沒有 GROUP BY
子句的彙總函數的情況。
在那些欄位沒有出現的分組集合中,對分組欄位或運算式的參考會被結果列中的 null 值取代。要區分特定輸出列來自哪個分組,請參閱表格 9.64。
提供了一種簡寫符號,用於指定兩種常見的分組集合類型。形式為的子句
ROLLUP (e1
,e2
,e3
, ... )
表示給定的表達式列表以及該列表的所有前綴,包括空列表;因此,它等同於
GROUPING SETS ( (e1
,e2
,e3
, ... ), ... (e1
,e2
), (e1
), ( ) )
這通常用於分析階層式資料;例如,按部門、事業部和公司總計的薪資。
形式如下的子句
CUBE (e1
,e2
, ... )
表示給定的列表及其所有可能的子集(即冪集)。因此
CUBE ( a, b, c )
等同於
GROUPING SETS ( ( a, b, c ), ( a, b ), ( a, c ), ( a ), ( b, c ), ( b ), ( c ), ( ) )
CUBE
或 ROLLUP
子句的個別元素可以是個別表達式,也可以是括在括號中的元素子列表。在後一種情況下,子列表被視為單個單元,以產生個別分組集。例如
CUBE ( (a, b), (c, d) )
等同於
GROUPING SETS ( ( a, b, c, d ), ( a, b ), ( c, d ), ( ) )
以及
ROLLUP ( a, (b, c), d )
等同於
GROUPING SETS ( ( a, b, c, d ), ( a, b, c ), ( a ), ( ) )
CUBE
和 ROLLUP
結構可以直接在 GROUP BY
子句中使用,也可以巢狀於 GROUPING SETS
子句內。如果一個 GROUPING SETS
子句巢狀於另一個子句內,則效果與將內部子句的所有元素直接寫入外部子句相同。
如果在單個 GROUP BY
子句中指定了多個分組項目,則最終分組集列表是個別項目的笛卡爾積。例如
GROUP BY a, CUBE (b, c), GROUPING SETS ((d), (e))
等同於
GROUP BY GROUPING SETS ( (a, b, c, d), (a, b, c, e), (a, b, d), (a, b, e), (a, c, d), (a, c, e), (a, d), (a, e) )
將多個分組項目一起指定時,最終的分組集集合可能包含重複項。例如
GROUP BY ROLLUP (a, b), ROLLUP (a, c)
等同於
GROUP BY GROUPING SETS ( (a, b, c), (a, b), (a, b), (a, c), (a), (a), (a, c), (a), () )
如果這些重複項是不希望的,可以使用 DISTINCT
子句直接在 GROUP BY
上將其刪除。因此
GROUP BY DISTINCT ROLLUP (a, b), ROLLUP (a, c)
等同於
GROUP BY GROUPING SETS ( (a, b, c), (a, b), (a, c), (a), () )
這與使用 SELECT DISTINCT
不同,因為輸出列仍然可能包含重複項。如果任何未分組的列包含 NULL,則它將與分組相同列時使用的 NULL 無法區分。
結構 (a, b)
通常在表達式中被識別為列建構函式。在 GROUP BY
子句中,這不適用於表達式的頂層,並且 (a, b)
被解析為如上所述的表達式列表。如果由於某些原因您需要在分組表達式中使用列建構函式,請使用 ROW(a, b)
。
如果查詢包含任何視窗函式(請參閱第 3.5 節、第 9.22 節和第 4.2.8 節),則這些函式會在執行任何分組、聚合和 HAVING
篩選之後進行評估。也就是說,如果查詢使用任何聚合、GROUP BY
或 HAVING
,則視窗函式看到的列是群組列,而不是來自 FROM
/WHERE
的原始資料表列。
當使用多個視窗函式時,保證所有在其視窗定義中具有語法上等效的 PARTITION BY
和 ORDER BY
子句的視窗函式,都會在資料的單次傳遞中進行評估。因此,即使 ORDER BY
無法唯一確定排序,它們也會看到相同的排序。但是,對於具有不同 PARTITION BY
或 ORDER BY
規格的函式,則不做任何保證。(在這種情況下,通常需要在視窗函式評估的傳遞之間進行排序步驟,並且不保證該排序能保留其 ORDER BY
視為等效的列的排序。)
目前,視窗函式始終需要預先排序的資料,因此查詢輸出將按照一個或另一個視窗函式的 PARTITION BY
/ORDER BY
子句進行排序。但是,不建議依賴此行為。如果要確保結果以特定方式排序,請使用明確的頂層 ORDER BY
子句。
如果您在文件中發現任何不正確、與您使用特定功能的經驗不符或需要進一步澄清之處,請使用此表單回報文件問題。