PostgreSQL NULL 值

PostgreSQL NULL 值完全指南

NULL 是“未知”或“缺失”的代名词,不是 0、不是空字符串,行为特殊。
本文涵盖 NULL 的本质、三值逻辑、与函数交互、排序、索引、聚合、JSON/数组处理、性能陷阱、最佳实践 等全部内容。


一、NULL 的本质

误解真相
NULL0不是
NULL 是空字符串 ''不是
NULL = NULL不是NULL 代表“未知”,未知 ≠ 未知
SELECT NULL = NULL;    -- 返回 UNKNOWN(不是 TRUE)
SELECT NULL IS NULL;   -- TRUE
SELECT NULL = 0;       -- UNKNOWN
SELECT '' = NULL;      -- UNKNOWN

二、三值逻辑(Three-Valued Logic)

ABA AND BA OR BNOT A
TRUETRUETRUETRUEFALSE
TRUEFALSEFALSETRUE
TRUENULLNULLTRUE
FALSEFALSEFALSEFALSETRUE
FALSENULLFALSENULL
NULLNULLNULLNULLNULL

关键WHERE 条件为 UNKNOWN不返回该行

-- 错误:不会匹配 NULL
SELECT * FROM employees WHERE email = NULL;

-- 正确
SELECT * FROM employees WHERE email IS NULL;

三、准备测试数据

CREATE TABLE employees (
    id BIGSERIAL PRIMARY KEY,
    name TEXT,
    email TEXT,
    salary NUMERIC(10,2),
    department TEXT,
    tags TEXT[],
    config JSONB,
    is_active BOOLEAN,
    hire_date DATE
);

INSERT INTO employees (name, email, salary, department, tags, config, is_active, hire_date) VALUES
('Alice', 'alice@company.com', 85000, 'IT', '{"dev","lead"}', '{"level": 5}', true, '2023-01-15'),
('Bob',   NULL,               52000, 'HR', '{"hr"}',         '{"level": 3}', true, '2022-06-10'),
('Carol', 'carol@company.com', NULL, 'IT', '{"dev","ai"}',   '{"level": 6}', false, NULL),
('Dave',  NULL,               NULL, NULL, '{}',             '{}',           NULL,  NULL),
('Eve',   'eve@company.com',  48000, 'HR', NULL,             NULL,           true, '2023-12-01');

四、NULL 与函数交互

函数NULL 行为
COALESCE(a, b)返回第一个非 NULL
NULLIF(a, b)a = bNULL,否则 a
GREATEST(), LEAST()忽略 NULL
算术运算 + - * /结果为 NULL
字符串拼接 ||NULL 使整串为 NULL
-- 安全取值
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;

五、NULLORDER 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 与聚合函数

函数NULL 行为
COUNT(*)统计所有行(含 NULL
COUNT(col)跳过 NULL
SUM(), AVG()跳过 NULL
MIN(), MAX()跳过 NULL
STRING_AGG()跳过 NULL
-- 统计有邮箱的员工数
SELECT COUNT(email) FROM employees;  -- 3(跳过 NULL)

-- 总薪资(跳过 NULL)
SELECT SUM(salary) FROM employees;  -- 85000 + 52000 + 48000 = 185000

七、NULL 与索引

索引类型NULL 行为
B 树索引包含 NULL
唯一索引NULL 不参与唯一性(可多个 NULL
表达式索引可建在 COALESCE(col, 0)
-- 建唯一索引,允许多个 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 表中:

  1. 查询 无邮箱无部门 的员工
  2. 显示 薪资NULL 显示 'Unknown'
  3. 统计 有薪资记录 的员工平均薪资
  4. 薪资降序 排序,NULL 排最后
  5. 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;

需要我生成:

  • 1000 条含 NULL 测试数据?回复 测试数据
  • 自动 NULL 安全封装函数?回复 NULL 函数
  • 软删除 vs NULL 设计对比?回复 软删除设计
  • EXPLAIN 可视化分析?回复 EXPLAIN 分析

随时告诉我!

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注