支援的版本:目前 (17) / 16 / 15 / 14 / 13
開發版本:devel
不支援的版本:12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0 / 8.4 / 8.3 / 8.2 / 8.1 / 8.0 / 7.4 / 7.3 / 7.2

7.2. 表述式 #

表述式會計算出一個表。表述式包含一個 FROM 子句,其後可選擇性地接續 WHEREGROUP BYHAVING 子句。簡單的表述式僅僅參考磁碟上的表,也就是所謂的基底表,但更複雜的表示式可以用於以各種方式修改或組合基底表。

表述式中可選的 WHEREGROUP BYHAVING 子句指定了對 FROM 子句中衍生的表執行的一系列連續轉換。所有這些轉換都會產生一個虛擬表,該表提供傳遞到選取清單的列,以計算查詢的輸出列。

7.2.1. FROM 子句 #

FROM 子句從以逗號分隔的表參考清單中給定的一個或多個其他表衍生出一個表。

FROM table_reference [, table_reference [, ...]]

表參考可以是表名稱(可能具有 schema 限定詞),或衍生的表,例如子查詢、JOIN 建構,或這些的複雜組合。如果在 FROM 子句中列出多個表參考,則這些表會交叉聯結(也就是形成其列的笛卡爾積;請參閱下文)。FROM 清單的結果是一個中間虛擬表,然後可以通過 WHEREGROUP BYHAVING 子句進行轉換,最終成為整個表述式的結果。

當表參考命名一個表,該表是表繼承階層的父表時,表參考會產生不僅該表的列,還會產生其所有後代表的列,除非關鍵字 ONLY 位於表名稱之前。但是,參考僅產生出現在命名表中的列 — 在子表中新增的任何列都會被忽略。

您可以編寫 * 在表名稱之後,而不是在表名稱之前編寫 ONLY,以明確指定包含後代表。現在沒有真正的理由再使用此語法,因為搜尋後代表現在始終是預設行為。但是,為了與舊版本相容,仍然支援它。

7.2.1.1. 聯結表 #

聯結表是根據特定聯結類型的規則從另外兩個(真實或衍生的)表衍生而來的表。可以使用內部聯結、外部聯結和交叉聯結。聯結表的一般語法為

T1 join_type T2 [ join_condition ]

所有類型的聯結都可以鏈結在一起或巢狀:T1T2 中的任一或兩者都可以是聯結表。可以使用括號圍繞 JOIN 子句來控制聯結順序。在沒有括號的情況下,JOIN 子句會從左到右巢狀。

聯結類型

交叉聯結
T1 CROSS JOIN T2

對於 T1T2 中列的每個可能組合(即笛卡爾積),聯結表將包含一個列,該列由 T1 中的所有列,後接 T2 中的所有列組成。如果表分別有 N 和 M 列,則聯結表將有 N * M 列。

FROM T1 CROSS JOIN T2 等同於 FROM T1 INNER JOIN T2 ON TRUE(請參閱下文)。它也等同於 FROM T1, T2

請注意

當出現兩個以上的表時,後一種等價性並不完全成立,因為 JOIN 比逗號的約束力更強。例如,FROM T1 CROSS JOIN T2 INNER JOIN T3 ON conditionFROM T1, T2 INNER JOIN T3 ON condition 不同,因為 condition 可以在第一種情況下參考 T1,但不能在第二種情況下參考。

限定聯結
T1 { [INNER] | { LEFT | RIGHT | FULL } [OUTER] } JOIN T2 ON boolean_expression
T1 { [INNER] | { LEFT | RIGHT | FULL } [OUTER] } JOIN T2 USING ( join column list )
T1 NATURAL { [INNER] | { LEFT | RIGHT | FULL } [OUTER] } JOIN T2

在所有形式中,INNEROUTER 這兩個詞是可選的。INNER 是預設值;LEFTRIGHTFULL 表示外部聯結。

聯結條件是在 ONUSING 子句中指定,或透過 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,則來自 T1T2 的一對列即符合。

USING 子句是一種簡寫,可讓您利用聯結的兩側對聯結欄位使用相同名稱的特定情況。 它接受以逗號分隔的共享欄位名稱清單,並形成一個聯結條件,其中包含每個欄位的相等性比較。 例如,使用 USING (a, b) 聯結 T1T2 會產生聯結條件 ON T1.a = T2.a AND T1.b = T2.b

此外,JOIN USING 的輸出會抑制冗餘欄位:由於它們必須具有相等的值,因此無需列印兩個符合的欄位。 雖然 JOIN ON 產生來自 T1 的所有欄位,然後是來自 T2 的所有欄位,但 JOIN USING 為每個列出的欄位對(按列出的順序)產生一個輸出欄位,然後是來自 T1 的任何剩餘欄位,然後是來自 T2 的任何剩餘欄位。

最後,NATURALUSING 的簡寫形式:它形成一個 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 子句中的限制在聯結之後處理。 這對於內部聯結沒有影響,但對於外部聯結而言影響很大。

