Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
WITH
provides a way to write auxiliary statements for use in a larger query. These statements, which are often referred to as Common Table Expressions or CTEs, can be thought of as defining temporary tables that exist just for one query. Each auxiliary statement in a WITH
clause can be a SELECT
, INSERT
, UPDATE
, or DELETE
; and the WITH
clause itself is attached to a primary statement that can also be a SELECT
, INSERT
, UPDATE
, or DELETE
.
SELECT
in WITH
The basic value of SELECT
in WITH
is to break down complicated queries into simpler parts. An example is:
which displays per-product sales totals in only the top sales regions. The WITH
clause defines two auxiliary statements named regional_sales
and top_regions
, where the output of regional_sales
is used in top_regions
and the output of top_regions
is used in the primary SELECT
query. This example could have been written without WITH
, but we'd have needed two levels of nested sub-SELECT
s. It's a bit easier to follow this way.
The optional RECURSIVE
modifier changes WITH
from a mere syntactic convenience into a feature that accomplishes things not otherwise possible in standard SQL. Using RECURSIVE
, a WITH
query can refer to its own output. A very simple example is this query to sum the integers from 1 through 100:
The general form of a recursive WITH
query is always a non-recursive term, then UNION
(or UNION ALL
), then a recursive term, where only the recursive term can contain a reference to the query's own output. Such a query is executed as follows:
Recursive Query Evaluation
Evaluate the non-recursive term. For UNION
(but not UNION ALL
), discard duplicate rows. Include all remaining rows in the result of the recursive query, and also place them in a temporary working table.
So long as the working table is not empty, repeat these steps:
Evaluate the recursive term, substituting the current contents of the working table for the recursive self-reference. For UNION
(but not UNION ALL
), discard duplicate rows and rows that duplicate any previous result row. Include all remaining rows in the result of the recursive query, and also place them in a temporary intermediate table.
Replace the contents of the working table with the contents of the intermediate table, then empty the intermediate table.
Strictly speaking, this process is iteration not recursion, but RECURSIVE
is the terminology chosen by the SQL standards committee.
In the example above, the working table has just a single row in each step, and it takes on the values from 1 through 100 in successive steps. In the 100th step, there is no output because of the WHERE
clause, and so the query terminates.
Recursive queries are typically used to deal with hierarchical or tree-structured data. A useful example is this query to find all the direct and indirect sub-parts of a product, given only a table that shows immediate inclusions:
When working with recursive queries it is important to be sure that the recursive part of the query will eventually return no tuples, or else the query will loop indefinitely. Sometimes, using UNION
instead of UNION ALL
can accomplish this by discarding rows that duplicate previous output rows. However, often a cycle does not involve output rows that are completely duplicate: it may be necessary to check just one or a few fields to see if the same point has been reached before. The standard method for handling such situations is to compute an array of the already-visited values. For example, consider the following query that searches a table graph
using a link
field:
This query will loop if the link
relationships contain cycles. Because we require a “depth” output, just changing UNION ALL
to UNION
would not eliminate the looping. Instead we need to recognize whether we have reached the same row again while following a particular path of links. We add two columns path
and cycle
to the loop-prone query:
Aside from preventing cycles, the array value is often useful in its own right as representing the “path” taken to reach any particular row.
In the general case where more than one field needs to be checked to recognize a cycle, use an array of rows. For example, if we needed to compare fields f1
and f2
:
Omit the ROW()
syntax in the common case where only one field needs to be checked to recognize a cycle. This allows a simple array rather than a composite-type array to be used, gaining efficiency.
The recursive query evaluation algorithm produces its output in breadth-first search order. You can display the results in depth-first search order by making the outer query ORDER BY
a “path” column constructed in this way.
A helpful trick for testing queries when you are not certain if they might loop is to place a LIMIT
in the parent query. For example, this query would loop forever without the LIMIT
:
This works because PostgreSQL's implementation evaluates only as many rows of a WITH
query as are actually fetched by the parent query. Using this trick in production is not recommended, because other systems might work differently. Also, it usually won't work if you make the outer query sort the recursive query's results or join them to some other table, because in such cases the outer query will usually try to fetch all of the WITH
query's output anyway.
A useful property of WITH
queries is that they are normally evaluated only once per execution of the parent query, even if they are referred to more than once by the parent query or sibling WITH
queries. Thus, expensive calculations that are needed in multiple places can be placed within a WITH
query to avoid redundant work. Another possible application is to prevent unwanted multiple evaluations of functions with side-effects. However, the other side of this coin is that the optimizer is not able to push restrictions from the parent query down into a multiply-referenced WITH
query, since that might affect all uses of the WITH
query's output when it should affect only one. The multiply-referenced WITH
query will be evaluated as written, without suppression of rows that the parent query might discard afterwards. (But, as mentioned above, evaluation might stop early if the reference(s) to the query demand only a limited number of rows.)
但是,如果 WITH 查詢是非遞迴且不會在執行中變動的(即它是一個不包含 volatile 函數的 SELECT),則可以將其合併到父查詢之中,從而可以對兩個查詢等級進行聯合語法最佳化。預設情況下,如果父查詢僅引用一次 WITH 語句,而不是多次引用 WITH 一次查詢,則會觸發這個機制。您可以透過指定 MATERIALIZED 強制執行 WITH 查詢的單獨計算,或者透過指定 NOT MATERIALIZED 強制執行將其合併到父查詢中來覆蓋該查詢計畫。後面一種選擇可能會冒著重複計算 WITH 查詢的風險,但如果 WITH 查詢的每次使用只需要 WITH 查詢全部輸出的一小部分,那麼它仍然可以節省成本。
A simple example of these rules is
This WITH
query will be folded, producing the same execution plan as
In particular, if there's an index on key
, it will probably be used to fetch just the rows having key = 123
. On the other hand, in
the WITH
query will be materialized, producing a temporary copy of big_table
that is then joined with itself — without benefit of any index. This query will be executed much more efficiently if written as
so that the parent query's restrictions can be applied directly to scans of big_table
.
An example where NOT MATERIALIZED
could be undesirable is
Here, materialization of the WITH
query ensures that very_expensive_function
is evaluated only once per table row, not twice.
The examples above only show WITH
being used with SELECT
, but it can be attached in the same way to INSERT
, UPDATE
, or DELETE
. In each case it effectively provides temporary table(s) that can be referred to in the main command.
WITH
You can use data-modifying statements (INSERT
, UPDATE
, or DELETE
) in WITH
. This allows you to perform several different operations in the same query. An example is:
This query effectively moves rows from products
to products_log
. The DELETE
in WITH
deletes the specified rows from products
, returning their contents by means of its RETURNING
clause; and then the primary query reads that output and inserts it into products_log
.
A fine point of the above example is that the WITH
clause is attached to the INSERT
, not the sub-SELECT
within the INSERT
. This is necessary because data-modifying statements are only allowed in WITH
clauses that are attached to the top-level statement. However, normal WITH
visibility rules apply, so it is possible to refer to the WITH
statement's output from the sub-SELECT
.
Data-modifying statements in WITH
usually have RETURNING
clauses (see Section 6.4), as shown in the example above. It is the output of the RETURNING
clause, not the target table of the data-modifying statement, that forms the temporary table that can be referred to by the rest of the query. If a data-modifying statement in WITH
lacks a RETURNING
clause, then it forms no temporary table and cannot be referred to in the rest of the query. Such a statement will be executed nonetheless. A not-particularly-useful example is:
This example would remove all rows from tables foo
and bar
. The number of affected rows reported to the client would only include rows removed from bar
.
Recursive self-references in data-modifying statements are not allowed. In some cases it is possible to work around this limitation by referring to the output of a recursive WITH
, for example:
This query would remove all direct and indirect subparts of a product.
Data-modifying statements in WITH
are executed exactly once, and always to completion, independently of whether the primary query reads all (or indeed any) of their output. Notice that this is different from the rule for SELECT
in WITH
: as stated in the previous section, execution of a SELECT
is carried only as far as the primary query demands its output.
The sub-statements in WITH
are executed concurrently with each other and with the main query. Therefore, when using data-modifying statements in WITH
, the order in which the specified updates actually happen is unpredictable. All the statements are executed with the same snapshot (see Chapter 13), so they cannot “see” one another's effects on the target tables. This alleviates the effects of the unpredictability of the actual order of row updates, and means that RETURNING
data is the only way to communicate changes between different WITH
sub-statements and the main query. An example of this is that in
the outer SELECT
would return the original prices before the action of the UPDATE
, while in
the outer SELECT
would return the updated data.
Trying to update the same row twice in a single statement is not supported. Only one of the modifications takes place, but it is not easy (and sometimes not possible) to reliably predict which one. This also applies to deleting a row that was already updated in the same statement: only the update is performed. Therefore you should generally avoid trying to modify a single row twice in a single statement. In particular avoid writing WITH
sub-statements that could affect the same rows changed by the main statement or a sibling sub-statement. The effects of such a statement will not be predictable.
At present, any table used as the target of a data-modifying statement in WITH
must not have a conditional rule, nor an ALSO
rule, nor an INSTEAD
rule that expands to multiple statements.
前面的章節解釋了如何建立資料表,如何填入資料以及如何操作這些資料。現在我們是時候討論如何從資料庫中檢索資料了。
檢索過程或從資料庫檢索資料的命令稱之為查詢。在 SQL 中,SELECT 命令用於進行條件查詢。 SELECT 指令的一般語法是:
以下各節介紹了資料列表(select list),資料表和排序規則的詳細資訊。由於 WITH 查詢是高級功能,因此最後再介紹。
一種簡單的查詢形式如下:
假設有一個名稱為 table1 的資料表,該指令會將取出 table1 中的所有資料表和所有用戶定義的欄位。(檢索的方法取決於用戶端的應用程序,例如,psql 程序將在屏幕上顯示一個 ASCII-art 表格,而用戶端的程式函式庫將提供從查詢結果中提取單一值的功能。選擇資料列表定義「*」表示由資料表表示式所產生的所有欄位。篩選列表可以是可用欄位的子集或使用欄位進行計算。例如,如果 table1 具有名稱為 a,b 和 c(也許是其他)的欄位,則可以進行以下查詢:
(假設 b 和 c 是數字型別)。更多細節詳見 7.3 節。
FROM table1是一種簡單的資料表表示式:它只讀取一個資料表。一般來說,資料表表示式可以是一般的資料表,交叉查詢和子查詢的複雜結構。但是,你也可以完全省略資料表表示式,並使用 SELECT 指令作為計算機:
使用資料列表中的表達式產生變動的結果,是更為常用的方式。例如,你可以這樣呼叫一個函數:
一個 資料表表示式 計算出一個資料表。資料表表示式包含了一個可以選擇在後方跟隨WHERE
、GROUP BY
和HAVING
子句的FROM
子句。普遍的資料表表示式簡單地在磁碟上引用一個資料表,, 即聲稱的基底資料表(base table), 但更複雜的表示式可被用於以多種形式修改或組合基底資料表。
在資料表表示式中選擇性的WHERE
、GROUP BY
和HAVING
子句指定一個逐次變換執行在FROM
子句衍生的資料表上的管道。所有的這些轉換都會產生一個虛擬資料表,該資料表提供了被傳遞到選擇串列的資料列,以計算查詢的輸出資料列。
FROM
子句The FROM
子句從逗號分隔資料表參照串列中給出的一個或多個其他的資料表衍生一個資料表。
一個資料表參照能是一個表格名稱(也許綱要限定的),或一個衍生出的資料表,例如子查詢,JOIN
建構或這些的複雜組合。如果多個資料表參照被列在FROM
子句中, 這些資料表參照則表將被交叉聯接(cross-joined,即形成其資料列的笛卡爾積;請參見下文。)FROM
串列的結果是一個中間的虛擬表,該表可以受到WHERE
、GROUP BY
和HAVING
子句的轉換,並且最終是整個資料表表示式的結果。
當一個資料表參照命名一個表格繼承層次結構的父級資料表,資料表參照不只是產生該表格的列,還會產生其所有後代表格的列,除非關鍵字ONLY
在表格名稱之前。然而,該參照僅產生出現在已命名資料表中的欄位—子資料表中添加的任何欄位都將被忽略。
可以在表格名稱之後寫入*
來明確指定包含後代表格,而不是在表格名稱之前寫入ONLY
。因為搜索後代表格現在始終是默認行為,沒有真正的理由再使用此語法。但是,支持它是為了與舊版本的兼容性。
聯接的資料表(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
子句會在聯接之後被處理。這與內部聯接無關緊要,但對於外部聯接則很重要。
可以為資料表和復雜資料表參照給定一個臨時名稱來用在其餘查詢中參照衍生的資料表。這稱為 資料表別名(table alias) 。
要創建資料表別名,請編寫
或者是
關鍵字AS
是選擇性的。 alias
可以是任何標識符。
資料表別名的典型應用是將短標識符分配給長資料表名稱,以保持連接子句的可讀性。例如:
以當前查詢而言,別名成為表參照的新名稱 —不允許在查詢其他位置中使用原始名稱引用該表。因此,這是無效的:
資料表別名主要是為了表示法的方便,但是在將資料表聯接到自身時必須使用它們,例如:
此外,如果表參照是子查詢,則需要別名(詳見7.2.1.3節。)
括號被用於解決歧義。在以下範例中,第一條語句將別名b
分配給my_table
的第二個實例,但是第二條語句將別名分配給聯接結果:
資料表別名的另一種形式為資料表欄位以及資料表本身賦予臨時名稱:
如果指定的欄位別名少於實際表中包含的欄位,則不會重命名剩餘的欄位。此語法對於自聯接或子查詢特別有用。
當別名被應用到JOIN
子句的輸出時,別名將原始名稱隱藏在JOIN
中。例如:
是有效的SQL,但是:
是無效的;資料表別名a
在別名c
之外並不可見。
子查詢指定衍生資料表必須括號括起來必須為資料表分配別名(如7.2.1.2節。)例如:
這個例子相當於FROM table1 AS alias_name
。當子查詢涉及分組或彙總時會出現更有趣的無法簡化為普通聯接的情況。
子查詢也可以是VALUES
串列:
同樣,需要資料表別名。為VALUES
串列的欄位分配別名是選擇性的,但這是一種好的實踐。有關更多訊息,請參見7.7節。
資料表函數是產生一組資料列的函數,這些列由基本資料型別(標量(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
,因為它可以用於任何種類的查詢。實際的欄位集合必須被指定在調用的查詢以便讓解析器知道,舉例來說,*
應該擴展成什麼。
出現在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()
返回製造商生產的產品名稱,但是我們表中的某些製造商目前未生產任何產品,我們可以像這樣找出:
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
是必要的。但即使不需要,限定欄位名稱會增加清晰度。此範例顯示了外部查詢的欄位命名作用域如何延伸到其內部查詢中。
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
子句則同樣會發生。
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)
。
如果查詢包含任何窗函數(詳見 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
子句如果要確保結果以特定方式排序。
如前一節所述,SELECT 指令中的資料示表表示式透過各種可能地組合資料表、view、消除資料列、分組等來建構中介的虛擬資料表。這個資料表最終會被傳遞給資料列表的處理。資料列表確認中介資料表的哪些欄位是實際上要輸出的。
最簡單的選擇列表是*,它表示資料表表示式產生的所有欄位。否則,資料列表是逗號分隔的參數表示式列表(如第 4.2 節中所定義的)。例如,它可能是欄位名稱的列表:
欄位名稱 a、b 和 c 是 FROM 子句中資料表的欄位的實際名稱,或者是由第 7.2.1.2 節中所賦予它們的別名。資料列表中可用的命名空間與 WHERE 子句中的命名空間相同,除非是使用分組查詢,在這種情況下,它與 HAVING 子句中的相同。
如果多個資料表具有相同名稱的欄位,則還必須加上資料表的名稱,如下所示:
處理多個資料表時,查詢特定資料表的所有欄位也是可以的:
有關 table_name.* 表示法的更多信息,請參閱第 8.16.5 節。
如果在資料列表中使用任意值表示式,則概念上是它將新的虛擬欄位加到回傳的資料表中。參數表示式對每個結果資料列計算一次,將該資料列的值替換為任何欄位引用。但是資料列表中的表示式不必引用 FROM 子句的資料表表示式中的任何欄位;例如,它們可以是常數算術表示式。
資料列表中的項目可以被分配用於後續處理的名稱,例如在 ORDER BY 子句中使用或由用戶端應用程序顯示。 例如:
如果沒有使用 AS 指定輸出欄位的名稱,系統將分配一個預設的欄位名稱。對於簡單欄位的引用,就是引用欄位的名稱。對於函數呼叫,就是函數的名稱。對於複雜的表示式,系統將會產成一個通用的名稱。
AS 關鍵字是選用的,但前提是新的欄位名稱不為任何PostgreSQL 關鍵字(請參閱附錄C)。為避免與關鍵字意外撞名,你可以對欄位名稱使用雙引號。例如,VALUE 是一個關鍵字,所以就不能這樣使用:
但這樣就可以了:
為了防止未來可能增加的關鍵字,建議你習慣使用 AS 或總是在欄位名稱使用雙引號。
注意這裡輸出欄位的命名與 FROM 子句中的命名不同(參閱第 7.2.1.2 節)。可以重新命名相同的欄位兩次,但在資料列表中分配的名稱是將要回傳的名稱。
DISTINCT
在處理了資料列表之後,結果資料表可以選擇性地消除重複的資料列。 DISTINCT 關鍵字在 SELECT 之後直接寫入以指定這個動作:
(如果不是 DISTINCT,而是關鍵字 ALL,可用於指定保留所有資料列的預設行為。)
顯然,如果至少有一個欄位值不同,則兩個資料列就會被認為是不同的。 在這個比較中,空值(null)被認為是相等的。
或者,使用表示式可以指定資料列如何被認為是不同的:
這裡表示式是一個任意的運算表示式,對所有資料列進行求值運算。所有表示式相等的一組資料列被認為是重複的,並且只有該組的第一個資料列會被保留在輸出中。請注意,集合中的「第一行」是不可預知的,除非查詢按足夠的欄位進行排序,以保證進到 DISTINCT 過濾器的資料列是唯一排序。(在 ORDER BY 排序後才進行 DISTINCT ON 處理。)
DISTINCT ON 子句不是SQL標準的一部分,有時被認為是不好的樣式,因為其結果有潛在的不確定性。透過在 FROM 中智慧地使用 GROUP BY 和子查詢,可以避免這種結構,但這卻往往是最方便的選擇。
LIMIT 和 OFFSET 允許你只回傳由查詢生成的一部分資料列:
如果給了一個限制的數量,那麼只有那個數目的資料列會回傳(如果查詢本身產生較少的資料列,則可能會少一些)。LIMIT ALL 與省略 LIMIT 子句相同,也如同 LIMIT 的參數為 NULL。
OFFSET 指的是在開始回傳資料列之前跳過那麼多少資料列。OFFSET 0 與忽略 OFFSET 子句相同,就像使用 NULL 參數的 OFFSET 一樣。
如果同時出現 OFFSET 和 LIMIT,則在開始計算回傳的LIMIT 資料列之前,先跳過 OFFSET 數量的資料列。
使用 LIMIT 時,運用 ORDER BY 子句將結果資料列限制為唯一順序非常重要。否則,你會得到一個不可預知的查詢資料列的子集。你可能會查詢第十到第二十個資料列,但是第十到第二十個資料列是按什麼順序排列的?次序是未知的,除非你指定 ORDER BY。
查詢最佳化在產生查詢計劃時會將 LIMIT 考慮在內,所以根據你給的 LIMIT 和 OFFSET,你很可能會得到不同的計劃(產生不同的資料列順序)。因此,使用不同的 LIMIT / OFFSET 值來選擇查詢結果的不同子集將導致不一致的結果,除非使用 ORDER BY 強制執行可預測的結果排序。這不是一個錯誤;這是一種事實上的結果,即 SQL 不保證以任何特定順序傳遞查詢的結果,除非使用 ORDER BY 來約束順序。
由 OFFSET 子句跳過的資料列仍然需要在伺服器內計算。因此一個大的 OFFSET 可能是低效率的。
兩個查詢的結果可以使用集合操作聯、交集和差集來組合。其語法為:
query1 和 query2 是到目前為止討論過的任何查詢功能。集合操作也可以巢狀也可以連接,例如:
會如下方式執行:
UNION 將 query2 的結果有效率地附加到 query1 的結果中(但不能保證這是實際回傳資料列的次序)。此外,除非使用了UNION ALL,否則它將以與 DISTINCT相同的方式從結果中消除重複的資料列。
INTERSECT 返回 query1 的結果和 query2 的結果中所有共同的資料列。除非使用 INTERSECT ALL,否則會刪除重複的資料列。
EXCEPT 回傳 query1 的結果中但不包含在 query2 的結果中的所有資料列。(這有時被稱為兩個查詢之間的差集。)同樣地,除非使用 EXCEPT ALL,否則重複資料列將被刪除。
為了計算兩個查詢的聯集、交集或差集,兩個查詢必須是「union compatible」,這意味著它們回傳相同數量的欄位,相應的欄位具有相容的資料型別,如 10.5 節所述。
在查詢產生了一個輸出資料表(處理了資料列表之後)之後,可以對其資料列進行排序。如果未選擇排序,則資料列將以未指定的順序回傳。在這種情況下的實際順序將取決於資料掃描和交叉查詢類型以及磁碟上的順序,但不能依賴它。只有明確選擇了排序方式,才能保證特定的輸出排序。
以 ORDER BY 子句指定排序順序:
排序表示式可以在查詢的資料列表中有效的任何表示式。 一個例子是:
當指定多個表示式時,後面的表示式用於前面表示式都相同的資料進行排序。每個表示式可以跟隨一個選擇性的 ASC 或 DESC 關鍵字來設定排序方向為升冪或降冪。 ASC 排序是預設的選項。升冪首先放置較小的值,其中「較小」是根據「<」運算元定義的。 同樣,降冪也是由「>」運算元決定的。
NULLS FIRST 和 NULLS LAST 選項可用於確定在排序順序中是否出現空值出現在非空值之前或之後。預設情況下,空值排序大於任何非空值;也就是 NULLS FIRST 是 DESC 選項的預設值,否則就是 NULLS LAST。
請注意,排序選項是針對每個排序欄位獨立考慮的。例如 ORDER BY x, y DESC 是指 ORDER BY x ASC, y DESC,它與 ORDER BY x DESC, y DESC 不同。
排序表示式也可以是輸出欄位的欄位標籤或編號,如下所示:
兩者都按第一個輸出欄位排序。請注意,輸出欄位名稱必須獨立,也就是說,不能在表示式中使用 - 例如,這樣是不正確的:
這種限制是為了減少歧義。 即使 ORDER BY 項目是一個簡單的名字,可以匹配輸出欄位名稱或者資料表表示式中的一項,這仍然是會混淆的。在這種情況下請使用輸出欄位。如果您使用 AS 來重新命名輸出欄位以匹配其他資料表欄位的名稱,只會導致混淆。
可以將 ORDER BY 應用於 UNION、INTERSECT 或 EXCEPT 組合的結果,但在這種情況下,只允許按輸出欄位名稱或數字進行排序,而不能使用表示式進行排序。
VALUES 提供了一種產生「靜態資料表」的方法,可以在查詢中使用,而不必實際創建和寫入磁碟上的資料表。其語法是
每個括號內的表示式列表在資料表中生成一個資料列。列表必須具有相同數量的元素(即資料表中的欄位數),並且每個列表中的對應條目必須具有兼容的資料型別。 分配給結果中每個欄位的實際資料型別,使用與 UNION 相同的規則來給定(請參閱第 10.5 節)。
如下範例所示:
將回傳一個兩個欄位三個資料列的資料表。這實際上相當於:
預設情況下,PostgreSQL 會將名稱 column1、column2 等分配給 VALUES 資料表的欄位。欄位名稱並不是由 SQL 標準規定的,不同的資料庫系統會以不同的方式賦予,所以通常以資料表別名列表覆寫預設名稱會比較好,如下所示:
在語法上,VALUES 接在表示式列表之後被視為等同於:
並可以出現在任何一個 SELECT 可以使用的地方。例如,你可以將其用作為 UNION 的一部分,或者為其增加排序規則(ORDER BY、LIMIT 和 OFFSET)。在 INSERT 命令中,VALUES 最常來作為資料源,其次最常在子查詢。
關於更多訊息,請參閱 VALUES。