PostgreSQL 表表達(dá)式

2021-08-26 11:41 更新
7.2.1. FROM子句
7.2.2. WHERE子句
7.2.3. GROUP BYHAVING子句
7.2.4. GROUPING SETSCUBEROLLUP
7.2.5. 窗口函數(shù)處理

表表達(dá)式計算一個表。該表表達(dá)式包含一個FROM子句,該子句后面可以根據(jù)需要選用WHERE、GROUP BYHAVING子句。最簡單的表表達(dá)式只是引用磁盤上的一個表,一個所謂的基本表,但是我們可以用更復(fù)雜的表表達(dá)式以多種方法修改或組合基本表。

表表達(dá)式里可選的WHERE、GROUP BYHAVING子句指定一系列對源自FROM子句的表的轉(zhuǎn)換操作。所有這些轉(zhuǎn)換最后生成一個虛擬表,它提供行傳遞給選擇列表計算查詢的輸出行。

7.2.1. FROM子句

FROM子句從一個用逗號分隔的表引用列表中的一個或更多個其它表中生成一個表。

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

表引用可以是一個表名字(可能有模式限定)或者是一個生成的表, 例如子查詢、一個JOIN結(jié)構(gòu)或者這些東西的復(fù)雜組合。如果在FROM子句中引用了多于一個表, 那么它們被交叉連接(即構(gòu)造它們的行的笛卡爾積,見下文)。FROM列表的結(jié)果是一個中間的虛擬表,該表可以進(jìn)行由WHERE、GROUP BYHAVING子句指定的轉(zhuǎn)換,并最后生成全局的表表達(dá)式結(jié)果。

如果一個表引用是一個簡單的表名字并且它是表繼承層次中的父表,那么該表引用將產(chǎn)生該表和它的后代表中的行,除非你在該表名字前面放上ONLY關(guān)鍵字。但是,這種引用只會產(chǎn)生出現(xiàn)在該命名表中的列 — 在子表中增加的列都會被忽略。

除了在表名前寫ONLY,你可以在表名后面寫上*來顯式地指定要包括所有的后代表。沒有實際的理由再繼續(xù)使用這種語法,因為搜索后代表現(xiàn)在總是默認(rèn)行為。不過,為了保持與舊版本的兼容性,仍然支持這種語法。

7.2.1.1. 連接表

一個連接表是根據(jù)特定的連接類型的規(guī)則從兩個其它表(真實表或生成表)中派生的表。目前支持內(nèi)連接、外連接和交叉連接。一個連接表的一般語法是:

T1 join_type T2 [ join_condition ]

所有類型的連接都可以被鏈在一起或者嵌套:T1T2都可以是連接表。在JOIN子句周圍可以使用圓括號來控制連接順序。如果不使用圓括號,JOIN子句會從左至右嵌套。

連接類型

交叉連接
T1 CROSS JOIN T2

對來自于T1T2的行的每一種可能的組合(即笛卡爾積),連接表將包含這樣一行:它由所有T1里面的列后面跟著所有T2里面的列構(gòu)成。如果兩個表分別有 N 和 M 行,連接表將有 N * M 行。

FROM T1 CROSS JOIN T2等效于FROM T1 INNER JOIN T2 ON TRUE(見下文)。它也等效于FROM T1,T2。

注意

當(dāng)多于兩個表出現(xiàn)時,后一種等效并不嚴(yán)格成立,因為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子句中指定, 或者用關(guān)鍵字NATURAL隱含地指定。連接條件決定來自兩個源表中的哪些行是匹配的,這些我們將在后文詳細(xì)解釋。

可能的條件連接類型是:

INNER JOIN

對于 T1 的每一行 R1,生成的連接表都有一行對應(yīng) T2 中的每一個滿足和 R1 的連接條件的行。

LEFT OUTER JOIN

首先,執(zhí)行一次內(nèi)連接。然后,為 T1 中每一個無法在連接條件上匹配 T2 里任何一行的行返回一個連接行,該連接行中 T2 的列用空值補齊。因此,生成的連接表里為來自 T1 的每一行都至少包含一行。

RIGHT OUTER JOIN

