7.2. 資料表表示式

一個 資料表表示式 計算出一個資料表。資料表表示式包含了一個可以選擇在後方跟隨WHEREGROUP BYHAVING子句的FROM子句。普遍的資料表表示式簡單地在磁碟上引用一個資料表,, 即聲稱的基底資料表(base table), 但更複雜的表示式可被用於以多種形式修改或組合基底資料表。

在資料表表示式中選擇性的WHEREGROUP BYHAVING子句指定一個逐次變換執行在FROM子句衍生的資料表上的管道。所有的這些轉換都會產生一個虛擬資料表,該資料表提供了被傳遞到選擇串列的資料列,以計算查詢的輸出資料列。

7.2.1. FROM子句

The FROM子句從逗號分隔資料表參照串列中給出的一個或多個其他的資料表衍生一個資料表。

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

一個資料表參照能是一個表格名稱(也許綱要限定的),或一個衍生出的資料表,例如子查詢,JOIN建構或這些的複雜組合。如果多個資料表參照被列在FROM子句中, 這些資料表參照則表將被交叉聯接(cross-joined,即形成其資料列的笛卡爾積;請參見下文。)FROM串列的結果是一個中間的虛擬表,該表可以受到WHEREGROUP BYHAVING子句的轉換,並且最終是整個資料表表示式的結果。

當一個資料表參照命名一個表格繼承層次結構的父級資料表,資料表參照不只是產生該表格的列,還會產生其所有後代表格的列,除非關鍵字ONLY在表格名稱之前。然而,該參照僅產生出現在已命名資料表中的欄位—子資料表中添加的任何欄位都將被忽略。

可以在表格名稱之後寫入*來明確指定包含後代表格,而不是在表格名稱之前寫入ONLY。因為搜索後代表格現在始終是默認行為,沒有真正的理由再使用此語法。但是,支持它是為了與舊版本的兼容性。

7.2.1.1. 聯接的資料表

聯接的資料表(joined table)是一個根據特定聯接型別的規則從兩個(真實的或被衍生的)其他資料表衍生的資料表。可以使用 Inner、outer、及cross-join 。聯接資料表的一般語法是

T1 join_type T2 [ join_condition ]

所有型別的聯接可以鏈結或嵌套在一起: T1 and T2 中的一個或兩個都可以被聯接資料表。可以在JOIN子句周圍使用括號來控制聯接順序。在沒有括號的情況下,JOIN子句從左到右嵌套。

聯接型別

Cross join

T1 CROSS JOIN T2

對於從 T1 and T2 的列的每種可能的組合(即笛卡爾積), 聯接的資料表將包含一個由 T1 所有欄其次是 T2 所有欄組成的列。如果資料表分別有 N 列及 M 列, 聯接表將具有 N * M 列。

FROM T1CROSS JOIN T2 相當於 FROM T1 INNER JOIN T2 ON TRUE(見下文。)它也等同於 FROM T1, T2

注意

當出現兩個以上的表時,後者的等價關係並不完全成立,因為JOIN的綁定比逗號更緊密。例如,FROM T1 CROSS JOIN T2 INNER JOIN T3 ON condition 不同於FROM T1, T2 INNER JOIN T3 ON condition 因為 condition 可以第一種情況中但不能在第二個情況中參照 T1

Qualified joins

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 表示外部聯接。

ON or USING子句中指定 join condition ,或由單詞NATURAL隱式指定。聯接條件決定兩個來源資料表中的哪些列被視為“匹配”,如下面詳細的說明。

限定聯接(qualified joins)的可能型別為:

INNER JOIN

對於T1的每一列 R1 ,聯接表有一列在T2中的每一列中滿足R1的聯接條件。

LEFT OUTER JOIN

首先,執行內部聯接。然後,對於T1中每一列與T2中任何列不滿足聯接條件,聯接列在T2的欄中添加空值。因此,對於T1中的每一列聯接表始終至少具有一列。

RIGHT OUTER JOIN

首先,執行內部聯接。 然後,對於T2中每一列與T1中任何列不滿足聯接條件,聯接列在T1的欄中添加空值。這是左聯接的反面:對於T2中的每一列結果表將始終有一列。

FULL OUTER JOIN

首先,執行內部聯接。然後,對於T1中每一列與T2中任何列不滿足聯接條件,聯接列在T2的欄中添加空值。另外,對於T2中每一列與T1中任何列不滿足聯接條件,聯接列在T1的欄中添加空值。

ON子句是最通用種類的聯接條件:它採用與WHERE子句中使用的種類相同的Boolean值表示式。如果 ON表示式評估為真值,來自 T1T2 的一對資料列匹配。

USING 子句是一種簡寫形式,可讓您在特定的情況充分利用,即在聯接兩端使用相同的名稱聯接欄位。它使用逗號分隔的共享欄位名稱串列並形成一個包括每個條件相等性比較的聯接條件。例如,將 T1T2USING (a, b) 進行聯接會產生聯接條件ON T1.a =T2.a ANDT1.b =T2.b