7.2.1.2. 表格和欄位別名 #

可以為表格和複雜表格引用指定一個臨時名稱,以用於在查詢的其餘部分中引用衍生的表格。 這稱為表格別名

要建立表格別名,請寫入

FROM table_reference AS alias

FROM table_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 ...

表格別名的另一種形式是為表格的欄位以及表格本身提供臨時名稱

FROM table_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.3. 子查詢 #

指定衍生表格的子查詢必須用括號括起來。 可以為它們分配表格別名名稱,並且可以選擇性地分配欄位別名名稱(如 第 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 程式碼中一個良好的習慣。

7.2.1.4. 表格函數 #

表格函數是產生一組列的函數,這些列由基本資料類型(純量類型)或複合資料類型(表格列)組成。它們像表格、檢視或子查詢一樣,用於查詢的 FROM 子句中。表格函數傳回的欄位可以包含在 SELECTJOINWHERE 子句中,就像表格、檢視或子查詢的欄位一樣。

表格函數也可以使用 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,第二個是 textgenerate_series() 的結果被直接使用。ORDER BY 子句將欄位值排序為整數。

7.2.1.5. 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;

7.2.2. 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 子句中完成。外部聯結的 ONUSING 子句等同於 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。但是,即使不需要限定欄位名稱,也能增加清晰度。此範例說明外部查詢的欄位命名範圍如何擴展到其內部查詢中。

7.2.3. GROUP BYHAVING 子句 #

在通過 WHERE 篩選器後,衍生的輸入表格可能會受到分組的影響,使用 GROUP BY 子句,並使用 HAVING 子句來刪除群組列。

SELECT select_list
    FROM ...
    [WHERE ...]
    GROUP BY grouping_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_idp.namep.price 必須位於 GROUP BY 子句中,因為它們在查詢選取清單中被參考(但請參閱下文)。欄位 s.units 不必位於 GROUP BY 清單中,因為它僅在彙總運算式 (sum(...)) 中使用,該運算式表示產品的銷售額。對於每個產品,查詢會傳回有關該產品所有銷售額的摘要列。

如果產品表格的設定方式是,例如,product_id 是主索引鍵,那麼在上面的範例中僅按 product_id 分組就足夠了,因為名稱和價格將在功能上依賴於產品 ID,因此對於每個產品 ID 群組要傳回哪個名稱和價格值都不會存在歧義。

在嚴格的 SQL 中,GROUP BY 只能按來源表格的欄位分組,但 PostgreSQL 擴展了這一點,允許 GROUP BY 也按選取清單中的欄位分組。也允許按數值運算式而不是簡單的欄位名稱分組。

如果表格已使用 GROUP BY 進行分組,但只有某些群組是感興趣的,則可以使用 HAVING 子句(很像 WHERE 子句)從結果中刪除群組。語法為:

SELECT select_list FROM ... [WHERE ...] GROUP BY ... HAVING boolean_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 子句,也是如此。

7.2.4. GROUPING SETSCUBEROLLUP #

使用分組集合的概念,可以實現比上述更複雜的分組操作。由 FROMWHERE 子句選取的資料會由每個指定的分組集合分別分組,就像簡單的 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 ),
    (         )
)

CUBEROLLUP 子句的個別元素可以是個別表達式,也可以是括在括號中的元素子列表。在後一種情況下,子列表被視為單個單元,以產生個別分組集。例如

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          ),
    (            )
)

CUBEROLLUP 結構可以直接在 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)

7.2.5. 視窗函式處理 #

如果查詢包含任何視窗函式(請參閱第 3.5 節第 9.22 節第 4.2.8 節),則這些函式會在執行任何分組、聚合和 HAVING 篩選之後進行評估。也就是說,如果查詢使用任何聚合、GROUP BYHAVING,則視窗函式看到的列是群組列,而不是來自 FROM/WHERE 的原始資料表列。

當使用多個視窗函式時,保證所有在其視窗定義中具有語法上等效的 PARTITION BYORDER BY 子句的視窗函式,都會在資料的單次傳遞中進行評估。因此,即使 ORDER BY 無法唯一確定排序,它們也會看到相同的排序。但是,對於具有不同 PARTITION BYORDER BY 規格的函式,則不做任何保證。(在這種情況下,通常需要在視窗函式評估的傳遞之間進行排序步驟,並且不保證該排序能保留其 ORDER BY 視為等效的列的排序。)

目前,視窗函式始終需要預先排序的資料,因此查詢輸出將按照一個或另一個視窗函式的 PARTITION BY/ORDER BY 子句進行排序。但是,不建議依賴此行為。如果要確保結果以特定方式排序,請使用明確的頂層 ORDER BY 子句。

提交更正

如果您在文件中發現任何不正確、與您使用特定功能的經驗不符或需要進一步澄清之處,請使用此表單回報文件問題。