SQL 查詢是資料函式庫操作的核心,熟練掌握各種查詢技巧對於提升開發效率至關重要。本文從基礎的 SELECT 陳述式開始,逐步深入講解如何使用 WHERE 子句進行條件篩選、如何處理空值、如何使用模式匹配查詢特定資料,以及如何進行隨機抽樣。此外,文章也針對不同資料函式庫系統(如 DB2、MySQL、PostgreSQL、Oracle 和 SQL Server)的語法差異進行了詳細說明,並提供了一些最佳實務建議,幫助開發者編寫更有效率且更易維護的 SQL 查詢程式碼。透過理解這些技巧,開發者可以更靈活地操作資料函式庫,並提升資料處理的效率。
致謝與聯絡資訊
如何聯絡我們
我們為本文設立了一個網頁,列出了勘誤、範例和任何其他附加資訊。您可以透過以下連結存取該頁面:https://oreil.ly/sql-ckbk-2e。
第二版致謝
許多優秀的人士為第二版的出版提供了幫助。特別感謝技術評審 Alan Beaulieu、Scott Haines 和 Thomas Nield。
最後,感謝我的家人——Clare、Maya和Leda——耐心地忍受我再次投入到寫作中。
—Robert de Graaf
第一版致謝
本文的完成離不開許多人的支援。首先,我要感謝我的母親Connie,本文是獻給她的。如果沒有您的辛勤工作和犧牲,我就不會成為今天的自己。感謝您為我和弟弟做的一切,我非常感激有您這樣的母親。
對於我的弟弟Joe,每當我從巴爾的摩回家休息時,您總是在提醒我,當我們不工作時,生活有多美好,並且催促我完成寫作,以便我能回到生活中更重要的事情上。您是個好人,我很尊敬您,也為有您這樣的弟弟感到自豪。
對於我的未婚妻Georgia,如果沒有您的支援,我不可能完成這本600多頁的書。您與我一同經歷了這段艱難的過程,天天陪伴在我身邊。我知道這對您來說和對我一樣艱難。我白天工作,晚上寫作,但您始終理解並支援我,對此我永遠感激。謝謝您,我愛您。
對於我的準岳父母們,感謝我的岳母Kiki和岳父George在整個過程中給予我的支援。每當我回去拜訪時,您們總是讓我感到賓至如歸,並且確保Georgia和我吃飽。對於我的小姨子Anna和Kathy,和你們一起玩耍總是很有趣,能夠暫時忘卻寫書的辛勞和巴爾的摩的生活。
內容解密:
本段落主要敘述了作者對於編輯Jonathan Gennick的感謝,以及Jonathan Gennick在編輯過程中的重要貢獻。
技術支援與鳴謝
我還要感謝Ales Spetic和Jonathan Gennick,他們的《Transact-SQL Cookbook》一書對我的影響很大。艾薩克·牛頓曾說:“如果我看得更遠,那是因為我站在巨人的肩膀上。” Ales Spetic在《Transact-SQL Cookbook》的致謝部分寫下的話,是對這句名言的最佳詮釋,我覺得應該在每一本SQL書中出現。在此參照他的話:
我希望這本文能夠與Joe Celko、David Rozenshtein、Anatoly Abramovich、Eugene Berger、Itzik Ben-Gan、Richard Snodgrass等優秀作者的作品相互補充。我花了很多個夜晚研究他們的工作,從他們的書中學到了幾乎所有的知識。當我在寫下這些文字時,我意識到,對於我每花一個夜晚來發現他們的秘密,他們一定花了10個夜晚將他們的知識以一致且可讀的形式呈現出來。能夠為SQL社群貢獻一些東西,是我的榮幸。
內容解密:
本段落解釋了Ales Spetic對於SQL社群貢獻的感謝,並參照了艾薩克·牛頓的名言,表達了站在巨人肩膀上的感受。
此外,我還要感謝Sanjay Mishra,他的《Mastering Oracle SQL》一書非常出色,並且他還介紹我認識了Jonathan。如果沒有Sanjay,我可能永遠不會遇到Jonathan,也就不會寫這本文。一個簡單的電子郵件竟然改變了我的生活,真令人驚訝。我還要特別感謝David Rozenshtein,他的《Essence of SQL》一書為我提供了對集合/SQL思維和問題解決方法的紮實理解。我還要感謝David Rozenshtein、Anatoly Abramovich和Eugene Birger,他們合著的《Optimizing Transact-SQL》一書讓我學到了許多先進的SQL技術。
內容解密:
本段落詳細介紹了作者對於其他技術人員和作者的感謝,包括Sanjay Mishra、David Rozenshtein等,他們的作品和幫助對作者產生了重要影響。
最後,我要感謝Wireless Generation的所有同事,這是一家優秀的公司,裡面有許多優秀的人才。同時,我也要感謝所有花時間審閱、批評或提供建議以幫助我完成這本文的人,包括Jesse Davis、Joel Patterson、Philip Zee、Kevin Marshall、Doug Daniels、Otis Gospodnetic、Ken Gunn、John Stewart、Jim Abramson、Adam Mayer、Susan Lau、Alexis Le-Quoc和Paul Feuer。此外,我還要特別感謝Maggie Ho,她仔細審閱了我的工作,並提供了極具價值的反饋意見,特別是在視窗函式方面的重新整理。
內容解密:
本段落敘述了作者對於公司同事和其他提供幫助的人士的感謝,包括技術審閱者和其他提供建議的人。
SQL 基本查詢技巧
在資料函式倉管理與分析中,SQL(Structured Query Language)是一種不可或缺的工具。本章節將探討基本的 SELECT 陳述式,這是 SQL 中最常用來檢索資料函式庫中資料的指令。掌握這些基本技巧對於處理更複雜的查詢至關重要。
1.1 檢索表中的所有資料列和欄位
問題描述
您有一個資料表,希望檢視其中的所有資料。
解決方案
使用特殊的 * 字元對該表發出 SELECT 查詢:
SELECT *
FROM emp;
內容解密:
SELECT *表示選擇所有的欄位。FROM emp指定了要查詢的資料表名稱為emp。- 由於沒有使用
WHERE子句,因此會傳回該表中的所有資料列。
替代方案
您可以明確列出所有欄位名稱:
SELECT empno, ename, job, sal, mgr, hiredate, comm, deptno
FROM emp;
內容解密:
- 明確列出欄位名稱可以讓查詢結果更易於理解和維護。
- 在程式碼中使用明確的欄位名稱比使用
*更為推薦,因為它可以避免因表結構變更而導致的意外錯誤。
1.2 檢索滿足特定條件的資料列
問題描述
您希望檢視資料表中滿足特定條件的資料列。
解決方案
使用 WHERE 子句來指定條件。例如,檢視部門編號為 10 的所有員工:
SELECT *
FROM emp
WHERE deptno = 10;
內容解密:
WHERE子句允許您根據特定條件過濾資料列。- 在本例中,條件是
deptno = 10,即部門編號為 10 的員工。
1.3 檢索滿足多個條件的資料列
問題描述
您希望傳回滿足多個條件的資料列。
解決方案
結合使用 WHERE、OR 和 AND 子句。例如,找出部門編號為 10 的員工、擁有手續費的員工,以及部門編號為 20 且薪水不超過 $2,000 的員工:
SELECT *
FROM emp
WHERE deptno = 10
OR comm IS NOT NULL
OR (sal <= 2000 AND deptno = 20);
內容解密:
- 使用
OR和AND可以構建複雜的條件。 - 小括號內的條件會優先被評估,本例中確保了正確的邏輯順序。
1.4 檢索特定欄位
問題描述
您希望檢視資料表中特定欄位的資料。
解決方案
在 SELECT 子句中指定所需的欄位名稱。例如,檢視員工姓名、部門編號和薪水:
SELECT ename, deptno, sal
FROM emp;
內容解密:
- 指定欄位名稱可以避免檢索不必要的資料,提高查詢效率。
- 這種做法在跨網路檢索資料時尤其重要,可以減少無謂的資料傳輸。
1.5 為欄位提供有意義的別名
問題描述
您希望更改查詢結果中的欄位名稱,使其更具可讀性。
解決方案
使用 AS 關鍵字為欄位指定別名。例如,將 sal 和 comm 分別命名為 salary 和 commission:
SELECT sal AS salary, comm AS commission
FROM emp;
內容解密:
- 使用別名(aliasing)可以使查詢結果更易於理解。
- 這種做法在生成報表或與其他系統介接時尤其有用。
資料函式庫查詢中的欄位別名與條件篩選
在進行資料函式庫查詢時,合理使用欄位別名(Alias)可以大幅提升查詢結果的可讀性。然而,當我們嘗試在 WHERE 子句中參照這些別名時,可能會遇到問題。本文將探討如何解決這一問題,並介紹如何在查詢中使用條件邏輯、字串串接以及限制傳回的行數。
在 WHERE 子句中參照別名欄位
問題描述
假設我們有一個員工薪資查詢,希望根據別名 salary 進行篩選,但直接在 WHERE 子句中使用 salary 會導致查詢失敗:
SELECT sal AS salary, comm AS commission
FROM emp
WHERE salary < 5000
解決方案
為瞭解決這個問題,我們可以使用內嵌檢視(Inline View)將原始查詢包裝起來,然後在外部查詢中參照別名欄位:
SELECT *
FROM (
SELECT sal AS salary, comm AS commission
FROM emp
) x
WHERE salary < 5000
#### 內容解密:
1. 內嵌檢視 `x` 包含了原始查詢,並對 `sal` 和 `comm` 欄位賦予了別名 `salary` 和 `commission`。
2. 外部查詢則直接對這些別名進行篩選,符合條件的資料才會被傳回。
3. 這種方法的關鍵在於,`FROM` 子句的執行順序先於 `WHERE` 子句,因此內嵌檢視中的別名可以在外部查詢的 `WHERE` 子句中使用。
字串串接
問題描述
有時候,我們需要將多個欄位的值合併成一個欄位。例如,將員工的名字和職位合併成一條訊息:
CLARK WORKS AS A MANAGER
KING WORKS AS A PRESIDENT
MILLER WORKS AS A CLERK
解決方案
不同的資料函式倉管理系統(DBMS)提供了不同的字串串接方法:
DB2、Oracle、PostgreSQL
使用雙豎線 || 作為串接運算元:
SELECT ename || ' WORKS AS A ' || job AS msg
FROM emp
WHERE deptno = 10
#### 內容解密:
1. 使用 `||` 將 `ename`、`' WORKS AS A '` 和 `job` 三個部分串接起來。
2. 結果賦予別名 `msg` 以便於識別。
3. 篩選條件 `deptno = 10` 確保只處理特定部門的員工。
MySQL
使用 CONCAT 函式進行串接:
SELECT CONCAT(ename, ' WORKS AS A ', job) AS msg
FROM emp
WHERE deptno = 10
#### 內容解密:
1. `CONCAT` 函式接受多個引數,將它們串接成一個字串。
2. 結果同樣賦予別名 `msg`。
3. 篩選條件與前述相同。
SQL Server
使用加號 + 進行串接:
SELECT ename + ' WORKS AS A ' + job AS msg
FROM emp
WHERE deptno = 10
#### 內容解密:
1. 使用 `+` 將三個部分串接起來,與其他 DBMS 的方法類別似。
2. 結果賦予別名 `msg`。
3. 篩選條件保持一致。
在 SELECT 陳述式中使用條件邏輯
問題描述
我們希望根據員工的薪水範圍傳回不同的狀態訊息,例如「UNDERPAID」、「OK」或「OVERPAID」。
解決方案
使用 CASE 表示式來實作條件邏輯:
SELECT ename, sal,
CASE
WHEN sal <= 2000 THEN 'UNDERPAID'
WHEN sal >= 4000 THEN 'OVERPAID'
ELSE 'OK'
END AS status
FROM emp
#### 內容解密:
1. `CASE` 表示式根據 `sal` 的值進行判斷。
2. 當薪水小於等於 2000 時,傳回 `'UNDERPAID'`;大於等於 4000 時,傳回 `'OVERPAID'`;否則傳回 `'OK'`。
3. 結果賦予別名 `status` 以提高可讀性。
限制傳回的行數
問題描述
有時我們只需要查詢結果的一部分,例如前五筆資料。
解決方案
不同 DBMS 提供不同的語法來限制傳回的行數:
DB2
使用 FETCH FIRST 子句:
SELECT *
FROM emp
FETCH FIRST 5 ROWS ONLY
#### 內容解密:
1. 直接在查詢結尾新增 `FETCH FIRST 5 ROWS ONLY`。
2. 這會限制結果集只包含前五行資料。
MySQL 和 PostgreSQL
使用 LIMIT 子句:
SELECT *
FROM emp
LIMIT 5
#### 內容解密:
1. 在查詢結尾使用 `LIMIT 5`。
2. 功能與 `FETCH FIRST` 相同,限制結果集大小。
Oracle
使用 ROWNUM 篩選:
SELECT *
FROM emp
WHERE ROWNUM <= 5
#### 內容解密:
1. Oracle 使用虛擬欄位 `ROWNUM` 為每一行賦予一個遞增的編號。
2. 在 `WHERE` 子句中篩選 `ROWNUM <= 5` 以取得前五行資料。
3. 需要注意的是,直接使用 `ROWNUM = 5` 無法取得第五行,因為 `ROWNUM` 是在資料檢索過程中動態生成的。
SQL Server
使用 TOP 關鍵字:
SELECT TOP 5 *
FROM emp
#### 內容解密:
1. 在 `SELECT` 後直接使用 `TOP 5` 指定傳回的行數。
2. 簡潔直觀,易於理解。
資料檢索基礎:常見查詢技巧與應用
在資料函式庫查詢中,檢索記錄是最基本的操作。本章節將介紹一系列常見的查詢技巧,包括限制傳回行數、隨機傳回記錄、查詢空值、轉換空值為實際值以及模式匹配等。
1.10 從表中傳回 n 筆隨機記錄
問題描述
需要從表中傳回特定數量的隨機記錄。例如,修改以下 SQL 陳述式,使其每次執行時傳回不同的五筆記錄:
select ename, job
from emp
解決方案
利用資料函式倉管理系統(DBMS)提供的內建函式生成隨機值,並在 ORDER BY 子句中使用該函式對結果進行隨機排序。然後,使用前述的技術限制傳回的記錄數量。
DB2
使用內建函式 RAND 結合 ORDER BY 和 FETCH:
select ename, job
from emp
order by rand() fetch first 5 rows only
內容解密:
rand()函式生成隨機數值。order by rand()對結果集進行隨機排序。fetch first 5 rows only限制只傳回前 5 筆記錄。
MySQL
使用內建函式 RAND 結合 LIMIT 和 ORDER BY:
select ename, job
from emp
order by rand() limit 5
內容解密:
rand()函式生成隨機數值。order by rand()對結果集進行隨機排序。limit 5限制只傳回前 5 筆記錄。
PostgreSQL
使用內建函式 RANDOM 結合 LIMIT 和 ORDER BY:
select ename, job
from emp
order by random() limit 5
內容解密:
random()函式生成隨機數值。order by random()對結果集進行隨機排序。limit 5限制只傳回前 5 筆記錄。
Oracle
使用內建套件 DBMS_RANDOM 中的 VALUE 函式結合 ORDER BY 和 ROWNUM:
select *
from (
select ename, job
from emp
order by dbms_random.value()
)
where rownum <= 5
內容解密:
dbms_random.value()生成隨機數值。order by dbms_random.value()對結果集進行隨機排序。- 外層查詢使用
rownum <= 5限制傳回前 5 筆記錄。
SQL Server
使用內建函式 NEWID 結合 TOP 和 ORDER BY:
select top 5 ename, job
from emp
order by newid()
內容解密:
newid()生成唯一的隨機識別碼。order by newid()對結果集進行隨機排序。top 5限制只傳回前 5 筆記錄。
1.11 查詢空值
問題描述
需要找出某個欄位為空的所有記錄。
解決方案
使用 IS NULL 運算子檢查空值:
select *
from emp
where comm is null
內容解密:
is null用於檢查欄位是否為空值。
1.12 將空值轉換為實際值
問題描述
希望將包含空值的記錄轉換為非空值傳回。
解決方案
使用 COALESCE 函式替代空值:
select coalesce(comm, 0)
from emp
內容解密:
coalesce(comm, 0)傳回comm的值如果它非空,否則傳回0。
1.13 模式匹配查詢
問題描述
需要傳回符合特定子字串或模式的記錄。
解決方案
使用 LIKE 運算子結合 SQL 萬用字元 %:
select ename, job
from emp
where deptno in (10,20)
and (ename like '%I%' or job like '%ER')
內容解密:
%萬用字元匹配任意字元序列。like '%I%'查詢包含 “I” 的字串。like '%ER'查詢以 “ER” 結尾的字串。