首先,執(zhí)行一次內(nèi)連接。然后,為 T2 中每一個無法在連接條件上匹配 T1 里任何一行的行返回一個連接行,該連接行中 T1 的列用空值補齊。因此,生成的連接表里為來自 T2 的每一行都至少包含一行。

FULL OUTER JOIN

首先,執(zhí)行一次內(nèi)連接。然后,為 T1 中每一個無法在連接條件上匹配 T2 里任何一行的行返回一個連接行,該連接行中 T2 的列用空值補齊。同樣,為 T2 中每一個無法在連接條件上匹配 T1 里任何一行的行返回一個連接行,該連接行中 T1 的列用空值補齊。

ON子句是最常見的連接條件的形式:它接收一個和WHERE子句里用的一樣的布爾值表達(dá)式。 如果兩個分別來自T1T2的行在ON表達(dá)式上運算的結(jié)果為真,那么它們就算是匹配的行。

USING是個縮寫符號,它允許你利用特殊的情況:連接的兩端都具有相同的連接列名。它接受共享列名的一個逗號分隔列表,并且為其中每一個共享列構(gòu)造一個包含等值比較的連接條件。例如用USING (a, b)連接T1T2會產(chǎn)生連接條件ON T1.a = T2.a AND T1.b = T2.b

更進(jìn)一步,JOIN USING的輸出會廢除冗余列:不需要把匹配上的列都打印出來,因為它們必須具有相等的值。不過JOIN ON會先產(chǎn)生來自T1的所有列,后面跟上所有來自T2的列;而JOIN USING會先為列出的每一個列對產(chǎn)生一個輸出列,然后先跟上來自T1的剩余列,最后跟上來自T2的剩余列。

最后,NATURALUSING的縮寫形式:它形成一個USING列表, 該列表由那些在兩個表里都出現(xiàn)了的列名組成。和USING一樣,這些列只在輸出表里出現(xiàn)一次。如果不存在公共列,NATURAL JOIN的行為將和JOIN ... ON TRUE一樣產(chǎn)生交叉集連接。

注意

USING對于連接關(guān)系中的列改變是相當(dāng)安全的,因為只有被列出的列會被組合成連接條件。NATURAL的風(fēng)險更大,因為如果其中一個關(guān)系的模式改變會導(dǎo)致出現(xiàn)一個新的匹配列名,就會導(dǎo)致連接將新列也組合成連接條件。

為了解釋這些問題,假設(shè)我們有一個表t1

 num | name
-----+------
   1 | a
   2 | b
   3 | c

t2

 num | value
-----+-------
   1 | xxx
   3 | yyy
   5 | zzz

然后我們用不同的連接方式可以獲得各種結(jié)果:

=> 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指定的連接條件也可以包含與連接不直接相關(guān)的條件。這種功能可能對某些查詢很有用,但是需要我們仔細(xì)想清楚。例如:

=> 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子句中會產(chǎn)生不同的結(jié)果:

=> 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子句中的一個約束是在連接之后被處理。這對內(nèi)連接沒有關(guān)系,但是對于外連接會帶來麻煩。

7.2.1.2. 表和列別名

你可以給一個表或復(fù)雜的表引用指定一個臨時的名字,用于剩下的查詢中引用那些派生的表。這被叫做表別名。

要創(chuàng)建一個表別名,我們可以寫:

FROM table_reference AS alias

或者

FROM table_reference alias

AS關(guān)鍵字是可選的。別名可以是任意標(biāo)識符。

表別名的典型應(yīng)用是給長表名賦予比較短的標(biāo)識符, 好讓連接子句更易讀。例如:

SELECT * FROM some_very_long_table_name s JOIN another_fairly_long_name a ON s.id = a.num;

到這里,別名成為當(dāng)前查詢的表引用的新名稱 — 我們不再能夠用該表最初的名字引用它了。因此,下面的用法是不合法的:

SELECT * FROM my_table AS m WHERE my_table.a > 5;    -- 錯誤

表別名主要用于簡化符號,但是當(dāng)把一個表連接到它自身時必須使用別名,例如:

