返回文章列表

SQL查詢技巧與最佳實務

本文探討 SQL 查詢的各種技巧,包含如何有效篩選資料、處理空值、使用模式匹配以及隨機抽樣等。同時,也涵蓋了不同資料函式庫系統的語法差異,例如 DB2、MySQL、PostgreSQL、Oracle 與 SQL Server,並提供最佳實務建議,以提升查詢效能和程式碼可讀性。

資料函式庫 SQL

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 檢索滿足多個條件的資料列

問題描述

您希望傳回滿足多個條件的資料列。

解決方案

結合使用 WHEREORAND 子句。例如,找出部門編號為 10 的員工、擁有手續費的員工,以及部門編號為 20 且薪水不超過 $2,000 的員工:

SELECT *
FROM emp
WHERE deptno = 10
OR comm IS NOT NULL
OR (sal <= 2000 AND deptno = 20);

內容解密:

  • 使用 ORAND 可以構建複雜的條件。
  • 小括號內的條件會優先被評估,本例中確保了正確的邏輯順序。

1.4 檢索特定欄位

問題描述

您希望檢視資料表中特定欄位的資料。

解決方案

SELECT 子句中指定所需的欄位名稱。例如,檢視員工姓名、部門編號和薪水:

SELECT ename, deptno, sal
FROM emp;

內容解密:

  • 指定欄位名稱可以避免檢索不必要的資料,提高查詢效率。
  • 這種做法在跨網路檢索資料時尤其重要,可以減少無謂的資料傳輸。

1.5 為欄位提供有意義的別名

問題描述

您希望更改查詢結果中的欄位名稱,使其更具可讀性。

解決方案

使用 AS 關鍵字為欄位指定別名。例如,將 salcomm 分別命名為 salarycommission

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 BYFETCH

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 結合 LIMITORDER BY

select ename, job
from emp
order by rand() limit 5

內容解密:

  • rand() 函式生成隨機數值。
  • order by rand() 對結果集進行隨機排序。
  • limit 5 限制只傳回前 5 筆記錄。
PostgreSQL

使用內建函式 RANDOM 結合 LIMITORDER BY

select ename, job
from emp
order by random() limit 5

內容解密:

  • random() 函式生成隨機數值。
  • order by random() 對結果集進行隨機排序。
  • limit 5 限制只傳回前 5 筆記錄。
Oracle

使用內建套件 DBMS_RANDOM 中的 VALUE 函式結合 ORDER BYROWNUM

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 結合 TOPORDER 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” 結尾的字串。