《SQL學習指南》中的第11章
1.1 概念:
條件邏輯:條件邏輯是程序執行時從多個路徑中選取其一的能力
1)簡單例子演示:
例子1:查詢客戶信息時根據客戶類型從individual表中檢索fname/lname列或者從business表中
獲取name列 (左外連接)
SELECT c.cust_id,c.fed_id,c.cust_type_cd,
CONCAT(i.fname,' ',i.lname) AS indv_name,
b.`name` AS business_name
FROM customer c
LEFT JOIN individual i
ON c.cust_id = i.cust_id
LEFT JOIN business b
ON c.cust_id = b.cust_id;
結果如圖所示

例子2:查詢客戶信息時根據客戶類型從individual表中檢索fname/lname列或者從business表中
獲取name列 (使用case表達式使用條件邏輯決定客戶類型,進而返回恰當的字符串)
SELECT c.cust_id, c.fed_id,
CASE
WHEN c.cust_type_cd = 'I'
THEN CONCAT(i.fname,' ',i.fname)
WHEN c.cust_type_cd = 'B'
THEN b.`name`
ELSE 'Unkown'
END AS `name` -- 給這個條件判斷的結果取個別名
FROM customer c
LEFT JOIN individual i
ON c.cust_id = i.cust_id
LEFT JOIN business b
ON c.cust_id = b.cust_id;
結果如圖所示

分析:查詢只返回由case表達式生成的單個name列,這個從查詢的第二行起的case表達式首先檢查cust_type_cd列的值,然后依據該值決定返回個人名稱還是企業名稱。
1.2 case表達式
主流的數據庫服務器中模擬條件判斷的內置函數包括:Oracle的decode()函數,MySQL中if()函數以及SQL Server的coalesce()函數。
case表達式作為一種條件邏輯表達式,具備以下特點:
case表達式是SQL標准的一部分(SQL92),並且在多種數據庫中實現;
case表達式已經內置於SQL語法,可以用於select,insert,update和delete語句。
下面介紹兩種不同類型的case表達式
1)查找型case表達式,其語法如下:
CASE
WHEN C1 THEN E1
WHEN C2 THEN E2
....
[ELSE ED]
END AS 別名
其中C1,C2....代表條件,E1,E2....表示case表達式返回的表達式結果,else子句是可選的。
注意:case表達式返回的類型可以為日期型,數字性,字符串類型等,但是同一個case表達式中每個THEN返回的表達式結果必須相同
例子1:使用子查詢代替外連接從individual和business表中檢索個人名稱/企業名稱
SELECT c.cust_id,c.fed_id,
CASE
WHEN c.cust_type_cd = 'I'
THEN (
SELECT CONCAT(i.fname,' ',i.lname)
FROM individual i
WHERE i.cust_id = c.cust_id
)
WHEN c.cust_type_cd = 'B'
THEN (
SELECT b.`name`
FROM business b
WHERE b.cust_id = c.cust_id
)
END AS `name`
FROM customer c
結果如圖所示

2)簡單case表達式,其語法如下:
CASE V0
WHEN V1 THEN E1
WHEN V2 THEN E2
.....
[ELSE END]
END
簡單case表達式主要是通過自動構建等式條件,通過對V1,V2....與V0的值進行匹配,然后返回相應的表達式結果
例子1.下面修改1.1中例子1那個查找型case表達式
SELECT c.cust_id, c.fed_id,
CASE
c.cust_type_cd
WHEN 'I'
THEN CONCAT(i.fname,' ',i.fname)
WHEN 'B'
THEN b.`name`
ELSE 'Unkown'
END AS `name` -- 給這個條件判斷的結果取個別名
FROM customer c
LEFT JOIN individual i
ON c.cust_id = i.cust_id
LEFT JOIN business b
ON c.cust_id = b.cust_id;
結果如圖所示

分析:上面的例子將查找型case表達式轉化成簡單case表達式,注意這里由於查找型case表達式的條件單一,這樣轉換並不會出現什么問題,但是在范圍條件,不等條件以及基於and/or/not這些運算符的復合條件對於簡單case條件並不適用。
1.3 case表達式用途
case表達式適用於那些場景:1.結果集變換,2.選擇性聚合,3.存在性檢查,4.除0失誤,5.有條件更新,6.null值處理
1)結果集變換
結果集變換:對結果集的顯示形式進行變換,如多行轉列
例子1.查詢展示從2000年到2005年每年的開戶數目:
SELECT YEAR(a.open_date) `YEAR`,
COUNT(*) YearCount
FROM account a
WHERE (a.open_date >= '2000-01-01'
AND a.open_date <= '2005-12-30'
)
GROUP BY YEAR(a.open_date);
結果如圖所示

例子2.將上面的結果變換成單行多列顯示
SELECT
SUM(
CASE
WHEN EXTRACT(YEAR FROM a.open_date) = 2000
THEN 1
ELSE 0
END ) year_2000,
SUM(
CASE
WHEN EXTRACT(YEAR FROM a.open_date) = 2001
THEN 1
ELSE 0
END ) year_2001,
SUM(
CASE
WHEN EXTRACT(YEAR FROM a.open_date) = 2002
THEN 1
ELSE 0
END ) year_2002,
SUM(
CASE
WHEN EXTRACT(YEAR FROM a.open_date) = 2003
THEN 1
ELSE 0
END ) year_2003,
SUM(
CASE
WHEN EXTRACT(YEAR FROM a.open_date) = 2004
THEN 1
ELSE 0
END ) year_2004,
SUM(
CASE
WHEN EXTRACT(YEAR FROM a.open_date) = 2005
THEN 1
ELSE 0
END ) year_2005
FROM account a;
結果如圖所示