SELECT * FROM people AS mother JOIN people AS child ON mother.id = child.mother_id;

此外,如果一個表引用是一個子查詢,則必須要使用一個別名(見第 7.2.1.3子查詢 )。

圓括弧用于解決歧義。在下面的例子中,第一個語句將把別名b賦給my_table的第二個實例,但是第二個語句把別名賦給連接的結(jié)果:

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列表中的列分配別名是可選的,但是選擇這樣做是一個好習(xí)慣。更多信息可參見第 7.7 節(jié)

7.2.1.4. 表函數(shù)

表函數(shù)是那些生成一個行集合的函數(shù),這個集合可以是由基本數(shù)據(jù)類型(標(biāo)量類型)組成, 也可以是由復(fù)合數(shù)據(jù)類型(表行)組成。它們的用法類似一個表、視圖或者在查詢的FROM子句里的子查詢。表函數(shù)返回的列可以像一個表列、視圖或者子查詢那樣被包含在SELECT、JOINWHERE子句里。

也可以使用ROWS FROM語法將平行列返回的結(jié)果組合成表函數(shù); 這種情況下結(jié)果行的數(shù)量是最大一個函數(shù)結(jié)果的數(shù)量,較小的結(jié)果會用空值來填充。

function_call [WITH ORDINALITY] [[AS] table_alias [(column_alias [, ... ])]]
ROWS FROM( function_call [, ... ] ) [WITH ORDINALITY] [[AS] table_alias [(column_alias [, ... ])]]

如果指定了WITH ORDINALITY子句,一個額外的 bigint類型的列將會被增加到函數(shù)的結(jié)果列中。這個列對 函數(shù)結(jié)果集的行進(jìn)行編號,編號從 1 開始(這是對 SQL 標(biāo)準(zhǔn)語法 UNNEST ... WITH ORDINALITY的一般化)。默認(rèn)情 況下,序數(shù)列被稱為ordinality,但也可以通過使用一個 AS子句給它分配一個不同的列名。

調(diào)用特殊的表函數(shù)UNNEST可以使用任意數(shù)量的數(shù)組參數(shù), 它會返回對應(yīng)的列數(shù),就好像在每一個參數(shù)上單獨調(diào)用 UNNEST第 9.19 節(jié))并且使用 ROWS FROM結(jié)構(gòu)把它們組合起來。

UNNEST( array_expression [, ... ] ) [WITH ORDINALITY] [[AS] table_alias [(column_alias [, ... ])]]

如果沒有指定table_alias,該函數(shù)名將被用作 表名。在ROWS FROM()結(jié)構(gòu)的情況中,會使用第一個函數(shù)名。

如果沒有提供列的別名,那么對于一個返回基數(shù)據(jù)類型的函數(shù),列名也與該函數(shù) 名相同。對于一個返回組合類型的函數(shù),結(jié)果列會從該類型的屬性得到名稱。

例子:

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;

參數(shù)的 有時侯,定義一個能夠根據(jù)它們被調(diào)用方式返回不同列集合的表函數(shù)是很有用的。 為了支持這些,表函數(shù)可以被聲明為返回沒有OUT參數(shù)的偽類型record。 如果在查詢里使用這樣的函數(shù),那么我們必須在查詢中指定所預(yù)期的行結(jié)構(gòu),這樣系統(tǒng)才知道如何分析和規(guī)劃該查詢。 這種語法是這樣的:

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()語法時, 可以為每一個成員函數(shù)單獨附著一個 column_definition列表;或者在只有一個成員 函數(shù)并且沒有WITH ORDINALITY子句的情況下,可以在 ROWS FROM()后面寫一個 column_definition列表來取代一個列別名列表。

考慮下面的例子:

SELECT *
    FROM dblink('dbname=mydb', 'SELECT proname, prosrc FROM pg_proc')
      AS t1(proname name, prosrc text)
    WHERE proname LIKE 'bytea%';

dblink函數(shù)(dblink模塊的一部分)執(zhí)行一個遠(yuǎn)程的查詢。它被聲明為返回record,因為它可能會被用于任何類型的查詢。 實際的列集必須在調(diào)用它的查詢中指定,這樣分析器才知道類似*這樣的東西應(yīng)該擴展成什么樣子。

