Skip to content

PostgreSQL 条件表达式详解 ​

概述 ​

PostgreSQL 中的条件表达式是 SQL 标准的重要组成部分,它们提供了在查询中进行逻辑判断和值选择的强大功能。本章将详细介绍四种主要的条件表达式:CASE、COALESCE、NULLIF、GREATEST 和 LEAST。

TIP

使用建议如果您的需求超出了这些条件表达式的功能范围,您可能需要考虑使用更具表达力的编程语言编写服务器端函数。

WARNING

重要提醒尽管 COALESCE、GREATEST 和 LEAST 在语法上与函数类似,但它们不是普通函数,因此不能与显式的 VARIADIC 数组参数一起使用。

1. CASE 表达式 ​

1.1 基本概念 ​

CASE 表达式是 SQL 中最灵活的条件表达式,类似于其他编程语言中的 if/else 语句。它提供了两种语法形式:通用形式和简单形式。

1.2 通用 CASE 语法 ​

sql
CASE WHEN condition THEN result
     [WHEN condition THEN result ...]
     [ELSE result]
END

语法结构说明 ​

实际应用示例 ​

sql
-- 创建员工薪资表
CREATE TABLE employees (
    id SERIAL PRIMARY KEY,
    name VARCHAR(50),
    salary INTEGER,
    department VARCHAR(30)
);

-- 插入测试数据
INSERT INTO employees (name, salary, department) VALUES
('张三', 15000, '技术部'),
('李四', 8000, '销售部'),
('王五', 25000, '管理层'),
('赵六', 6000, '客服部');
sql
-- 根据薪资划分员工等级
SELECT
    name,
    salary,
    CASE
        WHEN salary >= 20000 THEN '高级'
        WHEN salary >= 10000 THEN '中级'
        WHEN salary >= 5000 THEN '初级'
        ELSE '实习生'
    END as salary_level
FROM employees;

查询结果:

namesalarysalary_level
张三15000中级
李四8000初级
王五25000高级
赵六6000初级

1.3 简单 CASE 语法 ​

sql
CASE expression
    WHEN value1 THEN result1
    [WHEN value2 THEN result2 ...]
    [ELSE result]
END

实际应用示例 ​

sql
-- 部门名称本地化
SELECT
    name,
    department,
    CASE department
        WHEN '技术部' THEN 'Technology'
        WHEN '销售部' THEN 'Sales'
        WHEN '管理层' THEN 'Management'
        WHEN '客服部' THEN 'Customer Service'
        ELSE 'Unknown'
    END as department_en
FROM employees;

查询结果:

namedepartmentdepartment_en
张三技术部Technology
李四销售部Sales
王五管理层Management
赵六客服部Customer Service

1.4 CASE 表达式的高级应用 ​

聚合函数中的条件统计 ​

sql
-- 统计不同薪资等级的员工数量
SELECT
    COUNT(*) as total_employees,
    COUNT(CASE WHEN salary >= 20000 THEN 1 END) as high_level,
    COUNT(CASE WHEN salary >= 10000 AND salary < 20000 THEN 1 END) as mid_level,
    COUNT(CASE WHEN salary < 10000 THEN 1 END) as low_level
FROM employees;

避免除零错误 ​

sql
-- 安全的除法运算
SELECT
    name,
    salary,
    CASE
        WHEN salary > 0 THEN (salary * 12) / salary  -- 年薪计算示例
        ELSE 0
    END as annual_multiplier
FROM employees;

WARNING

计算顺序注意事项如第 4.2.14 节中所述,在各种情况下,表达式的子表达式会在不同的时间进行计算,因此"CASE 仅计算必要的子表达式"的原则并非铁律。例如,即使常量 1/0 子表达式在运行时永远不会进入的 CASE 分支中,通常也会在计划时导致除零错误。

2. COALESCE 函数 ​

2.1 基本概念 ​

COALESCE 函数返回其参数中第一个非 NULL 的值,这在处理空值和提供默认值时非常有用。

2.2 语法结构 ​

