7.2. 資料表表示式
一個 資料表表示式 計算出一個資料表。資料表表示式包含了一個可以選擇在後方跟隨WHERE
、GROUP BY
和HAVING
子句的FROM
子句。普遍的資料表表示式簡單地在磁碟上引用一個資料表,, 即聲稱的基底資料表(base table), 但更複雜的表示式可被用於以多種形式修改或組合基底資料表。
在資料表表示式中選擇性的WHERE
、GROUP BY
和HAVING
子句指定一個逐次變換執行在FROM
子句衍生的資料表上的管道。所有的這些轉換都會產生一個虛擬資料表,該資料表提供了被傳遞到選擇串列的資料列,以計算查詢的輸出資料列。
7.2.1. FROM
子句
FROM
子句The FROM
子句從逗號分隔資料表參照串列中給出的一個或多個其他的資料表衍生一個資料表。
一個資料表參照能是一個表格名稱(也許綱要限定的),或一個衍生出的資料表,例如子查詢,JOIN
建構或這些的複雜組合。如果多個資料表參照被列在FROM
子句中, 這些資料表參照則表將被交叉聯接(cross-joined,即形成其資料列的笛卡爾積;請參見下文。)FROM
串列的結果是一個中間的虛擬表,該表可以受到WHERE
、GROUP BY
和HAVING
子句的轉換,並且最終是整個資料表表示式的結果。
當一個資料表參照命名一個表格繼承層次結構的父級資料表,資料表參照不只是產生該表格的列,還會產生其所有後代表格的列,除非關鍵字ONLY
在表格名稱之前。然而,該參照僅產生出現在已命名資料表中的欄位—子資料表中添加的任何欄位都將被忽略。
可以在表格名稱之後寫入*
來明確指定包含後代表格,而不是在表格名稱之前寫入ONLY
。因為搜索後代表格現在始終是默認行為,沒有真正的理由再使用此語法。但是,支持它是為了與舊版本的兼容性。
7.2.1.1. 聯接的資料表
聯接的資料表(joined table)是一個根據特定聯接型別的規則從兩個(真實的或被衍生的)其他資料表衍生的資料表。可以使用 Inner、outer、及cross-join 。聯接資料表的一般語法是
所有型別的聯接可以鏈結或嵌套在一起: T1
and T2
中的一個或兩個都可以被聯接資料表。可以在JOIN
子句周圍使用括號來控制聯接順序。在沒有括號的情況下,JOIN
子句從左到右嵌套。
聯接型別
Cross join
對於從 T1
and T2
的列的每種可能的組合(即笛卡爾積), 聯接的資料表將包含一個由 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
condition
不同於FROM
T1
,
T2
INNER JOIN
T3
ON
condition
因為 condition
可以第一種情況中但不能在第二個情況中參照 T1
。
Qualified joins
單詞 INNER
及 OUTER
在所有形式中都是可選的。INNER
是默認值; LEFT
、RIGHT
及 FULL
表示外部聯接。
在 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
表示式評估為真值,來自 T1
和 T2
的一對資料列匹配。
USING
子句是一種簡寫形式,可讓您在特定的情況充分利用,即在聯接兩端使用相同的名稱聯接欄位。它使用逗號分隔的共享欄位名稱串列並形成一個包括每個條件相等性比較的聯接條件。例如,將 T1
和 T2
與 USING (a, b)
進行聯接會產生聯接條件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
的行為類似於JOIN ... ON TRUE
,產生外積聯接(cross-product join。
注意
USING
對於在聯接關係中變更欄位是相當安全的因為只有列出的欄位被合併。NATURAL
的風險相當可觀,因為任何綱要(schema)變更為任一導致新的匹配欄位名稱出現的關係,也將會導致聯接合併該新的欄位。
綜合以上所述,假設我們有資料表t1
:
和資料表t2
:
然後對於各種聯接我們得到以下結果:
以ON
指定的聯接條件還可以包含與聯接不直接相關的條件。對於某些查詢這可以證明是有用的但需要小心地深思熟慮。例如:
請注意,將限制放置在WHERE
子句中會產生不同的結果:
這是因為限制放在 ON
子句會在聯接之前被處理,而限制放在 WHERE
子句會在聯接之後被處理。這與內部聯接無關緊要,但對於外部聯接則很重要。
7.2.1.2. 資料表和欄位別名
可以為資料表和復雜資料表參照給定一個臨時名稱來用在其餘查詢中參照衍生的資料表。這稱為 資料表別名(table alias) 。
要創建資料表別名,請編寫
或者是
關鍵字AS
是選擇性的。 alias
可以是任何標識符。
資料表別名的典型應用是將短標識符分配給長資料表名稱,以保持連接子句的可讀性。例如:
以當前查詢而言,別名成為表參照的新名稱 —不允許在查詢其他位置中使用原始名稱引用該表。因此,這是無效的:
資料表別名主要是為了表示法的方便,但是在將資料表聯接到自身時必須使用它們,例如:
此外,如果表參照是子查詢,則需要別名(詳見7.2.1.3節。)
括號被用於解決歧義。在以下範例中,第一條語句將別名b
分配給my_table
的第二個實例,但是第二條語句將別名分配給聯接結果:
資料表別名的另一種形式為資料表欄位以及資料表本身賦予臨時名稱:
如果指定的欄位別名少於實際表中包含的欄位,則不會重命名剩餘的欄位。此語法對於自聯接或子查詢特別有用。
當別名被應用到JOIN
子句的輸出時,別名將原始名稱隱藏在JOIN
中。例如:
是有效的SQL,但是:
是無效的;資料表別名a
在別名c
之外並不可見。
7.2.1.3. 子查詢
子查詢指定衍生資料表必須括號括起來必須為資料表分配別名(如7.2.1.2節。)例如:
這個例子相當於FROM table1 AS alias_name
。當子查詢涉及分組或彙總時會出現更有趣的無法簡化為普通聯接的情況。
子查詢也可以是VALUES
串列:
同樣,需要資料表別名。為VALUES
串列的欄位分配別名是選擇性的,但這是一種好的實踐。有關更多訊息,請參見7.7節。
7.2.1.4. 資料表函數
資料表函數是產生一組資料列的函數,這些列由基本資料型別(標量(scalar)型別)或複合數資料型別(資料表列)組成。在查詢的 FROM
子句中,它們像資料表、檢視表或子查詢一樣使用。資料表函數返回的欄位以資料表欄位、檢視表或子查詢相同的方式可以包含在SELECT
、JOIN
或WHERE
子句中。
資料表函數也可以使用ROWS FROM
語法進行組合,以並行欄位返回結果;在這種情況下結果列的數量是最大的函數結果,較小的結果將填充空值來匹配。
如果WITH ORDINALITY
子句被指定,一個額外的bigint
型別欄位将被添加到函數結果欄位。這個欄位從1開始為函數結果集合的列作編號(這是SQL標準語法UNNEST ... WITH ORDINALITY
的概括。)在默認情況下,序數欄位欄位被稱為ordinality
,但可以使用AS
子句分配不同的欄位名稱給它。
特別的資料表函數UNNEST
也許伴隨著任意數量的陣列參數被調用,並且他返回一個對應數量的欄位,就如同分別對每個參數調用UNNEST
(9.19節)並使用ROWS FROM
建構將其組合在一起。
如果沒有指定 table_alias
,該函數名稱被用作資料表名稱; 在ROWS FROM
建構的情況中使用第一個函數的名稱。
如果沒有提供欄位別名,則對於返回一個基礎資料型別的函數,該欄位名稱也與函數名稱相同。對於返回一個複合資料型別的函數,該結果欄位取得該型別個別屬性的名稱。
舉一些範例:
在一些情況中他對定義能根據它們的調用方式返回不同欄位集合的資料表函數很有用。為了要支持這情況,資料表函數可以被宣告為返回偽型別 record
。在查詢中使用此種函數時,在查詢本身中必須指定預期的資料列結構,以便讓系統知道如何解析和規劃查詢。這種語法看起來像是:
沒有使用ROWS FROM()
語法時,column_definition
串列替換原本能被附加到FROM
項目的欄位別名串列;在欄位定義中的名稱充當欄位別名。當使用ROWS FROM()
語法時,column_definition
串列能被分別附加到每個成員函數;或者如果只有一個成員函數且沒有WITH ORDINALITY
子句,能編寫_column_definition
_ 串列來代替ROWS FROM()
之後的欄位別名串列。
考慮以下範例:
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
的一個簡單範例是:
這不是特別有用,因為它與完全常規的結果完全相同
LATERAL
主要有用的時機是在運算資料列聯接而需要交叉參照後欄位的時候。典型的應用是提供一個參數值給會返回集合的函數。舉例來說,假如vertices(polygon)
返回多邊形的頂點集合,我們可以經由以下方式識別存儲在表中多邊形的近似頂點:
這個查詢也可以寫成
或者以其他幾種等效公式表示。(如前所述,關鍵字LATERAL
在此範例中是不必要的,但為了清楚起見而使用它。)
即使LATERAL
子查詢沒有產生資料列,通常特別便利將LEFT JOIN
添加到LATERAL
子查詢,使得來源資料列將出現在結果中。舉例來說,如果get_product_names()
返回製造商生產的產品名稱,但是我們表中的某些製造商目前未生產任何產品,我們可以像這樣找出:
7.2.2. WHERE
子句
WHERE
子句WHERE
子句的語法是
其中 search_condition
是任何返回型別boolean
值的值表示式(參見4.2節。)
在完成FROM
子句的處理之後,針對搜尋條件檢查衍生虛擬表的每一列。如果條件的結果為true,則資料列保留在輸出表中,否則(即結果為false或null) 被丟棄。搜尋條件通常參照在FROM
子句中生成的表中的至少一欄;這不是必須的,但反之WHERE
子句是相當毫無用處的。
注意
內部聯接的聯接條件可以寫入在 WHERE
子句中或JOIN
子句中。例如,這些資料表表示式等同於:
以及:
或也甚至:
使用其中哪一個主要是風格問題。FROM
子句 的JOIN
語法對其他SQL資料庫管理系統的可能不是可攜式的, 即使它處於SQL標準中。對於外部聯接來說別無選擇:他們必須在FROM
子句中完成。外部聯接的ON
或USING
子句不是等同於WHERE
條件,因為它導致列的添加(對於沒有匹配的輸入列)以及在最終結果中列的刪除。
以下是WHERE
子句的一些範例:
fdt
是在 FROM
子劇中衍生的資料表。不符合WHERE
子句搜尋條件的列從FDT
排除。請注意標量(scalar)子查詢作為值表示式的使用。就像任何其他查詢一樣,子查詢可以採用複雜的資料表表示式。還要注意在子查詢中fdt
是如何被參照的。僅當c1
也是子查詢衍生輸入表中的欄位名稱時,限定(qualifying)c1
為fdt.c1
是必要的。但即使不需要,限定欄位名稱會增加清晰度。此範例顯示了外部查詢的欄位命名作用域如何延伸到其內部查詢中。
7.2.3. GROUP BY
及 HAVING
子句
GROUP BY
及 HAVING
子句在經過WHERE
篩選器後,衍生的輸入表可能會遭受到使用GROUP BY
子句進行分組,而使用HAVING
子句進行群組資料列的排除。
GROUP BY
子句用於將資料列分組在一起,這些資料列在條列出的所有資料列中具有相同的值。條列出的的欄位順序無關緊要。其效果是將具有共同值的資料列集合在群組中組合到一個群組資料列來表示所有資料列。這樣做是為了排除輸出中的的冗餘且/或運算應用於這些群組的彙總。例如:
在第二個查詢中,我們不能寫成 SELECT * FROM test1 GROUP BY x
,因為對於可能與每個群組相關聯的欄位y
來說沒有單一值。可以在選擇串列中參照被分組的列,因為它們在每個群組中具有單一值。
通常來說,如果將資料表被分組,則除了彙總表示式之外不能參照沒有在GROUP BY
中條列出的欄位。彙總表示式的範例是:
在這裡sum
是一個在整個群組之上運算一個單一值的彙總函數。有關彙總函數的更多訊息,請參見9.21節。
Tip
沒有彙總表示式的分組有效地運算一個欄位中的相異值集合。這也可以使用DISTINCT
子句來實現(詳見7.3.3節。)
這是另一個範例,它計算每個產品的總銷售額(而不是所有產品的總銷售):
在這個範例,欄位product_id
、p.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
子句,從結果來排除群組。語法如下:
在HAVING
子句中的表示式能引用已分組表示式及未分組表示式兩者(其必然涉及彙總函數。)
舉例:
再來一個更真實的範例:
在上方的範例中,WHERE
子句正在經由一個未被分組的欄位選擇資料列(在過去四周內,該表示式僅適用於銷售額),儘管 HAVING
子句限制輸出為總銷售額超過5000的群組。 請注意,彙總表示式在查詢的所有部分中不一定需要相同。
如果查詢包含彙總函數調用但沒有 GROUP BY
子句,分組仍然會發生:結果是單個群組資料列(或者可能沒有資料列,如果經由HAVING
排除該單一資料列。)即使沒有任何彙總函數調用或 GROUP BY
子句,如果包含HAVING
子句則同樣會發生。
7.2.4. GROUPING SETS
、CUBE
及 ROLLUP
GROUPING SETS
、CUBE
及 ROLLUP
更多比上方描述較複雜的分組操作可以使用 分組集合(grouping sets) 的概念。經由FROM
及WHERE
子句選擇的資料被每一個特定的分組集合分別地分組,對於每一個群組運算的彙總就如同簡單的GROUP BY
子句,而後返回其結果。舉例來說:
每一個GROUPING SETS
的子串列可以指定零個或多個欄位或表示式並且以它直接在GROUP BY
子句中相同的方式來解釋。 一個空的分組集合意味著所有資料列被彙總到單一的群組(即使沒有輸入資料列被呈現也會輸出),如同上方所述對於沒有GROUP BY
子句的彙總函數之情況。
分組欄位或表示式的參照對於未出現在這些欄位中的分組集合來說會在結果列中由null值替換。要區分源自哪邊的分組特定輸出列,詳見表 9.59。
為了指定兩個分組集合的常見型別提供了一個簡寫表示法。該形式的子句為
代表了給定的表達式串列和該串列的所有前綴,包括空串列;因此它相當於
這通常用於分析階層式資料:例如,部門,分部和公司的總薪資。
另一形式的子句為
表示給定的串列和所有可能的子集合(即power set。)因此
相當於
CUBE
或ROLLUP
子句各自的元素也許是各自的表示式,或元素在括號中的子串列。在後一種情況下,為了生成各自的分組集合的意圖,該子串列被視為單個單元。例如:
相當於
以及
相當於
CUBE
或ROLLUP
建構能被直接用在GROUP BY
子句中,或被嵌套在GROUPING SETS
子句內。如果GROUPING SETS
子句被嵌套在另一個內,效果與內部子句內的所有元素被直接寫入外部子句中時相同。
如果多個的分組項目被指定在單一GROUP BY
子句,分組集合的最終串列會是各自項目的外積。例如:
相當於
注意
建構 (a, b)
一般來說在表示式中被辨識為一個資料列建構子(row constructor)。在GROUP BY
子句內,這不適用於表示式的頂層,並且 (a, b)
是被解析為一個如上方所述的表示式串列。如果為某些理由你 需要 一個資料列建構子在分組表示式,請使用ROW(a, b)
。
7.2.5. 窗函數處理
如果查詢包含任何窗函數(詳見 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
子句如果要確保結果以特定方式排序。