此示例使用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

它將兩個函數(shù)連接到一個FROM目標(biāo)中。 json_to_recordset()被指示返回兩列,第一個integer 和第二個text。generate_series()的結(jié)果直接使用。 ORDER BY子句將列值排序為整數(shù)。

7.2.1.5. LATERAL子查詢

可以在出現(xiàn)于FROM中的子查詢前放置關(guān)鍵詞LATERAL。這允許它們引用前面的FROM項提供的列(如果沒有LATERAL,每一個子查詢將被獨立計算,并且因此不能被其他FROM項交叉引用)。

出現(xiàn)在FROM中的表函數(shù)的前面也可以被放上關(guān)鍵詞LATERAL,但對于函數(shù)該關(guān)鍵詞是可選的,在任何情況下函數(shù)的參數(shù)都可以包含對前面的FROM項提供的列的引用。

一個LATERAL項可以出現(xiàn)在FROM列表頂層,或者出現(xiàn)在一個JOIN樹中。在后一種情況下,如果它出現(xiàn)在JOIN的右部,那么它也可以引用 在JOIN左部的任何項。

如果一個FROM項包含LATERAL交叉引用,計算過程如下:對于提供交叉引用列的FROM項的每一行,或者多個提供這些列的多個FROM項的行集合,LATERAL項將被使用該行或者行集中的列值進(jìn)行計算。得到的結(jié)果行將和它們被計算出來的行進(jìn)行正常的連接。對于來自這些列的源表的每一行或行集,該過程將重復(fù)。

LATERAL的一個簡單例子:

SELECT * FROM foo, LATERAL (SELECT * FROM bar WHERE bar.id = foo.bar_id) ss;

這不是非常有用,因為它和一種更簡單的形式得到的結(jié)果完全一樣:

SELECT * FROM foo, bar WHERE bar.id = foo.bar_id;

在必須要使用交叉引用列來計算那些即將要被連接的行時,LATERAL是最有用的。一種常用的應(yīng)用是為一個返回集合的函數(shù)提供一個參數(shù)值。例如,假設(shè)vertices(polygon)返回一個多邊形的頂點集合,我們可以這樣標(biāo)識存儲在一個表中的多邊形中靠近的頂點:

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關(guān)鍵詞在這個例子中并不是必不可少的,但是我們在這里使用它是為了使表述更清晰)。

有時候也會很特別地把LEFT JOIN放在一個LATERAL子查詢的前面,這樣即使LATERAL子查詢對源行不產(chǎn)生行,源行也會出現(xiàn)在結(jié)果中。例如,如果get_product_names()返回一個制造商制造的產(chǎn)品的名字,但是某些制造商在我們的表中目前沒有制造產(chǎn)品,我們可以找出哪些制造商是這樣:

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類型值的值表達(dá)式(參閱第 4.2 節(jié))。

在完成對FROM子句的處理之后,生成的虛擬表的每一行都會對根據(jù)搜索條件進(jìn)行檢查。 如果該條件的結(jié)果是真,那么該行被保留在輸出表中;否則(也就是說,如果結(jié)果是假或空)就把它拋棄。搜索條件通常至少要引用一些在FROM子句里生成的列;雖然這不是必須的,但如果不引用這些列,那么WHERE子句就沒什么用了。

注意

內(nèi)連接的連接條件既可以寫在WHERE子句也可以寫在JOIN子句里。例如,這些表表達(dá)式是等效的:

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

你想用哪個只是一個風(fēng)格問題。FROM子句里的JOIN語法可能不那么容易移植到其它SQL數(shù)據(jù)庫管理系統(tǒng)中。 對于外部連接而言沒有選擇:它們必須在FROM子句中完成。 外部連接的ONUSING子句等于WHERE條件,因為它導(dǎo)致最終結(jié)果中行的增加(對那些不匹配的輸入行)和減少。