sql
COALESCE(value1, value2, value3, ...)

工作流程图 ​

2.3 实际应用示例 ​

用户信息显示 ​

sql
-- 创建用户信息表
CREATE TABLE user_profiles (
    id SERIAL PRIMARY KEY,
    username VARCHAR(50),
    full_name VARCHAR(100),
    nickname VARCHAR(50),
    display_name VARCHAR(100)
);

-- 插入测试数据(包含各种 NULL 情况)
INSERT INTO user_profiles (username, full_name, nickname, display_name) VALUES
('john_doe', '约翰·多伊', NULL, NULL),
('jane_smith', NULL, '小简', NULL),
('bob_wilson', NULL, NULL, '鲍勃'),
('alice_brown', '爱丽丝·布朗', '小爱', '爱丽丝'),
('unknown_user', NULL, NULL, NULL);
sql
-- 智能选择显示名称:优先级 display_name > nickname > full_name > username
SELECT
    username,
    COALESCE(display_name, nickname, full_name, username, '匿名用户') as display_name
FROM user_profiles;

查询结果:

usernamedisplay_name
john_doe约翰·多伊
jane_smith小简
bob_wilson鲍勃
alice_brown爱丽丝
unknown_userunknown_user

配置参数默认值 ​

sql
-- 系统配置表
CREATE TABLE system_config (
    config_key VARCHAR(50),
    user_value TEXT,
    system_default TEXT
);

INSERT INTO system_config VALUES
('theme', NULL, 'light'),
('language', 'zh-CN', 'en-US'),
('timeout', NULL, '300');

-- 获取有效配置值
SELECT
    config_key,
    COALESCE(user_value, system_default, 'undefined') as effective_value
FROM system_config;

2.4 COALESCE 与其他数据库的兼容性 ​

INFO

数据库兼容性此 SQL 标准函数提供的功能类似于某些其他数据库系统中使用的 NVL(Oracle)和 IFNULL(MySQL)。

数据库系统等价函数示例
PostgreSQLCOALESCECOALESCE(a, b, c)
OracleNVL / NVL2NVL(a, b)
MySQLIFNULL / COALESCEIFNULL(a, b)
SQL ServerISNULL / COALESCEISNULL(a, b)

3. NULLIF 函数 ​

3.1 基本概念 ​

NULLIF 函数在两个值相等时返回 NULL,否则返回第一个值。它常用于将特定值转换为 NULL,是 COALESCE 的逆操作。

3.2 语法结构 ​

sql
NULLIF(value1, value2)

逻辑流程图 ​

3.3 实际应用示例 ​

数据清洗场景 ​

sql
-- 创建产品信息表
CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    description TEXT,
    category VARCHAR(50),
    status VARCHAR(20)
);

-- 插入测试数据(包含需要清洗的数据)
INSERT INTO products (name, description, category, status) VALUES
('笔记本电脑', '高性能办公笔记本', '电子产品', 'active'),
('无线鼠标', '(none)', '电子产品', 'active'),
('键盘', '', '电子产品', 'inactive'),
('显示器', 'N/A', '电子产品', 'active'),
('音响', '高品质音响设备', '电子产品', '(empty)');
sql
-- 将特殊标记值转换为 NULL
SELECT
    name,
    NULLIF(NULLIF(NULLIF(description, '(none)'), ''), 'N/A') as clean_description,
    NULLIF(status, '(empty)') as clean_status
FROM products;

查询结果:

nameclean_descriptionclean_status
笔记本电脑高性能办公笔记本active
无线鼠标NULLactive
键盘NULLinactive
显示器NULLactive
音响高品质音响设备NULL

统计分析中的应用 ​

sql
-- 计算非零销售额的平均值
CREATE TABLE sales_data (
    product_id INTEGER,
    sales_amount DECIMAL(10,2)
);

INSERT INTO sales_data VALUES
(1, 1000.00),
(2, 0.00),      -- 零销售额
(3, 1500.50),
(4, 0.00),      -- 零销售额
(5, 2000.00);

