-- 安全取值
SELECT name, COALESCE(email, 'no-email@company.com') AS safe_email FROM employees;
-- 防止除零
SELECT salary, salary / NULLIF(bonus, 0) FROM employees;
-- 字符串拼接安全
SELECT name || ' (' || COALESCE(department, 'Unknown') || ')' FROM employees;
五、NULL 在 ORDER BY 中的排序
排序
NULL 位置
ASC
最后(默认 NULLS LAST)
DESC
最前(默认 NULLS LAST)
-- 薪资升序,NULL 排最后
SELECT name, salary FROM employees
ORDER BY salary ASC NULLS LAST;
-- 薪资降序,NULL 排最前
SELECT name, salary FROM employees
ORDER BY salary DESC NULLS FIRST;
-- 建唯一索引,允许多个 NULL
CREATE UNIQUE INDEX uq_email ON employees(email); -- 允许多个 NULL
-- PG15+:NULL 不重复
CREATE UNIQUE INDEX uq_email_nnd ON employees(email) NULLS NOT DISTINCT;
八、NULL 在 JSON 和数组中
1. JSON 字段
-- 检查键是否存在
SELECT * FROM employees WHERE config ? 'level';
-- 获取值,NULL 安全
SELECT config->>'level' FROM employees;
-- NULL → NULL
-- 安全转换
SELECT (config->>'level')::INT FROM employees;
-- 错误值 → NULL
2. 数组字段
-- 检查数组是否为空
SELECT * FROM employees WHERE tags = '{}'; -- 空数组
SELECT * FROM employees WHERE tags IS NULL; -- NULL
-- 数组长度(NULL → NULL)
SELECT array_length(tags, 1) FROM employees;
九、性能陷阱与优化
陷阱
说明
解决方案
WHERE col = NULL
不匹配
用 IS NULL
WHERE col != 'x'
NULL 被过滤
加 OR col IS NULL
聚合未处理 NULL
结果偏差
用 COALESCE
索引未覆盖 NULL 过滤
全表扫描
建表达式索引
-- 错误:漏掉 NULL
SELECT * FROM employees WHERE department != 'IT';
-- 正确
SELECT * FROM employees
WHERE department != 'IT' OR department IS NULL;
-- 表达式索引加速
CREATE INDEX idx_emp_salary_not_null ON employees(salary) WHERE salary IS NOT NULL;
十、最佳实践 Checklist
项目
建议
永远用 IS NULL / IS NOT NULL
避免 = NULL
优先加 NOT NULL 约束
减少 NULL
用 COALESCE / NULLIF 安全处理
防 NULL 爆炸
ORDER BY ... NULLS FIRST/LAST
控制排序
聚合前过滤 NULL
准确统计
唯一约束允许 NULL
按需用 NULLS NOT DISTINCT
建表达式索引
加速 NULL 过滤
十一、一键安全查询模板
-- 安全获取员工信息
SELECT
id,
name,
COALESCE(email, 'no-email@company.com') AS email,
COALESCE(salary, 0) AS salary,
COALESCE(department, 'Unassigned') AS department,
COALESCE(array_length(tags, 1), 0) AS tag_count,
COALESCE(config->>'level', '0')::INT AS level,
is_active,
hire_date
FROM employees
WHERE
(is_active IS NULL OR is_active = true)
AND (salary IS NULL OR salary > 0)
ORDER BY
salary DESC NULLS LAST,
hire_date ASC NULLS LAST;
十二、快速实战:5 分钟掌握 NULL
-- 1. 查找无邮箱员工
SELECT name FROM employees WHERE email IS NULL;
-- 2. 安全显示薪资
SELECT name, COALESCE(salary::TEXT, 'Unknown') FROM employees;
-- 3. 统计有薪资的员工平均薪资
SELECT AVG(salary) FROM employees WHERE salary IS NOT NULL;
-- 4. 排序:薪资降序,NULL 最后
SELECT name, salary FROM employees
ORDER BY salary DESC NULLS LAST;
-- 5. 建索引
CREATE INDEX idx_emp_email_not_null ON employees(email) WHERE email IS NOT NULL;
现在就动手: 在 employees 表中:
查询 无邮箱 或 无部门 的员工
显示 薪资,NULL 显示 'Unknown'
统计 有薪资记录 的员工平均薪资
按 薪资降序 排序,NULL 排最后
为 email IS NOT NULL 建表达式索引
十三、NULL 常见面试题
-- Q1: 以下查询返回几行?
SELECT * FROM employees WHERE salary > 50000 OR salary <= 50000;
-- A: 不包含 salary IS NULL 的行!
-- 正确写法:
SELECT * FROM employees WHERE salary IS NOT NULL;
十四、性能对比(EXPLAIN)
-- 慢:全表扫描
EXPLAIN ANALYZE SELECT * FROM employees WHERE email = NULL;
-- 快:用 IS NULL + 索引
CREATE INDEX idx_emp_email ON employees(email);
EXPLAIN ANALYZE SELECT * FROM employees WHERE email IS NULL;