這里是一些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中刪除。請注意我們把標(biāo)量子查詢當(dāng)做一個值表達(dá)式來用。 和任何其它查詢一樣,子查詢里可以使用復(fù)雜的表表達(dá)式。同時還請注意fdt在子查詢中也被引用。只有在c1也是作為子查詢輸入表的生成表的列時,才必須把c1限定成fdt.c1。但限定列名字可以增加語句的清晰度,即使有時候不是必須的。這個例子展示了一個外層查詢的列名范圍如何擴展到它的內(nèi)層查詢。

7.2.3. GROUP BYHAVING子句

在通過了WHERE過濾器之后,生成的輸入表可以使用GROUP BY子句進(jìn)行分組,然后用HAVING子句刪除一些分組行。

SELECT select_list
    FROM ...
    [WHERE ...]
    GROUP BY grouping_column_reference [, grouping_column_reference]...

GROUP BY子句被用來把表中在所列出的列上具有相同值的行分組在一起。 這些列的列出順序并沒有什么關(guān)系。其效果是把每組具有相同值的行組合為一個組行,它代表該組里的所有行。 這樣就可以刪除輸出里的重復(fù)和/或計算應(yīng)用于這些組的聚集。例如:

=> 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里沒有哪個值可以和每個組相關(guān)聯(lián)起來。被分組的列可以在選擇列表中引用是因為它們在每個組都有單一的值。

通常,如果一個表被分了組,那么沒有在GROUP BY中列出的列都不能被引用,除非在聚集表達(dá)式中被引用。 一個用聚集表達(dá)式的例子是:

=> SELECT x, sum(y) FROM test1 GROUP BY x;
 x | sum
---+-----
 a |   4
 b |   5
 c |   2
(3 rows)

這里的sum是一個聚集函數(shù),它在整個組上計算出一個單一值。有關(guān)可用的聚集函數(shù)的更多信息可以在第 9.21 節(jié)。

提示

沒有聚集表達(dá)式的分組實際上計算了一個列中可區(qū)分值的集合。我們也可以用DISTINCT子句實現(xiàn)(參閱第 7.3.3 節(jié))。

這里是另外一個例子:它計算每種產(chǎn)品的總銷售額(而不是所有產(chǎn)品的總銷售額):

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.namep.price必須在GROUP BY子句里, 因為它們都在查詢的選擇列表里被引用到(但見下文)。列s.units不必在GROUP BY列表里,因為它只是在一個聚集表達(dá)式(sum(...))里使用,它代表一組產(chǎn)品的銷售額。對于每種產(chǎn)品,這個查詢都返回一個該產(chǎn)品的所有銷售額的總和行。

如果產(chǎn)品表被建立起來,例如product_id是主鍵,那么在上面的例子中用product_id來分組就夠了,因為名稱和價格都是函數(shù)依賴于產(chǎn)品ID,并且關(guān)于為每個產(chǎn)品ID分組返回哪個名稱和價格值就不會有歧義。

在嚴(yán)格的 SQL 里,GROUP BY只能對源表的列進(jìn)行分組,但PostgreSQL把這個擴展為也允許GROUP BY去根據(jù)選擇列表中的列分組。也允許對值表達(dá)式進(jìn)行分組,而不僅是簡單的列名。

如果一個表已經(jīng)用GROUP BY子句分了組,然后你又只對其中的某些組感興趣, 那么就可以用HAVING子句,它很象WHERE子句,用于從結(jié)果中刪除一些組。其語法是:

SELECT select_list FROM ... [WHERE ...] GROUP BY ... HAVING boolean_expression

HAVING子句中的表達(dá)式可以引用分組的表達(dá)式和未分組的表達(dá)式(后者必須涉及一個聚集函數(shù))。

例子:

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

再次,一個更現(xiàn)實的例子:

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子句用那些非分組的列選擇數(shù)據(jù)行(表達(dá)式只是對那些最近四周發(fā)生的銷售為真)。 而HAVING子句限制輸出為總銷售收入超過 5000 的組。請注意聚集表達(dá)式不需要在查詢中的所有地方都一樣。