-- 计算平均值时排除零值
SELECT
    AVG(NULLIF(sales_amount, 0)) as avg_non_zero_sales,
    AVG(sales_amount) as avg_all_sales
FROM sales_data;

3.4 NULLIF 的数据类型处理 ​

Details

类型转换细节结果的类型与第一个参数相同——但存在一个微妙之处。实际返回的是隐含的 = 运算符的第一个参数,在某些情况下,该参数将被提升以匹配第二个参数的类型。

例如:NULLIF(1, 2.2) 产生 numeric 类型,因为没有 integer = numeric 运算符,只有 numeric = numeric。

sql
-- 演示类型转换
SELECT
    NULLIF(1, 2.2) as result,           -- 返回 numeric 类型的 1
    pg_typeof(NULLIF(1, 2.2)) as type_name;

4. GREATEST 和 LEAST 函数 ​

4.1 基本概念 ​

GREATEST 和 LEAST 函数分别用于从多个值中选择最大值和最小值。它们在数据比较和范围限制方面非常有用。

4.2 语法结构 ​

sql
GREATEST(value1, value2, value3, ...)
LEAST(value1, value2, value3, ...)

工作原理图 ​

4.3 实际应用示例 ​

学生成绩分析 ​

sql
-- 创建学生成绩表
CREATE TABLE student_scores (
    student_id INTEGER,
    student_name VARCHAR(50),
    math_score INTEGER,
    english_score INTEGER,
    science_score INTEGER,
    history_score INTEGER
);

-- 插入测试数据
INSERT INTO student_scores VALUES
(1, '张三', 85, 78, 92, 88),
(2, '李四', 76, 85, NULL, 82),  -- 科学成绩缺失
(3, '王五', 92, 88, 95, 90),
(4, '赵六', NULL, NULL, NULL, NULL);  -- 所有成绩缺失
sql
-- 计算每个学生的最高分和最低分
SELECT
    student_name,
    GREATEST(math_score, english_score, science_score, history_score) as highest_score,
    LEAST(math_score, english_score, science_score, history_score) as lowest_score,
    -- 计算成绩范围
    GREATEST(math_score, english_score, science_score, history_score) -
    LEAST(math_score, english_score, science_score, history_score) as score_range
FROM student_scores;

查询结果:

student_namehighest_scorelowest_scorescore_range
张三927814
李四85769
王五95887
赵六NULLNULLNULL

价格比较和优惠计算 ​

sql
-- 创建产品价格表
CREATE TABLE product_prices (
    product_name VARCHAR(100),
    store_a_price DECIMAL(8,2),
    store_b_price DECIMAL(8,2),
    store_c_price DECIMAL(8,2),
    online_price DECIMAL(8,2)
);

INSERT INTO product_prices VALUES
('笔记本电脑', 5999.00, 6299.00, 5899.00, 5799.00),
('无线鼠标', 199.00, NULL, 189.00, 179.00),
('机械键盘', 899.00, 999.00, 849.00, 829.00);

-- 找出最优价格和最高价格
SELECT
    product_name,
    LEAST(store_a_price, store_b_price, store_c_price, online_price) as best_price,
    GREATEST(store_a_price, store_b_price, store_c_price, online_price) as highest_price,
    -- 计算最大优惠金额
    GREATEST(store_a_price, store_b_price, store_c_price, online_price) -
    LEAST(store_a_price, store_b_price, store_c_price, online_price) as max_savings
FROM product_prices;

4.4 与 SQL 标准的差异 ​

WARNING

SQL 标准差异将忽略参数列表中的 NULL 值。仅当所有表达式都计算为 NULL 时,结果才为 NULL。

这是与 SQL 标准的偏差。根据标准,如果任何参数为 NULL,则返回值也为 NULL。某些其他数据库的行为方式就是这样。

不同数据库的 NULL 处理比较 ​

数据库系统NULL 处理方式示例结果
PostgreSQL忽略 NULL 值GREATEST(1, NULL, 3) → 3
MySQL忽略 NULL 值GREATEST(1, NULL, 3) → 3
SQL Server任何 NULL 返回 NULLGREATEST(1, NULL, 3) → NULL
Oracle任何 NULL 返回 NULLGREATEST(1, NULL, 3) → NULL