此外,JOIN USING的輸出抑制多餘的欄:無需打印兩個匹配的欄,因為它們必須具有相等的值。儘管JOIN ON會產生 T1 的所有欄其次是 T2 的所有欄,JOIN USING為每個列出的欄配對(按照列出的順序)產生一個輸出欄,其次是 T1 的所有剩餘欄,其次是 T2 的所有剩餘欄。

最後,NATURALUSING的簡寫形式:它形成一個由出現在兩個輸入資料表中的所有欄位名稱組成的USING串列。 與USING一樣,這些欄在輸出表中僅出現一次。如果沒有共用的欄位名稱,NATURAL JOIN 的行為類似於JOIN ... ON TRUE,產生外積聯接(cross-product 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. 資料表和欄位別名

可以為資料表和復雜資料表參照給定一個臨時名稱來用在其餘查詢中參照衍生的資料表。這稱為 資料表別名(table alias)

要創建資料表別名,請編寫

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;

此外,如果表參照是子查詢,則需要別名(詳見7.2.1.3節。)

括號被用於解決歧義。在以下範例中,第一條語句將別名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節

7.2.1.4. 資料表函數

資料表函數是產生一組資料列的函數,這些列由基本資料型別(標量(scalar)型別)或複合數資料型別(資料表列)組成。在查詢的 FROM 子句中,它們像資料表、檢視表或子查詢一樣使用。資料表函數返回的欄位以資料表欄位、檢視表或子查詢相同的方式可以包含在SELECTJOINWHERE子句中。

資料表函數也可以使用ROWS FROM語法進行組合,以並行欄位返回結果;在這種情況下結果列的數量是最大的函數結果,較小的結果將填充空值來匹配。

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也許伴隨著任意數量的陣列參數被調用,並且他返回一個對應數量的欄位,就如同分別對每個參數調用UNNEST9.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。在查詢中使用此種函數時,在查詢本身中必須指定預期的資料列結構,以便讓系統知道如何解析和規劃查詢。這種語法看起來像是:

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,因為它可以用於任何種類的查詢。實際的欄位集合必須被指定在調用的查詢以便讓解析器知道,舉例來說,*應該擴展成什麼。

7.2.1.5. LATERAL子查詢

出現在FROM中的子查詢的前面可以有關鍵字LATERAL。這允許它們參照前面FROM項目提供的欄位。(沒有LATERAL的話,每一個子查詢被個別評估所以不能交叉參照任何其他FROM項目。)

出現在FROM中的資料表函數的前面也能有關鍵字LATERAL,但對於函數來說該關鍵字是選擇性的;在任何情況下該函數的參數能包含前面FROM項目提供的欄位參照。

LATERAL項目能出現在FROM串列的頂層,或在JOIN樹之中。在後面的情況下在JOIN右邊的LATERAL也能引用在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在此範例中是不必要的,但為了清楚起見而使用它。)

即使LATERAL子查詢沒有產生資料列,通常特別便利將LEFT JOIN添加到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排除。請注意標量(scalar)子查詢作為值表示式的使用。就像任何其他查詢一樣,子查詢可以採用複雜的資料表表示式。還要注意在子查詢中fdt是如何被參照的。僅當c1也是子查詢衍生輸入表中的欄位名稱時,限定(qualifying)c1fdt.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節

Tip

沒有彙總表示式的分組有效地運算一個欄位中的相異值集合。這也可以使用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.name、及p.price必須在GROUP BY子句中是由於它們在查詢選擇串列中被參照(但詳見下文。)欄位s.units沒有需要在GROUP BY串列是由於它只能使用在彙總表示式(sum(...)),其代表一個產品的銷售。對於每個產品,查詢返回關於該產品所有銷售的摘要資料列。

如果產品資料被設置為product_id是主鍵(primary key),然後在上方的範例中它足以經由被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排除該單一資料列。)即使沒有任何彙總函數調用或 GROUP BY子句,如果包含HAVING子句則同樣會發生。

7.2.4. GROUPING SETSCUBEROLLUP

更多比上方描述較複雜的分組操作可以使用 分組集合(grouping sets) 的概念。經由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.59

為了指定兩個分組集合的常見型別提供了一個簡寫表示法。該形式的子句為

ROLLUP ( e1, e2, e3, ... )

代表了給定的表達式串列和該串列的所有前綴,包括空串列;因此它相當於

GROUPING SETS (
    ( e1, e2, e3, ... ),
    ...
    ( e1, e2 ),
    ( e1 ),
    ( )
)

這通常用於分析階層式資料:例如,部門,分部和公司的總薪資。

另一形式的子句為

CUBE ( e1, e2, ... )

表示給定的串列和所有可能的子集合(即power set。)因此

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

注意

建構 (a, b)一般來說在表示式中被辨識為一個資料列建構子(row constructor)。在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子句如果要確保結果以特定方式排序。

Last updated