SQL 里面最常用的命令是 SELECT 語句,用于檢索數據。語法是:
SELECT [ ALL | DISTINCT [ ON ( expression [, ...] ) ] ]
* | expression [ AS output_name ] [, ...]
[ INTO [ TEMPORARY | TEMP ] [ TABLE ] new_table ]
[ FROM from_item [, ...] ]
[ WHERE condition ]
[ GROUP BY expression [, ...] ]
[ HAVING condition [, ...] ]
[ { UNION | INTERSECT | EXCEPT [ ALL ] } select ]
[ ORDER BY expression [ ASC | DESC | USING operator ] [, ...] ]
[ FOR UPDATE [ OF class_name [, ...] ] ]
[ LIMIT { count | ALL } [ { OFFSET | , } start ]]
現在我們將通過不同的例子演示 SELECT 語句復雜的語法。用于這些例子的表在 供應商和部件數據庫 里定義。
這里是一些使用 SELECT 語句的簡單例子:
Example 1-4. 帶有條件的簡單查詢
要從表 PART 里面把字段 PRICE 大于 10 的所有記錄找出來, 我們寫出下面查詢:
SELECT * FROM PART
WHERE PRICE > 10;
然后得到表:
PNO | PNAME | PRICE
-----+---------+--------
3 | Bolt | 15
4 | Cam | 25
在 SELECT語句里使用 "*" 將檢索出表中的所有屬性。 如果我們只希望從表 PART 中檢索出屬性 PNAME 和 PRICE, 我們使用下面的語句:
SELECT PNAME, PRICE
FROM PART
WHERE PRICE > 10;
這回我們的結果是:
PNAME | PRICE
--------+--------
Bolt | 15
Cam | 25
請注意
SQL 的 SELECT 語句對應關系演算里面的 "projection" (映射),而不是 "selection"(選擇)(參閱
關系演算 獲取詳細信息)。
WHERE 子句里的條件也可以用關鍵字 OR,AND,和 NOT 邏輯地連接起來:
SELECT PNAME, PRICE
FROM PART
WHERE PNAME = 'Bolt' AND
(PRICE = 0 OR PRICE <= 15);
這樣將生成下面的結果:
PNAME | PRICE
--------+--------
Bolt | 15
目標列表和 WHERE 子句里可以使用算術操作。例如, 如果我們想知道如果我們買兩個部件的話要多少錢, 我們可以用下面的查詢:
SELECT PNAME, PRICE * 2 AS DOUBLE
FROM PART
WHERE PRICE * 2 < 50;
這樣我們得到:
PNAME | DOUBLE
--------+---------
Screw | 20
Nut | 16
Bolt | 30
請注意在關鍵字 AS 后面的 DOUBLE 是第二個列的新名字。 這個技巧可以用于目標列表里的每個元素, 給它們賦予一個在結果列中顯示的新的標題。 這個新的標題通常稱為別名。這個別名不能在該查詢的其他地方使用。
下面的例子顯示了 SQL 里是如何實現連接的。
要在共同的屬性上連接三個表 SUPPLIER,PART 和 SELLS, 我們通常使用下面的語句:
SELECT S.SNAME, P.PNAME
FROM SUPPLIER S, PART P, SELLS SE
WHERE S.SNO = SE.SNO AND
P.PNO = SE.PNO;
而我們得到的結果是:
SNAME | PNAME
-------+-------
Smith | Screw
Smith | Nut
Jones | Cam
Adams | Screw
Adams | Bolt
Blake | Nut
Blake | Bolt
Blake | Cam
在 FROM 子句里,我們為每個關系使用了一個別名, 因為在這些關系間有著公共的命名屬性(SNO 和 PNO)。 現在我們可以區分不同表的公共命名屬性, 只需要簡單的用每個關系的別名加上個點做前綴就行了。 聯合是用與 一個內部聯接 里顯示的同樣的方法計算的。首先算出笛卡兒積 SUPPLIER × PART × SELLS 。然后選出那些滿足 WHERE 子句里給出的條件的記錄 (也就是說,公共命名屬性的值必須相等)。 最后我們映射出除 S.SNAME 和 P.PNAME 外的所有屬性。
另外一個進行連接的方法是使用下面這樣的 SQL JOIN 語法:
select sname, pname from supplier
JOIN sells USING (sno)
JOIN part USING (pno);
giving again:
sname | pname
-------+-------
Smith | Screw
Adams | Screw
Smith | Nut
Blake | Nut
Adams | Bolt
Blake | Bolt
Jones | Cam
Blake | Cam
(8 rows)
一個用 JOIN 語法創建的連接表,是一個出現在 FROM 子句里的, 在任何 WHERE,GROUP BY 或 HAVING 子句之前的表引用列表項. 其它表引用,包括表名字或者其它 JOIN 子句,如果用逗號分隔的話, 可以包含在 FROM 子句里. 連接生成的表邏輯上和任何其它在 FROM 子句里列出的表都一樣.
SQL JOIN 有兩種主要類型,CROSS JOIN (無條件連接) 和條件連接.條件連接還可以根據聲明的 連接條件(ON,USING,或 NATURAL)和它 應用的方式(INNER 或 OUTER 連接)進一步細分.
連接類型
- CROSS JOIN
-
{ T1 } CROSS JOIN { T2 }
一個交叉連接(cross join)接收兩個分別有 N 行和 M 行 的表 T1 和 T2,然后返回一個包含交叉乘積 NxM 條記錄的 連接表. 對于 T1 的每行 R1,T2 的每行 R2 都與 R1 連接生成 連接的表行 JR,JR 包含所有 R1 和 R2 的字段. CROSS JOIN 實際上就是一個 INNER JOIN ON TRUE.
- 條件 JOIN
-
{ T1 } [ NATURAL ] [ INNER | { LEFT | RIGHT | FULL } [ OUTER ] ] JOIN { T2 } { ON search condition | USING ( join column list ) }
一個條件 JOIN 必須通過提供一個(并且只能有一個) NATURAL,ON,或者 USING 這樣的關鍵字來聲明它的 連接條件. ON 子句 接受一個 search condition, 它與一個 WHERE 子句相同.USING 子句接受一個用逗號分隔的 字段名列表,連接表中必須都有這些字段, 并且用那些字段連接這些表,生成的連接表包含每個共有字段 和兩個表的所有其它字段. NATURAL 是 USING 子句的縮寫,它列出兩個表中所有公共 的字段名字.使用 USING 和 NATURAL 的副作用是 每個連接的字段都只有一份拷貝出現在結果表中 (與前面定義的關系演算的 JOIN 相比較).
-
[ INNER ] JOIN
-
對于 T1 的每行 R1,連接成的表在 T2 里都有一行滿 足與 R1 一起的連接條件.
對于所有 JOIN 而言,INNER 和 OUTER 都是可選的.INNER 是缺?。?LEFT,RIGHT,和 FULL 只用于 OUTER JOIN.
-
LEFT [ OUTER ] JOIN
-
首先,執行一次 INNER JOIN. 然后,如果 T1 里有一行對任何 T2 的行都不滿足 連接條件,那么返回一個連接行,該行的 T2 的字段 為 null.
小技巧: 連接成的表無條件地包含 T1 里的所有行.
-
RIGHT [ OUTER ] JOIN
-
首先,執行一次 INNER JOIN. 然后,如果 T2 里有一行對任何 T1 的行都不滿足 連接條件,那么返回一個連接行,該行的 T1 的字段 為 null.
小技巧: 連接成的表無條件地包含 T2 里的所有行.
-
FULL [ OUTER ] JOIN
-
首先,執行一次 INNER JOIN. 然后,如果 T1 里有一行對任何 T2 的行都不滿足 連接條件,那么返回一個連接行,該行的 T1 的字段 為 null. 同樣,如果 T2 里有一行對任何 T1 的行都不滿足 連接條件,那么返回一個連接行,該行的 T2 的字段 為 null.
小技巧: 連接成的表無條件地擁有來自 T1 的每 一行和來自 T2 的每一行.
所有 類型的 JOIN 都可以鏈接在一起或者嵌套在一起, 這時 T1 和 T2 都可以是連接生成的表.我們可以使用圓括弧控制 JOIN 的順序,如果我們不主動控制,那么連接順序是從左到右.
SQL 提供以一些聚集操作符(如, AVG,COUNT,SUM,MIN,MAX),這些聚集操作符以一個表達式為參數。 只要是滿足 WHERE 子句的行,就會計算這個表達式, 然后聚集操作符對這個輸入數值的集合進行計算. 通常,一個聚集對整個 SELECT 語句計算的結果是 生成一個結果.但如果在一個查詢里面聲明了分組, 那么數據庫將對每個組進行一次獨立的計算,并且 聚集結果是按照各個組出現的(見下節).
Example 1-5. 聚集
果我們想知道表 PART 里面所有部件的平均價格,我們可以使用下面查詢:
SELECT AVG(PRICE) AS AVG_PRICE
FROM PART;
結果是:
AVG_PRICE
-----------
14.5
如果我們想知道在表 PART 里面存儲了多少部件,我們可以使用語句:
SELECT COUNT(PNO)
FROM PART;
得到:
COUNT
-------
4
SQL 允許我們把一個表里面的記錄分成組。 然后上面描述的聚集操作符可以應用于這些組上 (也就是說,聚集操作符的值不再是對所有聲明的列的值進行操作, 而是對一個組的所有值進行操作。這樣聚集函數是為每個組獨立地進行計算的。)
對記錄的分組是通過關鍵字 GROUP BY 實現的,GROUP BY 后面跟著一個定義組的構成的屬性列表。 如果我們使用語句 GROUP BY A1, ⃛, Ak 我們就把關系分成了組,這樣當且僅當兩條記錄在所有屬性 A1, ⃛, Ak 上達成一致,它們才是同一組的。
Example 1-6. 聚集
如果我們想知道每個供應商銷售多少個部件,我們可以這樣寫查詢:
SELECT S.SNO, S.SNAME, COUNT(SE.PNO)
FROM SUPPLIER S, SELLS SE
WHERE S.SNO = SE.SNO
GROUP BY S.SNO, S.SNAME;
得到:
SNO | SNAME | COUNT
-----+-------+-------
1 | Smith | 2
2 | Jones | 1
3 | Adams | 2
4 | Blake | 3
然后我們看一看發生了什么事情。首先生成表 SUPPLIER 和 SELLS 的連接:
S.SNO | S.SNAME | SE.PNO
-------+---------+--------
1 | Smith | 1
1 | Smith | 2
2 | Jones | 4
3 | Adams | 1
3 | Adams | 3
4 | Blake | 2
4 | Blake | 3
4 | Blake | 4
然后我們把那些屬性 S.SNO 和 S.SNAME 相同的記錄放在組中:
S.SNO | S.SNAME | SE.PNO
-------+---------+--------
1 | Smith | 1
| 2
--------------------------
2 | Jones | 4
--------------------------
3 | Adams | 1
| 3
--------------------------
4 | Blake | 2
| 3
| 4
在我們的例子里,我們有四個組并且現在我們可以對每個組應用聚集操作符 COUNT,生成上面給出的查詢的最終結果。
請注意如果要讓一個使用 GROUP BY 和聚集操作符的查詢的結果有意義, 那么用于分組的屬性也必須出現在目標列表中。 所有沒有在 GROUP BY 子句里面出現的屬性都只能通過使用聚集函數來選擇。 否則就不會有唯一的數值與其它字段關聯.
還要注意的是在聚集上聚集是沒有意義的,比如,AVG(MAX(sno)), 因為 SELECT 只做一個回合的分組和聚集.你可以獲得這樣的結果, 方法是使用臨時表或者在 FROM 子句中使用一個子 SELECT 做第一個層次的聚集.
HAVING 子句運做起來非常象 WHERE 子句, 只用于對那些滿足 HAVING 子句里面給出的條件的組進行計算。 其實,WHERE 在分組和聚集之前過濾掉我們不需要的輸入行, 而 HAVING 在 GROUP 之后那些不需要的組. 因此,WHERE 無法使用一個聚集函數的結果. 而另一方面,我們也沒有理由寫一個不涉及聚集函數的 HAVING. 如果你的條件不包含聚集,那么你也可以把它寫在 WHERE 里面, 這樣就可以避免對那些你準備拋棄的行進行的聚集運算.
Example 1-7. Having
如果我們想知道那些銷售超過一個部件的供應商,使用下面查詢:
SELECT S.SNO, S.SNAME, COUNT(SE.PNO)
FROM SUPPLIER S, SELLS SE
WHERE S.SNO = SE.SNO
GROUP BY S.SNO, S.SNAME
HAVING COUNT(SE.PNO) > 1;
and get:
SNO | SNAME | COUNT
-----+-------+-------
1 | Smith | 2
3 | Adams | 2
4 | Blake | 3
在 WHERE 和 HAVING 子句里,允許在任何要產生數值的地方使用子查詢 (子選擇)。 這種情況下,該值必須首先來自對子查詢的計算。子查詢的使用擴展了 SQL 的表達能力。
Example 1-8. 子查詢
如果我們想知道所有比名為 'Screw' 的部件貴的部件,我們可以用下面的查詢:
SELECT *
FROM PART
WHERE PRICE > (SELECT PRICE FROM PART
WHERE PNAME='Screw');
結果是:
PNO | PNAME | PRICE
-----+---------+--------
3 | Bolt | 15
4 | Cam | 25
當我們檢查上面的查詢時會發現出現了兩次 SELECT 關鍵字。 第一個在查詢的開頭 - 我們將稱之為外層 SELECT - 而另一個在 WHERE 子句里面,成為一個嵌入的查詢 - 我們將稱之為內層 SELECT。 對外層 SELECT 的每條記錄都必須先計算內層 SELECT。在完成所有計算之后, 我們得知名為 'Screw' 部件的記錄的價格, 然后我們就可以檢查那些價格更貴的記錄了。 (實際上,在本例中,內層查詢只需要執行一次, 因為它不依賴于外層查詢高等狀態.)
如果我們想知道那些不銷售任何部件的供應商 (比如說,我們想把這些供應商從數據庫中刪除),我們用:
SELECT *
FROM SUPPLIER S
WHERE NOT EXISTS
(SELECT * FROM SELLS SE
WHERE SE.SNO = S.SNO);
在我們的例子里,結果列將是空的,因為每個供應商至少銷售一個部件。 請注意我們在 WHERE 子句的內層 SELECT 里使用了來自外層 SELECT 的 S.SNO。 正如前面所說的,子查詢為每個外層查詢計算一次,也就是說, S.SNO 的值總是從外層 SELECT 的實際記錄中取得的。
一種有些特別的子查詢的用法是把它們放在 FROM 子句里. 這個特性很有用,因為這樣的子查詢可以輸出多列和多行, 而在表達式里使用的子查詢必須生成一個結果. FROM 里的子查詢還可以讓我們獲得多于一個回合的分組/聚集特性, 而不需要求助于臨時表.
Example 1-9. FROM 里面的子查詢
如果我們想知道在所有我們的供應商中的最高平均部件價格的那家, 我們不能用 MAX(AVG(PRICE)),但我們可以這么寫:
SELECT MAX(subtable.avgprice)
FROM (SELECT AVG(P.PRICE) AS avgprice
FROM SUPPLIER S, PART P, SELLS SE
WHERE S.SNO = SE.SNO AND
P.PNO = SE.PNO
GROUP BY S.SNO) subtable;
這個子查詢為每個供應商返回一行(因為它的 GROUP BY) 然后我們在外層查詢對所有行進行聚集.
這些操作符分別計算兩個子查詢產生的元組的聯合,相交和集合理論里的相異。
Example 1-10. Union, Intersect, Except
下面的例子是 UNION 的例子:
SELECT S.SNO, S.SNAME, S.CITY
FROM SUPPLIER S
WHERE S.SNAME = 'Jones'
UNION
SELECT S.SNO, S.SNAME, S.CITY
FROM SUPPLIER S
WHERE S.SNAME = 'Adams';
產生結果:
SNO | SNAME | CITY
-----+-------+--------
2 | Jones | Paris
3 | Adams | Vienna
下面是相交( INTERSECT)的例子:
SELECT S.SNO, S.SNAME, S.CITY
FROM SUPPLIER S
WHERE S.SNO > 1
INTERSECT
SELECT S.SNO, S.SNAME, S.CITY
FROM SUPPLIER S
WHERE S.SNO < 3;
產生結果:
SNO | SNAME | CITY
-----+-------+--------
2 | Jones | Paris
兩個查詢都會返回的元組是那條 SNO=2 的
最后是一個 EXCEPT 的例子:
SELECT S.SNO, S.SNAME, S.CITY
FROM SUPPLIER S
WHERE S.SNO > 1
EXCEPT
SELECT S.SNO, S.SNAME, S.CITY
FROM SUPPLIER S
WHERE S.SNO > 3;
結果是:
SNO | SNAME | CITY
-----+-------+--------
2 | Jones | Paris
3 | Adams | Vienna