如果一個查詢包含聚集函數(shù)調(diào)用,但是沒有GROUP BY子句,分組仍然會發(fā)生:結(jié)果是一個單一行(或者根本就沒有行,如果該單一行被HAVING所消除)。它包含一個HAVING子句時也是這樣,即使沒有任何聚集函數(shù)調(diào)用或者GROUP BY子句。

7.2.4. GROUPING SETSCUBEROLLUP

使用分組集的概念可以實現(xiàn)比上述更加復(fù)雜的分組操作。由 FROMWHERE子句選出的數(shù)據(jù)被按照每一個指定 的分組集單獨分組,按照簡單GROUP BY子句對每一個分組計算 聚集,然后返回結(jié)果。例如:

=> 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的每一個子列表可以指定一個或者多個列或者表達(dá)式, 它們將按照直接出現(xiàn)在GROUP BY子句中同樣的方式被解釋。一個空的 分組集表示所有的行都要被聚集到一個單一分組(即使沒有輸入行存在也會被輸出) 中,這就像前面所說的沒有GROUP BY子句的聚集函數(shù)的情況一樣。

對于分組列或表達(dá)式?jīng)]有出現(xiàn)在其中的分組集的結(jié)果行,對分組列或表達(dá)式的引用會 被空值所替代。要區(qū)分一個特定的輸出行來自于哪個分組,請見 表 9.59分組操作。

PostgreSQL 中提供了一種簡化方法來指定兩種常用類型的分組集。下面形式的子句

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

表示給定的表達(dá)式列表及其所有前綴(包括空列表),因此它等效于

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

這通常被用來分析歷史數(shù)據(jù),例如按部門、區(qū)和公司范圍計算的總薪水。

下面形式的子句

CUBE ( e1, e2, ... )

表示給定的列表及其可能的子集(即冪集)。因此

CUBE ( a, b, c )

等效于

GROUPING SETS (
    ( a, b, c ),
    ( a, b    ),
    ( a,    c ),
    ( a       ),
    (    b, c ),
    (    b    ),
    (       c ),
    (         )
)

CUBEROLLUP子句中的元素可以是表達(dá)式或者 圓括號中的元素子列表。在后一種情況中,對于生成分組集的目的來說,子列 表被當(dāng)做單一單元來對待。例如:

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子句被嵌套在另一個同類子句中, 效果和把內(nèi)層子句的所有元素直接寫在外層子句中一樣。

如果在一個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)
)

注意

在表達(dá)式中,結(jié)構(gòu)(a, b)通常被識別為一個 a 行構(gòu)造器。在 GROUP BY子句中,這不會在表達(dá)式的頂層應(yīng)用,并且 (a, b)會按照上面所說的被解析為一個表達(dá)式的列表。如果出于 某種原因你在分組表達(dá)式中需要一個行構(gòu)造器,請使用 ROW(a, b)。

7.2.5. 窗口函數(shù)處理

如果查詢包含任何窗口函數(shù)(見第 3.5 節(jié)、第 9.22 節(jié)第 4.2.8 節(jié)),這些函數(shù)將在任何分組、聚集和HAVING過濾被執(zhí)行之后被計算。也就是說如果查詢使用了任何聚集、GROUP BYHAVING,則窗口函數(shù)看到的行是分組行而不是來自于FROM/WHERE的原始表行。

當(dāng)多個窗口函數(shù)被使用,所有在窗口定義中有句法上等效的PARTITION BYORDER BY子句的窗口函數(shù)被保證在數(shù)據(jù)上的同一趟掃描中計算。因此它們將會看到相同的排序順序,即使ORDER BY沒有唯一地決定一個順序。但是,對于具有不同PARTITION BYORDER BY定義的函數(shù)的計算沒有這種保證(在這種情況中,在多個窗口函數(shù)計算之間通常要求一個排序步驟,并且并不保證保留行的順序,即使它的ORDER BY把這些行視為等效的)。

目前,窗口函數(shù)總是要求排序好的數(shù)據(jù),并且這樣查詢的輸出總是被根據(jù)窗口函數(shù)的PARTITION BY/ORDER BY子句的一個或者另一個排序。但是,我們不推薦依賴于此。如果你希望確保結(jié)果以特定的方式排序,請顯式使用頂層的ORDER BY子句。


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號