| PostgreSQL 8.0.0 中文文件(轉譯自 PostgreSQL 中國 製作的簡體中文版本) | ||||
|---|---|---|---|---|
| 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 是任意返回一個類型為boolean的值資料表達式 (參閱 Section 4.2)。
在完成對FROM子句的處理之後,生成的每一行都會對搜索條件進行檢查。 如果該條件的結果是真,那麼該行輸出到輸出資料表中,否則(也就是說, 如果結果是假或空)就把它拋棄。搜索條件通常至少要引用一些在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語法可能不那麼容易移植到其它產品中。 對於外部連接(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 的組的行。 請注意聚集函數不需要在查詢中的所有地方都一樣。