| PostgreSQL 7.4 文檔 | ||||
|---|---|---|---|---|
| Prev | Fast Backward | Chapter 7. 查詢 | Fast Forward | Next |
表表達式計算一個表。 該表表達式包含一個FROM子句,該子句可以根據需要選用WHERE,GROUP BY, 和HAVING 子句。大部分的表表達式只是指向磁盤上的一個表,一個所謂的基本表,但是我們可以用更復雜的表表達式以各種方法修改或組合基本表。
表表達式裡的WHERE,GROUP BY,和 HAVING 子句聲明一系列對源自 FROM 子句的表的轉換操作。所有這些轉換最後生成一個虛擬表,提供傳遞給選擇列表計算查詢輸出行的數據行。
FROM子句從一個用逗號分隔的表引用列表中的一個或更多個其它表中生成一個表。
FROM table_reference [, table_reference [, ...]]表引用可以是一個表名字(可能有模式修飾)或者是一個生成的表, 比如子查詢,一個表連接,或者這些東西的復雜組合。如果在FROM子句中列出了多于一個表, 那麼它們被 cross join (見下文)形成一個派生表,該表可以進行 WHERE,GROUP BY 和 HAVING 子句的轉換處理,並最後生成所有表表達式的結果。
如果一個表引用是一個簡單的表名字並且它是表繼承級別中的超級表, 那麼該表的行包括所有它的後代子表的行,除非你在該表名字前面加ONLY關鍵字。 這樣的話,這個引用就只生成出現在命名表中的列 --- 任何在子表中追加的列都會被忽略。
一個連接表是根據特定的連接類型的規則從兩個其它表(真實表或生成表)中派生的表。 我們支持內連接,外連接和交叉連接類型。
連接類型
T1 CROSS JOIN T2
對每個從 T1 和 T2 來的行的組合, 生成的表將包含這樣一行:它包含所有 T1 裡面的字段後面跟著所有 T2 裡面的字段。 如果兩表分別有 N 和 M 行,連接成的表將有 N * M 行。
FROM T1 CROSS JOIN T2 等效于 FROM T1, T2。 它還等效于 FROM T1 INNER JOIN T2 ON TRUE(見下文)。
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 T2INNER 和 OUTER 對所有連接(join) 類型都是可選的。 INNER 是缺省;LEFT,RIGHT,和 FULL 隱含外連接。
連接條件在ON或USING子句裡聲明, 或者用關鍵字NATURAL隱含地聲明。連接條件判斷來自兩個源表中的那些行是"匹配"的,這些我們將在下面詳細解釋。
ON子句是最常見的連接條件的類型:它接收一個和WHERE子句裡用的一樣的布爾值表達式。 如果兩個分別來自T1和T2的行在ON表達式上運算的結果為真,那麼它們就算是匹配的行。
USING是個縮寫的概念:它接收一個用逗號分隔的字段名字列表, 這些字段必須是連接表共有的,最終形成一個連接條件,表示這些字段對必須相同。 最後,JOIN USING 的輸出會為每一對相等的輸入字段輸出一個字段,後面跟著來自各個表的所有其它字段。 因此,USING (a, b, c) 等效于 ON (t1.a = t2.a AND t1.b = t2.b AND t1.c = t2.c) 只不過是如果使用了ON,那麼在結果裡 a,b,和 c字段每個都會有兩個, 而用USING的時候每個字段就只會有一個。
最後,NATURAL 是 USING 的縮寫形式:它形成一個 USING 列表, 該列表由那些在兩個表裡都出現了的字段名字組成。和USING一樣,這些字段只在輸出表裡出現一次。
條件連接可能的類型是:
對于 T1 的每一行 R1,生成的連接表都有一行對應 T2 中的每一個滿足和 R1 的連接條件的行。
首先,執行一次內連接。然後,為 T1 裡那些和 T2 裡任何一行都不滿足連接條件的行返回一個連接行, 同時該連接行裡對應 T2 的列用空值補齊。因此, 生成的連接表裡無條件地包含來自 T1 裡的每一行至少一個副本。
首先,執行一次內連接。然後,為 T2 裡那些和 T1 裡任何一行都不滿足連接條件的行返回一個連接行, 同時該連接行裡對應 T1 的列用空值補齊。因此, 生成的連接表裡無條件地包含來自 T2 裡的每一行。
首先,執行一次內連接。然後,為 T1 裡那些和 T2 裡任何一行都不滿足連接條件的行返回一個連接行, 同時該連接行裡對應 T2 的列用空值補齊。 同樣,為 T2 裡那些和 T1 裡的任何行都不滿足連接條件的行返回一個連接行,該行裡對應 T1 的列用空值補齊。
如果 T1 和 T2 有一個或者都是可以連接的表, 那麼所有類型的連接都可以串在一起或嵌套在一起。 你可以在JOIN子句週圍使用圓括弧來控制連接順序, 如果沒有圓括弧,那麼JOIN子句是從左向右嵌套的。
為了解釋這些問題,假設我們有一個表 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)
你可以給一個表或復雜表引用一個臨時的名字,用于在後面的處理過程中引用那些派生的表。 這樣做叫做表別名。
要創建一個表別名,我們可以寫:
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;
是不合法的 SQL 語法。這裡將發生的事情(這是 PostgreSQL對標準的擴展)是在 FROM 子句裡面隱含地增加了一個表引用,因此這個查詢將會象下面這樣處理
SELECT * FROM my_table AS m, my_table AS my_table WHERE my_table.a > 5;
這樣會生成一個交叉連接,通常可不是你想要的。
圓括弧用于解決歧義.下面的語句將把別名 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外面是看不到的。
聲明一個派生表的子查詢必須包圍在圓括弧裡並且必須賦予一個別名。 (參閱 Section 7.2.1.2.)比如:
FROM (SELECT * FROM table1) AS alias_name
這個例子等效于 FROM table1 AS alias_name。 更有趣的例子是在子查詢裡面有分組或聚集的時候, 這個時候子查詢不能歸納成一個簡單的連接。
表函數是那些生成一個行集合的函數,這個集合可以是由基本數據類型(標量類型)組成, 也可以是由符合數據類型(表的行)組成。他們的用法類似一個表,視圖,或者在查詢的FROM子句裡的子查詢。 表函數返回的字段可以像一個表,視圖,或者子查詢字段那樣包含在 SELECT,JOIN,或者 WHERE 子句裡。
如果一個表函數返回一個基本數據類型,那麼單列的結果數據是以函數的名字命名的。 如果函數返回一個復合類型,那麼結果字段的名字和該類型的每個獨立屬性的名字相同。
表函數可以在 FROM 子句中取一個別名,但你也可以不給它別名。 如果一個函數在 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。 如果在查詢裡使用這樣的函數,那麼我們必須在查詢中聲明預期的行結構, 這樣系統才知道如何分析和規劃該查詢。讓我們看看下面的例子:
SELECT *
FROM dblink('dbname=mydb', 'select proname, prosrc from pg_proc')
AS t1(proname name, prosrc text)
WHERE proname LIKE 'bytea%';dblink 函數執行一個遠程的查詢(參閱 contrib/dblink)。 它聲明為返回 record,因為它可能會被用于任何類型的查詢。 實際的字段集必須在調用它的查詢中聲明,這樣分析器才知道類似 * 這樣的東西應該擴展成什麼樣子。
WHERE子句的語法是
WHERE search_condition
這裡的 search condition 是定義在 Section 4.2 裡的任意表達式,它返回一個類型為boolean的值。
在完成對FROM子句的處理之後,生成的每一行都會對搜索條件進行檢查。 如果該條件的結果是真,那麼該行輸出到輸出表中,否則(也就是說, 如果結果是假或空)就把它拋棄。搜索條件通常至少要引用一些在FROM子句裡生成的列; 這不是必須的,但如果不是這樣的話,那麼WHERE子句就沒什麼用了。
注意: 在JOIN語法實現以前,我們必須把 inner join(內部連接)的連接條件放在 WHERE子句裡。比如,這些表表達式是等效的:
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語法可能不那麼容易移植到其它產品中。 對于外部連接(outer join)而言,我們在任何情況下都沒有選擇:它們必須在FROM子句中完成。 外部連接的 ON/USING 子句不等于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修飾成fdt.c1只有在c1是該子查詢生成的列的名字時才是必須的。 但修飾列名字可以增加語句的準確性,即使有時候不是必須的。 這個例子就演示了字段名字範圍如何從外層查詢擴展到它的內層查詢.
在通過了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 裡沒有哪個值可以和每個組相關聯起來。 被分組的字段可以在選擇列表中引用是因為它們每個組都有已知的常量值。
通常,如果一個表被分了組,那麼沒有在分組中引用的字段都不能引用,除了在聚集表達式中以外。 一個帶聚集表達式的例子是:
=> SELECT x, sum(y) FROM test1 GROUP BY x; x | sum ---+----- a | 4 b | 5 c | 2 (3 rows)
這裡的 sum 是一個聚集函數,它在整個組上計算一個數值。 有關可用的聚集函數的更多信息可以在 Section 9.15 中找到。
提示: 沒有聚集表達式的分組實際上計算了一個字段中獨立數值的集合。 我們也可以用 DISTINCT 子句實現(參閱Section 7.3.3)。
這裡是另外一個例子:它計算每種產品的總銷售額。(而不是所有產品的總銷售額)。
SELECT pid, p.name, (sum(s.units) * p.price) AS sales FROM products p LEFT JOIN sales s USING ( pid ) GROUP BY pid, p.name, p.price;
在這個例子裡,字段pid, p.name,和p.price必須在GROUP BY子句裡, 因為它們都在查詢選擇列表裡被引用到。 (根據產品表具體的設置的不同,名字和價格可能和產品 ID 完全無關,因此理論上額外的分組可能是不必的, 但是這些尚未實現。) 字段s.units不必在GROUP BY列表裡,因為它只是在一個聚集表達式(sum(...)) 裡使用,它代表一組產品的銷售額。對于每種產品,這個查詢都返回一個該產品的所有銷售額的總和。
在嚴格的 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 的組的行。 請注意聚集函數不需要在查詢中的所有地方都一樣。