第11章 条件逻辑
# 第11章 条件逻辑
在某些情形下,你可能希望SQL逻辑能依据特定列或表达式的值,朝不同方向进行分支处理。本章聚焦于如何编写能够在执行过程中,依据所遇到的数据而展现出不同行为的语句。
# 什么是条件逻辑?
条件逻辑指的是程序在执行期间,从多条路径中选择其一的能力。举例来说,在查询客户信息时,你可能会依据遇到的客户类型,从individual
表中检索fname
/lname
列,或者从business
表中检索name
列。使用外连接(outer joins)的话,你可以返回两个字符串,让调用者去决定使用哪一个,就像这样:
mysql> SELECT c.cust_id,
-> c.fed_id,
-> c.cust_type_cd,
-> CONCAT(i.fname, ' ', i.lname) indiv_name,
-> b.name business_name
-> FROM customer c
-> LEFT OUTER JOIN individual i
-> ON c.cust_id = i.cust_id
-> LEFT OUTER JOIN business b
-> ON c.cust_id = b.cust_id;
+---------+-------------+--------------+-----------------+------------------------+
| cust_id | fed_id | cust_type_cd | indiv_name | business_name |
+---------+-------------+--------------+-----------------+------------------------+
| 1 | 111-11-1111 | I | James Hadley | NULL |
| 2 | 222-22-2222 | I | Susan Tingley | NULL |
| 3 | 333-33-3333 | I | Frank Tucker | NULL |
| 4 | 444-44-4444 | I | John Hayward | NULL |
| 5 | 555-55-5555 | I | Charles Frasier | NULL |
| 6 | 666-66-6666 | I | John Spencer | NULL |
| 7 | 777-77-7777 | I | Margaret Young | NULL |
| 8 | 888-88-8888 | I | Louis Blake | NULL |
| 9 | 999-99-9999 | I | Richard Farley | NULL |
| 10 | 04-1111111 | B | NULL | Chilton Engineering |
| 11 | 04-2222222 | B | NULL | Northeast Cooling Inc. |
| 12 | 04-3333333 | B | NULL | Superior Auto Body |
| 13 | 04-4444444 | B | NULL | AAA Insurance Inc. |
+---------+-------------+--------------+-----------------+------------------------+
13 rows in set (0.13 sec)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
调用者可以查看cust_type_cd
列的值,然后决定使用indiv_name
列还是business_name
列。不过,你也可以借助CASE
表达式运用条件逻辑,来判定客户类型并返回合适的字符串,如下所示:
mysql> SELECT c.cust_id,
-> c.fed_id,
-> CASE
-> WHEN c.cust_type_cd = 'I'
-> THEN CONCAT(i.fname, ' ', i.lname)
-> WHEN c.cust_type_cd = 'B'
-> THEN b.name
-> ELSE 'Unknown'
-> END name
-> FROM customer c
-> LEFT OUTER JOIN individual i
-> ON c.cust_id = i.cust_id
-> LEFT OUTER JOIN business b
-> ON c.cust_id = b.cust_id;
+---------+-------------+------------------------+
| cust_id | fed_id | name |
+---------+-------------+------------------------+
| 1 | 111-11-1111 | James Hadley |
| 2 | 222-22-2222 | Susan Tingley |
| 3 | 333-33-3333 | Frank Tucker |
| 4 | 444-44-4444 | John Hayward |
| 5 | 555-55-5555 | Charles Frasier |
| 6 | 666-66-6666 | John Spencer |
| 7 | 777-77-7777 | Margaret Young |
| 8 | 888-88-8888 | Louis Blake |
| 9 | 999-99-9999 | Richard Farley |
| 10 | 04-1111111 | Chilton Engineering |
| 11 | 04-2222222 | Northeast Cooling Inc. |
| 12 | 04-3333333 | Superior Auto Body |
| 13 | 04-4444444 | AAA Insurance Inc. |
+---------+-------------+------------------------+
13 rows in set (0.00 sec)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
这个版本的查询返回一个名为name
的单列,该列由查询第二行开始的CASE
表达式生成。在这个例子中,CASE
表达式会检查cust_type_cd
列的值,然后返回个人的名和姓,或者企业名称。
# CASE表达式
所有主流数据库服务器都包含内置函数,这些函数旨在模仿大多数编程语言中的if-then-else
语句(例如,Oracle的decode()
函数、MySQL的if()
函数以及SQL Server的coalesce()
函数)。CASE
表达式同样是为了实现if-then-else
逻辑而设计的,但相较于内置函数,它具有两个优势:
CASE
表达式是SQL标准(SQL92版本)的一部分,并且已在Oracle数据库、SQL Server、MySQL、Sybase、PostgreSQL、IBM UDB等数据库中得以实现。CASE
表达式内置于SQL语法中,可以在SELECT
、INSERT
、UPDATE
和DELETE
语句中使用。
接下来的两个小节将介绍两种不同类型的CASE
表达式,然后为你展示一些实际运用CASE
表达式的示例。
# 搜索型CASE表达式
本章前面展示的CASE
表达式属于搜索型CASE
表达式,其语法如下:
CASE
WHEN C1 THEN E1
WHEN C2 THEN E2
...
WHEN CN THEN EN
[ELSE ED]
END
2
3
4
5
6
7
在上述定义中,符号C1
、C2
、...、CN
代表条件,符号E1
、E2
、...、EN
代表CASE
表达式要返回的表达式。如果WHEN
子句中的条件求值结果为真,那么CASE
表达式就会返回相应的表达式。此外,符号ED
代表默认表达式,当C1
、C2
、...、CN
中没有一个条件求值为真时,CASE
表达式将返回该默认表达式(ELSE
子句是可选的,所以用方括号括起来)。各个WHEN
子句返回的所有表达式必须求值为相同的数据类型(例如,日期、数字、可变长度字符串varchar
)。
以下是一个搜索型CASE
表达式的示例:
CASE
WHEN employee.title = 'Head Teller' THEN 'Head Teller'
WHEN employee.title = 'Teller'
AND YEAR(employee.start_date) > 2007 THEN 'Teller Trainee'
WHEN employee.title = 'Teller'
AND YEAR(employee.start_date) < 2006 THEN 'Experienced Teller'
WHEN employee.title = 'Teller' THEN 'Teller'
ELSE 'Non-Teller'
END
2
3
4
5
6
7
8
9
这个CASE
表达式返回一个字符串,可用于确定小时工资标准、打印员工名牌等等。在计算CASE
表达式时,WHEN
子句会按照从上到下的顺序进行求值;一旦某个WHEN
子句中的条件求值为真,就会返回相应的表达式,并且剩余的WHEN
子句将被忽略。如果没有一个WHEN
子句的条件求值为真,那么就会返回ELSE
子句中的表达式。
尽管前面的示例返回的是字符串表达式,但要记住,CASE
表达式可以返回任何类型的表达式,其中也包括子查询。下面是本章前面个人/企业名称查询的另一个版本,它使用子查询而非外连接,从individual
表和business
表中检索数据:
mysql> 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
)
-> ELSE 'Unknown'
-> END name
-> FROM customer c;
+---------+-------------+------------------------+
| cust_id | fed_id | name |
+---------+-------------+------------------------+
| 1 | 111-11-1111 | James Hadley |
| 2 | 222-22-2222 | Susan Tingley |
| 3 | 333-33-3333 | Frank Tucker |
| 4 | 444-44-4444 | John Hayward |
| 5 | 555-55-5555 | Charles Frasier |
| 6 | 666-66-6666 | John Spencer |
| 7 | 777-77-7777 | Margaret Young |
| 8 | 888-88-8888 | Louis Blake |
| 9 | 999-99-9999 | Richard Farley |
| 10 | 04-1111111 | Chilton Engineering |
| 11 | 04-2222222 | Northeast Cooling Inc. |
| 12 | 04-3333333 | Superior Auto Body |
| 13 | 04-4444444 | AAA Insurance Inc. |
+---------+-------------+------------------------+
13 rows in set (0.01 sec)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
这个版本的查询在FROM
子句中仅包含customer
表,并使用相关子查询为每个客户检索合适的名称。相较于本章前面的外连接版本,我更倾向于这个版本,因为服务器仅在有需要时才会从individual
表和business
表中读取数据,而不是始终连接所有三个表。
# 简单CASE表达式
简单CASE
表达式与搜索型CASE
表达式非常相似,但灵活性稍差。其语法如下:
CASE V0
WHEN V1 THEN E1
WHEN V2 THEN E2
...
WHEN VN THEN EN
[ELSE ED]
END
2
3
4
5
6
7
在上述定义中,V0
代表一个值,符号V1
、V2
、...、VN
代表要与V0
进行比较的值。符号E1
、E2
、...、EN
代表CASE
表达式要返回的表达式,ED
代表当V1
、V2
、...、VN
中没有一个值与V0
匹配时要返回的表达式。
以下是一个简单CASE
表达式的示例:
CASE customer.cust_type_cd
WHEN 'I' THEN
(SELECT CONCAT(i.fname, ' ', i.lname)
FROM individual I
WHERE i.cust_id = customer.cust_id)
WHEN 'B' THEN
(SELECT b.name
FROM business b
WHERE b.cust_id = customer.cust_id)
ELSE 'Unknown Customer Type'
END
2
3
4
5
6
7
8
9
10
11
简单CASE
表达式的功能比搜索型CASE
表达式弱,因为你不能指定自己的条件,而是内置了相等条件。为了让你明白我的意思,下面是一个与前面简单CASE
表达式逻辑相同的搜索型CASE
表达式:
CASE
WHEN customer.cust_type_cd = 'I' THEN
(SELECT CONCAT(i.fname, ' ', i.lname)
FROM individual I
WHERE i.cust_id = customer.cust_id)
WHEN customer.cust_type_cd = 'B' THEN
(SELECT b.name
FROM business b
WHERE b.cust_id = customer.cust_id)
ELSE 'Unknown Customer Type'
END
2
3
4
5
6
7
8
9
10
11
使用搜索型CASE
表达式,你可以构建范围条件、不等条件,以及使用AND
/OR
/NOT
的多部分条件。因此,除了最简单的逻辑外,我建议在所有情况下都使用搜索型CASE
表达式。
# CASE表达式示例
以下部分展示了各种示例,说明了条件逻辑在SQL语句中的实用性。
# 结果集转换
你可能遇到过这样的情况:对一组有限的值(例如一周中的每一天)进行聚合操作,但你希望结果集包含一行数据,每个值对应一列,而不是每个值对应一行。例如,假设要求你编写一个查询,显示2000年到2005年开设的账户数量:
mysql> SELECT YEAR(open_date) year,
-> COUNT(*) how_many
-> FROM account
-> WHERE open_date > '1999-12-31'
-> AND open_date < '2006-01-01'
-> GROUP BY YEAR(open_date);
+------+----------+
| year | how_many |
+------+----------+
| 2000 | 3 |
| 2001 | 4 |
| 2002 | 5 |
| 2003 | 3 |
| 2004 | 9 |
+------+----------+
5 rows in set (0.00 sec)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
然而,你还被要求返回一行数据,包含六列(数据范围内的每一年对应一列)。为了将这个结果集转换为一行,你需要创建六列,并且在每一列中,只对与该年份相关的行进行求和:
mysql> SELECT
-> SUM(
CASE
WHEN EXTRACT(YEAR FROM open_date) = 2000 THEN 1
ELSE 0
END
) year_2000,
-> SUM(
CASE
WHEN EXTRACT(YEAR FROM open_date) = 2001 THEN 1
ELSE 0
END
) year_2001,
-> SUM(
CASE
WHEN EXTRACT(YEAR FROM open_date) = 2002 THEN 1
ELSE 0
END
) year_2002,
-> SUM(
CASE
WHEN EXTRACT(YEAR FROM open_date) = 2003 THEN 1
ELSE 0
END
) year_2003,
-> SUM(
CASE
WHEN EXTRACT(YEAR FROM open_date) = 2004 THEN 1
ELSE 0
END
) year_2004,
-> SUM(
CASE
WHEN EXTRACT(YEAR FROM open_date) = 2005 THEN 1
ELSE 0
END
) year_2005
-> FROM account
-> WHERE open_date > '1999-12-31' AND open_date < '2006-01-01';
+-----------+-----------+-----------+-----------+-----------+-----------+
| year_2000 | year_2001 | year_2002 | year_2003 | year_2004 | year_2005 |
+-----------+-----------+-----------+-----------+-----------+-----------+
| 3 | 4 | 5 | 3 | 9 | 0 |
+-----------+-----------+-----------+-----------+-----------+-----------+
1 row in set (0.01 sec)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
前面查询中的六列,除了年份值不同,其他都相同。当EXTRACT()
函数返回该列所需的年份时,CASE
表达式返回值1;否则,返回0。对2000年以来开设的所有账户进行求和时,每一列返回的是该年份开设的账户数量。显然,这种转换只适用于少量的值;如果为1905年以来的每一年都生成一列,很快就会变得繁琐。
虽然这对本书来说有点高级,但值得指出的是,SQL Server和Oracle Database 11g都包含
PIVOT
子句,专门用于这类查询。
# 选择性聚合
在第9章中,我给出了一个示例的部分解决方案,该示例展示了如何找出账户余额与交易表中的原始数据不一致的账户。给出部分解决方案是因为完整的解决方案需要用到条件逻辑,现在所有要素都已具备,可以完成这项任务了。以下是我在第9章结束时留下的内容:
SELECT CONCAT('ALERT! : Account #', a.account_id, ' Has Incorrect Balance!')
FROM account a
WHERE (a.avail_balance, a.pending_balance) <>
(SELECT SUM(<expression to generate available balance>), SUM(<expression to generate pending balance>)
FROM transaction t
WHERE t.account_id = a.account_id);
2
3
4
5
6
该查询对交易表使用了一个相关子查询,将特定账户的各个交易金额加总。在对交易金额求和时,需要考虑以下两个问题:
- 交易金额始终为正数,所以你需要查看交易类型,以判断该交易是借方(debit)还是贷方(credit),对于借方交易要反转符号(乘以 -1 )。
- 如果资金可用日期(funds_avail_date)列中的日期大于当前日期,该笔交易应计入未决余额(pending balance)总计,但不计入可用余额(available balance)总计。
虽然部分交易需要从可用余额中排除,但所有交易都包含在未决余额中,这使得未决余额的计算相对简单。以下是用于计算未决余额的CASE表达式:
CASE
WHEN transaction.txn_type_cd = 'DBT' THEN transaction.amount * −1
ELSE transaction.amount END
2
3
因此,对于借方交易,所有交易金额都乘以 -1,而对于贷方交易则保持不变。同样的逻辑也适用于可用余额的计算,但仅应包含已到账的交易。所以,用于计算可用余额的CASE表达式多了一个WHEN子句:
CASE
WHEN transaction.funds_avail_date > CURRENT_TIMESTAMP() THEN 0
WHEN transaction.txn_type_cd = 'DBT' THEN transaction.amount * −1
ELSE transaction.amount END
2
3
4
有了第一个WHEN子句,未到账的资金(比如尚未兑现的支票)对总和的贡献为0美元。以下是包含两个CASE表达式的最终查询:
SELECT CONCAT('ALERT! : Account #', a.account_id, ' Has Incorrect Balance!')
FROM account a
WHERE (a.avail_balance, a.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);
2
3
4
5
6
7
8
9
10
11
12
通过使用条件逻辑,两个CASE表达式为SUM()聚合函数提供经过处理的数据,从而实现对相应金额的求和。
# 检查是否存在
有时,你可能想要确定两个实体之间是否存在某种关系,而无需考虑数量。例如,你可能想知道某个客户是否拥有支票账户或储蓄账户,但并不关心该客户每种类型的账户有多少个。下面的查询使用多个CASE表达式生成两个输出列,一个显示客户是否拥有支票账户,另一个显示客户是否拥有储蓄账户:
mysql> 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 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 has_savings
-> FROM customer c;
+---------+-------------+--------------+--------------+-------------+
| cust_id | fed_id | cust_type_cd | has_checking | has_savings |
+---------+-------------+--------------+--------------+-------------+
| 1 | 111-11-1111 | I | Y | Y |
| 2 | 222-22-2222 | I | Y | Y |
| 3 | 333-33-3333 | I | Y | N |
| 4 | 444-44-4444 | I | Y | Y |
| 5 | 555-55-5555 | I | Y | N |
| 6 | 666-66-6666 | I | Y | N |
| 7 | 777-77-7777 | I | N | N |
| 8 | 888-88-8888 | I | Y | Y |
| 9 | 999-99-9999 | I | Y | N |
| 10 | 04-1111111 | B | Y | N |
| 11 | 04-2222222 | B | N | N |
| 12 | 04-3333333 | B | Y | N |
| 13 | 04-4444444 | B | N | N |
+---------+-------------+--------------+--------------+-------------+
13 rows in set (0.00 sec)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
每个CASE表达式都包含一个针对账户表的相关子查询;一个用于查找支票账户,另一个用于查找储蓄账户。由于每个WHEN子句都使用了EXISTS运算符,只要客户至少拥有一个所需类型的账户,条件就会被评估为真。
在其他情况下,你可能会关心遇到的行数,但仅在一定程度上。例如,下一个查询使用一个简单的CASE表达式来计算每个客户的账户数量,然后返回“None”、“1”、“2”或“3+”:
mysql> SELECT c.cust_id,
-> c.fed_id,
-> c.cust_type_cd,
-> CASE (
SELECT COUNT(*)
FROM account a
WHERE a.cust_id = c.cust_id
)
WHEN 0 THEN 'None'
WHEN 1 THEN '1'
WHEN 2 THEN '2'
ELSE '3+'
END num_accounts
-> FROM customer c;
+---------+-------------+--------------+--------------+
| cust_id | fed_id | cust_type_cd | num_accounts |
+---------+-------------+--------------+--------------+
| 1 | 111-11-1111 | I | 3+ |
| 2 | 222-22-2222 | I | 2 |
| 3 | 333-33-3333 | I | 2 |
| 4 | 444-44-4444 | I | 3+ |
| 5 | 555-55-5555 | I | 1 |
| 6 | 666-66-6666 | I | 2 |
| 7 | 777-77-7777 | I | 1 |
| 8 | 888-88-8888 | I | 2 |
| 9 | 999-99-9999 | I | 3+ |
| 10 | 04-1111111 | B | 2 |
| 11 | 04-2222222 | B | 1 |
| 12 | 04-3333333 | B | 1 |
| 13 | 04-4444444 | B | 1 |
+---------+-------------+--------------+--------------+
13 rows in set (0.01 sec)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
对于这个查询,我不想区分拥有两个以上账户的客户,所以CASE表达式只是创建了一个“3+”类别。如果你正在寻找可能需要联系以开设新银行账户的客户,这样的查询可能会很有用。
# 除零错误
在进行包含除法的计算时,你始终要注意确保分母永远不为零。有些数据库服务器(如Oracle数据库)在遇到分母为零时会抛出错误,而MySQL则只是将计算结果设置为NULL,如下所示:
mysql> SELECT 100 / 0;
+---------+
| 100 / 0 |
+---------+
| NULL |
+---------+
1 row in set (0.00 sec)
2
3
4
5
6
7
为了避免计算出错,或者更糟糕的是,避免计算结果被莫名其妙地设为NULL,你应该使用条件逻辑将所有分母包裹起来,如下所示:
mysql> SELECT a.cust_id,
-> a.product_cd,
-> a.avail_balance /
CASE
WHEN prod_tots.tot_balance = 0 THEN 1
ELSE prod_tots.tot_balance
END percent_of_total
-> FROM account a
-> INNER JOIN (
SELECT a.product_cd,
SUM(a.avail_balance) tot_balance
FROM account a
GROUP BY a.product_cd
) prod_tots
-> ON a.product_cd = prod_tots.product_cd;
+---------+------------+------------------+
| cust_id | product_cd | percent_of_total |
+---------+------------+------------------+
| 10 | BUS | 0.000000 |
| 11 | BUS | 1.000000 |
| 1 | CD | 0.153846 |
| 6 | CD | 0.512821 |
| 7 | CD | 0.256410 |
| 9 | CD | 0.076923 |
| 1 | CHK | 0.014488 |
| 2 | CHK | 0.030928 |
| 3 | CHK | 0.014488 |
| 4 | CHK | 0.007316 |
| 5 | CHK | 0.030654 |
| 6 | CHK | 0.001676 |
| 8 | CHK | 0.047764 |
| 9 | CHK | 0.001721 |
| 10 | CHK | 0.322911 |
| 12 | CHK | 0.528052 |
| 3 | MM | 0.129802 |
| 4 | MM | 0.321915 |
| 9 | MM | 0.548282 |
| 1 | SAV | 0.269431 |
| 2 | SAV | 0.107773 |
| 4 | SAV | 0.413723 |
| 8 | SAV | 0.209073 |
| 13 | SBL | 1.000000 |
+---------+------------+------------------+
24 rows in set (0.13 sec)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
这个查询计算了每个账户余额与相同产品类型的所有账户总余额的比率。由于某些产品类型(如商业贷款)如果所有贷款目前都已全额偿还,总余额可能为零,因此最好包含CASE表达式以确保分母永远不为零。
# 条件更新
在更新表中的行时,有时需要确定某些列应设置的值。例如,插入新交易记录后,需要修改账户表中的可用余额(avail_balance)、待处理余额(pending_balance)和上次活动日期(last_activity_date)列。虽然最后两列很容易更新,但要正确修改可用余额列,需要通过查看交易表中的资金可用日期(funds_avail_date)列,来确定交易资金是否能立即使用。假设刚刚插入了交易ID为999的记录,可以使用以下更新语句来修改账户表中的这三列:
UPDATE account
SET last_activity_date = CURRENT_TIMESTAMP(),
pending_balance = pending_balance + (SELECT t.amount *
CASE t.txn_type_cd
WHEN 'DBT' THEN -1
ELSE 1
END
FROM transaction t
WHERE t.txn_id = 999),
avail_balance = avail_balance + (SELECT
CASE
WHEN t.funds_avail_date > CURRENT_TIMESTAMP() THEN 0
ELSE t.amount *
CASE t.txn_type_cd
WHEN 'DBT' THEN -1
ELSE 1
END
END
FROM transaction t
WHERE t.txn_id = 999)
WHERE account.account_id = (SELECT t.account_id
FROM transaction t
WHERE t.txn_id = 999);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
此语句总共包含三个CASE表达式:其中两个(第5行和第13行)用于反转借方交易的交易金额符号,第三个CASE表达式(第10行)用于检查资金可用日期。如果日期在未来,则向可用余额添加零;否则,添加交易金额。
# 处理空值
如果某列的值未知,在表中存储空值是合适的,但检索空值用于显示或参与表达式运算并不总是合适的。例如,可能希望在数据输入屏幕上显示“未知”一词,而不是让字段留空。在检索数据时,可以使用CASE表达式,在值为空时替换字符串,如下所示:
SELECT emp_id, fname, lname, CASE
WHEN title IS NULL THEN 'Unknown'
ELSE title
END
FROM employee;
2
3
4
5
对于计算,空值通常会导致结果为空,如下所示:
mysql> SELECT (7 * 5) / ((3 + 14) * null);
+----------------------------------+
| (7 * 5) / ((3 + 14) * null) |
+----------------------------------+
| NULL |
+----------------------------------+
1 row in set (0.08 sec)
2
3
4
5
6
7
在进行计算时,CASE表达式可用于将空值转换为一个数字(通常是0或1),使计算能够产生非空结果。例如,如果进行的计算包含account.avail_balance列,可以为已创建但尚未注资的账户替换为0(如果进行加法或减法运算)或1(如果进行乘法或除法运算):
SELECT <some calculation> + CASE
WHEN avail_balance IS NULL THEN 0
ELSE avail_balance
END + <rest of calculation>
...
2
3
4
5
如果允许数字列包含空值,在任何包含该列的计算中使用条件逻辑通常是个好主意,这样结果才有用。
# 测试你的知识
通过以下示例挑战自己解决条件逻辑问题的能力。完成后,将你的解决方案与附录C中的内容进行比较。
# 练习11 - 1
重写以下使用简单CASE表达式的查询,使其使用搜索CASE表达式获得相同结果。尽量使用最少的WHEN子句。
SELECT emp_id, CASE title
WHEN 'President' THEN 'Management'
WHEN 'Vice President' THEN 'Management'
WHEN 'Treasurer' THEN 'Management'
WHEN 'Loan Manager' THEN 'Management'
WHEN 'Operations Manager' THEN 'Operations'
WHEN 'Head Teller' THEN 'Operations'
WHEN 'Teller' THEN 'Operations'
ELSE 'Unknown'
END
FROM employee;
2
3
4
5
6
7
8
9
10
11
# 练习11 - 2
重写以下查询,使结果集包含一行四列(每个分支一列)。将这四列命名为branch_1到branch_4。
mysql> SELECT open_branch_id, COUNT(*)
-> FROM account
-> GROUP BY open_branch_id;
+---------------+----------+
| open_branch_id | COUNT(*) |
+---------------+----------+
| 1 | 8 |
| 2 | 7 |
| 3 | 3 |
| 4 | 6 |
+---------------+----------+
4 rows in set (0.00 sec)
2
3
4
5
6
7
8
9
10
11
12