第 57.1 节:处理包含 NULL 值的列

在 MySQL 和其他 SQL 方言中,NULL 值具有特殊属性。

考虑以下表,包含求职者、他们工作过的公司以及离职日期。NULL 表示该求职者仍在该公司工作:

CREATE TABLE example (
    applicant_id INT,
    company_name VARCHAR(255),
    end_date DATE
);

数据示例:

applicant_id company_name end_date
1 Google NULL
1 Intech 2013-01-31
2 Woodworking.com 2016-08-25
2 NY Times 2013-11-10
3 NFL.com 2014-04-13

你的任务是编写一个查询,返回 2016-01-01 之后的所有行,包括仍在公司工作的员工(end_date 为 NULL 的行)。

以下查询:

SELECT * FROM example WHERE end_date > '2016-01-01';

未能包含任何 NULL 值的行:

applicant_id company_name end_date
2 Woodworking.com 2016-08-25

根据 MySQL 文档,使用算术运算符(<、>、=、<>)进行比较时,如果任一操作数为 NULL,结果返回 NULL 而非布尔值 TRUE 或 FALSE。因此,end_date 为 NULL 的行既不大于 2016-01-01 也不小于 2016-01-01。

可以通过使用 IS NULL 关键字解决:

SELECT * FROM example WHERE end_date > '2016-01-01' OR end_date IS NULL;

结果:

applicant_id company_name end_date
1 Google NULL
2 Woodworking.com 2016-08-25

当使用 MAX() 等聚合函数和 GROUP BY 子句时,处理 NULL 会变得更加复杂。如果任务是选择每个 applicant_id 的最近离职日期,以下查询似乎是合理的首次尝试:

SELECT applicant_id, MAX(end_date) FROM example GROUP BY applicant_id;

结果:

applicant_id MAX(end_date)
1 2013-01-31
2 2016-08-25
3 2014-04-13

但考虑到 NULL 表示仍在公司工作,第一行结果是不准确的。使用 CASE WHEN 可以提供解决 NULL 问题的方法:

SELECT
    applicant_id,
    CASE WHEN MAX(end_date IS NULL) = 1 THEN 'present' ELSE MAX(end_date) END AS max_date
FROM example
GROUP BY applicant_id;

结果:

applicant_id max_date
1 present
2 2016-08-25
3 2014-04-13

此结果可以再连接回原 example 表,以确定每个求职者最后工作的公司:

SELECT data.applicant_id, data.company_name, data.max_date
FROM (
    SELECT *, CASE WHEN end_date IS NULL THEN 'present' ELSE end_date END AS max_date
    FROM example
) data
INNER JOIN (
    SELECT
        applicant_id,
        CASE WHEN MAX(end_date IS NULL) = 1 THEN 'present' ELSE MAX(end_date) END AS max_date
    FROM example
    GROUP BY applicant_id
) j ON data.applicant_id = j.applicant_id AND data.max_date = j.max_date;

结果:

applicant_id company_name max_date
1 Google present
2 Woodworking.com 2016-08-25
3 NFL.com 2014-04-13