分析:這種少量數據的由行轉列的可以這樣實現,但是當行數過多時,那就要用到后面的解決辦法了。
2)選擇性聚合
選擇性聚合:通過判斷條件進行對某些數據進行查找,篩選,聚合
例子1.查找account表中那些賬戶余額與transaction表中賬戶余額,代收余額不相符的地方。
分析:
1)由於交易賬戶總是正的,所以讀者需要查看交易類型是借款('DBT')還是存款('CBT'),借款則應該將金額數變成負的(乘以-1);
2) 如果funds_avail_date列中的日期大於當前日期(未到期),交易應該被加到代收余額總和,而不是可用余額總和;
3)同時,有些交易需要被排除在可用余額之外,而所有交易應該都被包含在代收余額之內。
SELECT a.account_id AS unbalance_account_id
FROM account a
WHERE (a.avail_balance, a.pending_balance) <>( -- avail_balance賬戶余額,pending_balance 代收余額
SELECT
SUM(
CASE
WHEN t.funds_avail_date > CURRENT_TIMESTAMP()
THEN 0
WHEN t.txn_type_cd = 'DBT'
THEN t.amount * -1
ELSE t.amount
END
),
SUM(
CASE
WHEN t.txn_type_cd = 'DBT'
THEN t.amount * -1
ELSE t.amount
END
)
FROM `transaction` t
WHERE t.account_id = a.account_id
)
結果如圖所示

3)存在性檢查
存在性檢測:對某些數據進行是否存在進行判斷,或者對數據量進行統計
例子1.查詢客戶是否存在支票賬戶或者儲蓄賬戶
SELECT c.cust_id,c.fed_id,c.cust_type_cd,
CASE
WHEN EXISTS(
SELECT 1 FROM account a
WHERE a.cust_id = c.cust_id
AND a.product_cd = 'CHK'
)
THEN 'Y'
ELSE 'N'
END AS has_checking
,
CASE
WHEN EXISTS(
SELECT 1 FROM account a
WHERE a.cust_id = c.cust_id
AND a.product_cd = 'SAV'
)
THEN 'Y'
ELSE 'N'
END AS has_saving
FROM customer c;
結果如圖所示

分析: 每個case表達式包含了一個對account表的關聯子查詢:一個查找支票賬戶,另一個查找儲蓄賬戶。
由於每一個when子句都使用了exists運算符,因此只要客戶至少存在一個相應的賬戶那么條件為真
例子2. 使用簡單case表達式為每個客戶計算賬戶數目,然后返回None,1,2,3+
SELECT
CASE COUNT(a.account_id)
WHEN 0 THEN 'None'
WHEN 1 THEN '1'
WHEN 2 THEN '2'
ELSE '3+'
END AS AccountCount
,c.cust_id
FROM customer c
RIGHT JOIN account a
ON c.cust_id = a.cust_id
GROUP BY c.cust_id;
結果如圖所示

4)除0錯誤
除0錯誤檢測
:執行除法運算時避免分母為0的情況,進行判斷。同時不同數據庫對除0出錯進行不同處理方法,Oracle在遇到0分母時會拋出一個錯誤,而MySQL只是簡單的將結果值置為null.
例子1.查詢計算同一產品類型的所有賬戶的每個賬戶余額與總余額的比率
SELECT a.product_cd,SUM(a.avail_balance)
FROM account a
GROUP BY a.product_cd;
SELECT an.cust_id,an.product_cd,an.avail_balance/
CASE
WHEN newCount.totalAvail = 0 THEN 1
ELSE newCount.totalAvail
END AS rate
FROM account an INNER JOIN (
SELECT a.product_cd,SUM(a.avail_balance) AS totalAvail
FROM account a
GROUP BY a.product_cd
) AS newCount
ON an.product_cd = newCount.product_cd;
結果如圖所示

5)有條件更新
有條件更新:更新表中的行時,常常需要指定的列應該置什么值,但這個值往往需要根據其他的表中數值進行判斷,才對該值進行更新。
例子1.假定插入一個ID為999的一個交易,但此時需要修改account表中avail_balance,prending_balance和last_activity_date這3列的值,后兩個值比較容易更新,更新avavil_balance列則必須檢查transaction表的funds_avail_date列判斷交易資金是否立即可以使用。
UPDATE account a
SET a.last_activity_date = CURRENT_TIMESTAMP(),
a.pending_balance = a.pending_balance + (
SELECT t.amount*
CASE
WHEN t.txn_type_cd = 'DBT'
THEN -1
ELSE 1
END
FROM `transaction` t
WHERE t.txn_id = 22
),
a.avail_balance = a.avail_balance + (
SELECT t.amount*
CASE
WHEN t.funds_avail_date > CURRENT_TIMESTAMP() THEN 0
WHEN t.txn_type_cd = 'DBT' THEN -1
ELSE 1
END
FROM `transaction` t
WHERE t.txn_id = 22
)
WHERE a.account_id = (
SELECT t.account_id
FROM `transaction` t
WHERE t.txn_id = 22
)
修改之前的數據:

修改之后的數據:

在transaction中插入的那條數據

分析:這個語句共包含2個case語句,第一個case表達式對交易賬戶金額進行判斷,是否是存款還是借款
第二個case表達式進行兩種判斷,首先用於檢查資金的可用性日期,如果日期是未來,則只對可用余額加0
否則,返回1;然后
對交易賬戶金額進行判斷,是否是存款還是借款,存款返回1,借款返回-1。
6)null值處理
null值處理:null是某列的值未知時存儲到表中的值,不過檢索時顯示null值或者null參與表達式運算會出現錯誤
樣例: SELECT <some calculation> +
CASE
WHEN avail_balance IS NULL THEN 0
ELSE avail_balance
END
+
<some calculation>