5. 性能优化和最佳实践 ​

5.1 性能考量 ​

索引利用 ​

sql
-- 创建带索引的表
CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    status VARCHAR(20),
    priority INTEGER,
    created_at TIMESTAMP
);

CREATE INDEX idx_orders_status ON orders(status);
CREATE INDEX idx_orders_priority ON orders(priority);

-- 优化的 CASE 查询(可以利用索引)
SELECT * FROM orders
WHERE status = CASE
    WHEN priority >= 5 THEN 'urgent'
    ELSE 'normal'
END;

-- 更好的方式:分别查询
SELECT * FROM orders WHERE priority >= 5 AND status = 'urgent'
UNION ALL
SELECT * FROM orders WHERE priority < 5 AND status = 'normal';

避免重复计算 ​

sql
-- 低效的写法:重复计算
SELECT
    product_name,
    CASE
        WHEN (price * quantity * tax_rate) > 1000 THEN 'expensive'
        WHEN (price * quantity * tax_rate) > 500 THEN 'moderate'
        ELSE 'cheap'
    END as price_category,
    (price * quantity * tax_rate) as total_price
FROM order_items;

-- 高效的写法:使用子查询避免重复计算
SELECT
    product_name,
    CASE
        WHEN total_price > 1000 THEN 'expensive'
        WHEN total_price > 500 THEN 'moderate'
        ELSE 'cheap'
    END as price_category,
    total_price
FROM (
    SELECT
        product_name,
        (price * quantity * tax_rate) as total_price
    FROM order_items
) t;

5.2 数据类型一致性 ​

TIP

最佳实践所有 result 表达式的数据类型必须可转换为单一输出类型。确保类型兼容性可以避免意外的类型转换和性能问题。

sql
-- 正确的类型使用
SELECT
    CASE
        WHEN score >= 90 THEN '优秀'::TEXT
        WHEN score >= 80 THEN '良好'::TEXT
        WHEN score >= 60 THEN '及格'::TEXT
        ELSE '不及格'::TEXT
    END as grade
FROM student_scores;

-- 避免混合类型
-- 错误示例:混合了 TEXT 和 INTEGER
SELECT
    CASE
        WHEN score >= 90 THEN '优秀'
        WHEN score >= 80 THEN 80    -- 这会导致类型问题
        ELSE '不及格'
    END as grade
FROM student_scores;

6. 综合应用案例 ​

6.1 电商订单状态管理 ​

sql
-- 创建订单表
CREATE TABLE ecommerce_orders (
    order_id SERIAL PRIMARY KEY,
    customer_id INTEGER,
    order_status VARCHAR(20),
    payment_status VARCHAR(20),
    ship_status VARCHAR(20),
    order_date TIMESTAMP,
    payment_date TIMESTAMP,
    ship_date TIMESTAMP,
    total_amount DECIMAL(10,2)
);

-- 插入测试数据
INSERT INTO ecommerce_orders VALUES
(1, 101, 'completed', 'paid', 'delivered', '2024-01-15', '2024-01-15', '2024-01-18', 299.99),
(2, 102, 'processing', 'paid', 'pending', '2024-01-16', '2024-01-16', NULL, 199.50),
(3, 103, 'cancelled', 'refunded', NULL, '2024-01-17', '2024-01-17', NULL, 399.00),
(4, 104, 'pending', 'pending', NULL, '2024-01-18', NULL, NULL, 159.99);

