我們人生中許多時刻都在談條件,可能是跟媽媽的約定、可能是談薪水,不只在人生決策上很重要,在IT領域當中,能夠依據條件去進行不同處理的功能在許多程式語言當中絕對是必備的,資料庫查詢語言(SQL)雖然屬性上與一般程式較為不同,不過也有撰寫條件式的功能,語法略為不同。
CASE
is 是一個表達式,因此您可以在任何可以使用表達式的地方使用它,例如SELECT、WHERE
和HAVING
。先寫入範例資料,由於本文重點在SQL條件判斷式,為了方便示範SQL條件判斷式,下面所使用的資料表與資料不會考慮正規化的內容,或可參考Day15 為什麼要Normalization,自己建立範例練習。
CREATE TABLE orders (
customer_id INT,
customer_level INT,
course_name VARCHAR(255),
course_time INTERVAL,
price INT
);
INSERT INTO orders(customer_id,customer_level, course_name,course_time,price)
VALUES (1,1,'PostgreSQL新手入門第一堂課', '17 hours 30 minutes', 2599),
(2,1,'PHP新手入門網站開發', '12 hours 17 minutes', 1799),
(3,1,'JavaScript新手入門轉職利器', '21 hours 02 minutes', 2999),
(4,1,'JavaScript新手入門轉職利器', '21 hours 02 minutes', 2999),
(5,2,'JavaScript新手入門轉職利器', '21 hours 02 minutes', 2999),
(6,2,'PostgreSQL新手入門第一堂課', '17 hours 30 minutes', 2599),
(6,2,'PHP新手入門網站開發', '12 hours 17 minutes', 1799),
(6,2,'JavaScript新手入門轉職利器', '21 hours 02 minutes', 2999),
(7,3,'PostgreSQL新手入門第一堂課', '17 hours 30 minutes', 2599),
(8,3,'PostgreSQL新手入門第一堂課', '17 hours 30 minutes', 2599),
(9,3,'PostgreSQL新手入門第一堂課', '17 hours 30 minutes', 2599),
(10,3,'PostgreSQL新手入門第一堂課', '17 hours 30 minutes', 2599),
(11,1,'PHP新手入門網站開發', '12 hours 17 minutes', 1799),
(12,2,'PHP新手入門網站開發', '12 hours 17 minutes', 1799),
(13,2,'PHP新手入門網站開發', '12 hours 17 minutes', 1799),
(14,3,'PHP新手入門網站開發', '12 hours 17 minutes', 1799),
(15,1,'PHP新手入門網站開發', '12 hours 17 minutes', 1799),
(16,1,'JavaScript新手入門轉職利器', '21 hours 02 minutes', 2999),
(17,2,'JavaScript新手入門轉職利器', '21 hours 02 minutes', 2999),
(18,3,'JavaScript新手入門轉職利器', '21 hours 02 minutes', 2999),
(19,4,'JavaScript新手入門轉職利器', '21 hours 02 minutes', 2999),
(20,5,'JavaScript新手入門轉職利器', '21 hours 02 minutes', 2999);
這是最簡單最基礎的用法,如果看到什麼就輸出什麼,假設為了增強客戶的榮譽心,我們將花越多錢的客戶給予越帥氣的名稱,絕對不是想騙客戶口袋裡的錢錢 : )
-- 假設這是一間稱為程式魔法學院的線上課程網站,給個帥帥的名稱有助於沉浸式學習。(!?)
SELECT customer_id AS 使用者編號,
CASE WHEN customer_level=1 THEN '初心者'
WHEN customer_level=2 THEN '魔法師'
WHEN customer_level=3 THEN '巫師'
ELSE '賢者'
END AS 使用者職業
FROM orders;
我們有時候會在不同條件下,關注不一樣的重點,例如客戶等級1的時候,我們想找出有多少第一次買線上課程就願意花2000元以上的課程,接著假設客戶等級大於1是代表已經購買不只一堂課的線上課程的客戶,這些客戶願意買15小時以下的課程有多少意願,雖然這是兩件事情,但我們可以透過CASE STATEMENT 一次處理起來。
謎之音 : 抓出願意買2000元以上課程的新用戶跟願意買較短課程的老用戶,可以用來決策課程價格定位以及確認課程長度多短可以被購買(開源跟節流)。
SELECT *
FROM orders
WHERE CASE
WHEN
customer_level = 1
THEN
price > 2000
ELSE
course_time < '15:00:00'
END
ORDER BY customer_level,customer_id;
假設使用者等級是一等初心者,價格一率打85折,那麼可以透過CASE來計算實際總收入,如果是初心者就必須算標價的85折,其他則原價計算。
SELECT
SUM(
CASE WHEN customer_level = 1
THEN ROUND(price * 0.85)
ELSE price
END
) AS 總收入
FROM orders
COALESCE : 不限數量的傳入參數,依序找不是NULL的參數,如果找到就回傳值,通常被運用於如果NULL則回傳什麼值,這種情境下比 CASE STATEMENT 更為簡潔。
假設有一間滷味攤,經營策略是買70元送10元,以此類推,那麼我們來看每筆交易狀況。
CREATE TABLE luwei(
id SERIAL PRIMARY KEY,
price NUMERIC NOT NULL,
discount NUMERIC
);
INSERT INTO luwei (price, discount)
VALUES
(70, 10),
(85, 10),
(45, NULL),
(75, 10)
(140, 20);
如果我們使用交易價減掉折價,會發生沒有折價的那筆會出現問題。
-- NULL的資料,相減後仍是NULL。(此為非預期的異常)
SELECT (price - discount) AS real_price
FROM luwei;
因此我們可以使用 COALESCE 來處理 NULL,如果是NULL,就往右找參數,找到0並回傳。
SELECT (price - COALESCE(discount,0)) AS real_price
FROM luwei;
CREATE TABLE employees (
name VARCHAR(255),
type VARCHAR(255)
);
INSERT INTO employees (name,type)
VALUES ('王大空','專任助理'),
('陳大天','專任助理'),
('廖小明','博士後研究員');
假設我們有一個需求卡博後人數與專任助理相比不能超過50%,可以用以下的語法去算。
SELECT
ROUND(
(SUM(CASE WHEN TYPE = '博士後研究員' THEN 1 ELSE 0 END)::NUMERIC /
SUM(CASE WHEN TYPE = '專任助理' THEN 1 ELSE 0 END)::NUMERIC * 100
),2) || '%' AS 博後人數為專助人數的幾趴
FROM employees
不過如果兩個專任助理都離職了,那麼只剩下一個博後跟零個專任助理,上面這個語句就會遇到1/0的錯誤,資料庫就會噴錯 ERROR: division by zero,我們可以過NULLIF去判斷如果除數等於0就回傳NULL,這樣至少可以保證程式可以繼續執行下去。
SELECT
ROUND(
( SUM(CASE WHEN TYPE = '博士後研究員' THEN 1 ELSE 0 END)::NUMERIC /
NULLIF(SUM(CASE WHEN TYPE = '專任助理' THEN 1 ELSE 0 END)::NUMERIC * 100 ,0)
)
,2) || '%' AS 博後人數為專助人數的幾趴
FROM employees