-- 综合状态分析查询
SELECT
    order_id,
    customer_id,

    -- 使用 CASE 确定订单优先级
    CASE
        WHEN total_amount >= 300 THEN '高优先级'
        WHEN total_amount >= 200 THEN '中优先级'
        ELSE '普通优先级'
    END as priority,

    -- 使用 COALESCE 处理空值显示
    COALESCE(
        CASE
            WHEN order_status = 'completed' THEN '订单完成'
            WHEN order_status = 'processing' THEN '处理中'
            WHEN order_status = 'cancelled' THEN '已取消'
            ELSE '待处理'
        END,
        '状态未知'
    ) as status_display,

    -- 使用 NULLIF 和 COALESCE 组合处理日期
    COALESCE(
        ship_date::TEXT,
        NULLIF(payment_date::TEXT, ''),
        '未处理'
    ) as last_action_date,

    -- 使用 GREATEST 计算处理时长
    GREATEST(
        EXTRACT(EPOCH FROM (COALESCE(ship_date, payment_date, order_date) - order_date))/86400,
        0
    ) as processing_days,

    total_amount

FROM ecommerce_orders
ORDER BY order_id;

6.2 数据质量检查和修复 ​

sql
-- 创建用户数据表(包含数据质量问题)
CREATE TABLE user_data_raw (
    user_id SERIAL PRIMARY KEY,
    email VARCHAR(100),
    phone VARCHAR(20),
    age INTEGER,
    city VARCHAR(50),
    registration_source VARCHAR(30)
);

-- 插入包含问题的数据
INSERT INTO user_data_raw (email, phone, age, city, registration_source) VALUES
('user1@example.com', '13800138000', 25, '北京', 'website'),
('', '13900139000', 30, 'unknown', 'mobile'),
('user3@example.com', 'N/A', -5, '上海', ''),
('invalid-email', '13700137000', 150, '', 'social'),
(NULL, NULL, NULL, NULL, NULL);

-- 数据清洗和标准化
SELECT
    user_id,

    -- 邮箱清洗:移除无效值
    NULLIF(
        CASE
            WHEN email ~ '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$' THEN email
            ELSE NULL
        END,
        ''
    ) as clean_email,

    -- 电话清洗:标准化格式
    CASE
        WHEN NULLIF(phone, '') ~ '^\d{11}$' THEN phone
        WHEN NULLIF(phone, 'N/A') IS NULL THEN NULL
        ELSE NULL
    END as clean_phone,

    -- 年龄验证:合理范围
    CASE
        WHEN age BETWEEN 0 AND 120 THEN age
        ELSE NULL
    END as clean_age,

    -- 城市标准化
    COALESCE(
        NULLIF(NULLIF(city, ''), 'unknown'),
        '未知城市'
    ) as clean_city,

    -- 注册来源标准化
    COALESCE(
        NULLIF(registration_source, ''),
        'unknown'
    ) as clean_source,

    -- 数据完整性评分
    CASE
        WHEN (email IS NOT NULL AND email != '')
             AND phone ~ '^\d{11}$'
             AND age BETWEEN 0 AND 120
             AND (city IS NOT NULL AND city != '' AND city != 'unknown')
        THEN '完整'
        WHEN (email IS NOT NULL AND email != '') OR phone ~ '^\d{11}$'
        THEN '部分完整'
        ELSE '不完整'
    END as data_quality_score

FROM user_data_raw;

7. 总结 ​

PostgreSQL 的条件表达式提供了强大而灵活的数据处理能力:

主要特点对比 ​

函数主要用途特点应用场景
CASE多条件判断最灵活,支持复杂逻辑数据分类、状态转换、复杂计算
COALESCE空值处理返回第一个非空值默认值设置、数据合并
NULLIF值转换相等时返回 NULL数据清洗、特殊值处理
GREATEST/LEAST值比较选择最值,忽略 NULL范围限制、优惠计算、统计分析

最佳实践要点 ​

  1. 性能优化:避免在大型表的 WHERE 子句中使用复杂的 CASE 表达式
  2. 类型一致性:确保所有分支返回兼容的数据类型
  3. NULL 处理:充分利用各函数的 NULL 处理特性
  4. 可读性:对于复杂逻辑,考虑使用 CTE 或子查询提高可读性
  5. 测试验证:在生产环境使用前,充分测试边界情况和 NULL 值处理

通过熟练掌握这些条件表达式,可以编写出更加高效、健壮和易维护的 SQL 查询语句。