PostgreSQL 表达式
PostgreSQL 表达式(Expressions)完全指南
表达式 是 PostgreSQL 中 可以计算出值的任何组合,包括常量、列名、函数、运算符、子查询等。
它是SELECT、WHERE、ORDER BY、UPDATE、INSERT等语句的核心。
一、什么是表达式?
-- 这些都是表达式
5 + 3
UPPER(username)
age > 18
now() - created_at
(SELECT COUNT(*) FROM orders WHERE user_id = u.id)
表达式 = 可求值的单元,最终返回 标量值(单个值)
二、表达式分类
| 类型 | 示例 | 说明 |
|---|---|---|
| 常量表达式 | 'hello'、100、true | 固定值 |
| 列引用 | username、salary * 1.1 | 表中的列 |
| 算术表达式 | price * qty | + - * / % ^ |
| 函数调用 | now()、COALESCE(email, 'N/A') | 内置或自定义函数 |
| 条件表达式 | CASE WHEN ... THEN ... END | 逻辑判断 |
| 子查询表达式 | (SELECT MAX(salary) FROM users) | 标量子查询 |
| 类型转换 | age::TEXT | :: 或 CAST() |
| 集合表达式 | ARRAY[1,2,3] | 数组构造 |
三、核心语法与示例
1. 算术表达式
SELECT
salary,
salary * 1.1 AS bonus_10,
salary / 12 AS monthly,
salary % 1000 AS remainder
FROM users;
2. 字符串表达式
SELECT
username,
UPPER(username) AS shout,
'User: ' || username || ' (ID: ' || id || ')' AS label,
LENGTH(username) AS name_len
FROM users;
3. 条件表达式(CASE)
SELECT
username,
salary,
CASE
WHEN salary > 100000 THEN 'High'
WHEN salary > 50000 THEN 'Medium'
ELSE 'Low'
END AS salary_level,
CASE WHEN is_active THEN 'Active' ELSE 'Inactive' END AS status
FROM users;
4. NULL 处理表达式
SELECT
email,
COALESCE(email, 'No email') AS safe_email,
NULLIF(username, 'admin') AS non_admin -- 如果是 admin 返回 NULL
FROM users;
5. 日期时间表达式
SELECT
created_at,
now() - created_at AS age,
DATE_TRUNC('day', created_at) AS day,
EXTRACT(YEAR FROM created_at) AS year,
created_at + INTERVAL '30 days' AS future
FROM users;
6. JSON 表达式
SELECT
profile,
profile -> 'settings' ->> 'theme' AS theme,
profile @> '{"level": 5}' AS is_senior,
jsonb_build_object('name', username, 'dept', department) AS info
FROM users;
7. 数组表达式
SELECT
tags,
tags[1] AS first_tag,
ARRAY_LENGTH(tags, 1) AS tag_count,
tags || '{"new"}' AS added,
ARRAY_AGG(username) OVER (PARTITION BY department) AS dept_users
FROM users;
8. 子查询表达式(标量子查询)
SELECT
username,
salary,
(SELECT AVG(salary) FROM users) AS global_avg,
salary > (SELECT AVG(salary) FROM users WHERE department = u.department) AS above_dept_avg
FROM users u;
9. 类型转换表达式
SELECT
id::TEXT || '-' || username AS code,
CAST(salary AS INTEGER) AS int_salary,
age::TEXT AS age_str
FROM users;
四、表达式在各语句中的使用
| 语句 | 表达式位置 | 示例 |
|---|---|---|
SELECT | 选择列表 | SELECT salary * 1.1 |
WHERE | 条件 | WHERE age > 18 AND UPPER(username) LIKE 'A%' |
ORDER BY | 排序 | ORDER BY salary DESC, LENGTH(username) |
UPDATE | 赋值 | UPDATE users SET salary = salary * 1.05 |
INSERT | 值 | INSERT INTO logs VALUES (now(), 'login') |
HAVING | 分组过滤 | HAVING COUNT(*) > 5 |
GROUP BY | 分组键 | GROUP BY DATE_TRUNC('month', created_at) |
五、生成列(Generated Columns)—— 表达式持久化
ALTER TABLE users
ADD COLUMN monthly_salary NUMERIC
GENERATED ALWAYS AS (salary / 12) STORED;
-- 查询
SELECT username, monthly_salary FROM users;
类型:
STORED:物理存储,可索引VIRTUAL:计算时生成(PG 不支持)
六、表达式索引(Expression Index)—— 性能提升
-- 加速忽略大小写搜索
CREATE INDEX idx_users_lower_name ON users(LOWER(username));
-- 加速 JSON 查询
CREATE INDEX idx_users_level ON users((profile ->> 'level'));
-- 加速函数查询
CREATE INDEX idx_users_year ON users(EXTRACT(YEAR FROM created_at));
七、常见函数 + 表达式组合
| 函数 | 用途 | 示例 |
|---|---|---|
COALESCE(a,b,c) | 第一个非 NULL | COALESCE(email, phone, 'N/A') |
NULLIF(a,b) | a=b 返回 NULL | NULLIF(score, 0) |
GREATEST(a,b) | 最大值 | GREATEST(salary, bonus) |
LEAST(a,b) | 最小值 | LEAST(price1, price2) |
GENERATE_SERIES() | 生成序列 | SELECT GENERATE_SERIES(1,10) |
ROW_NUMBER() OVER (...) | 窗口函数 | ROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC) |
八、表达式性能优化
| 技巧 | 说明 |
|---|---|
| 避免在 WHERE 中用函数包裹列 | 错:WHERE UPPER(name) = 'ALICE' → 无法用索引对: WHERE name = 'alice' + COLLATE 或表达式索引 |
| 使用表达式索引 | CREATE INDEX ON users(LOWER(email)); |
| 子查询改 JOIN | 标量子查询有时慢 |
| 避免重复计算 | 用 CTE 或 WITH |
-- 慢
SELECT * FROM users WHERE LENGTH(username) > 10;
-- 快:建索引
CREATE INDEX idx_users_name_len ON users(LENGTH(username));
九、表达式在视图中的应用
CREATE VIEW user_summary AS
SELECT
username,
salary,
CASE WHEN salary > 80000 THEN 'High' ELSE 'Normal' END AS level,
COALESCE(email, 'No email') AS contact
FROM users;
-- 查询视图
SELECT * FROM user_summary WHERE level = 'High';
十、表达式实战:复杂业务逻辑
-- 1. 用户等级 + 奖励
SELECT
username,
salary,
CASE
WHEN salary >= 100000 THEN 'S'
WHEN salary >= 70000 THEN 'A'
WHEN salary >= 50000 THEN 'B'
ELSE 'C'
END AS grade,
salary * CASE
WHEN EXTRACT(MONTH FROM now()) = 12 THEN 2.0 -- 双薪
ELSE 1.1
END AS bonus
FROM users;
-- 2. 动态 JSON 构建
SELECT
username,
jsonb_build_object(
'id', id,
'name', username,
'active', is_active,
'tags', tags
) AS user_info
FROM users;
十一、表达式错误与调试
| 错误 | 原因 | 解决 |
|---|---|---|
operator does not exist: text + integer | 类型不匹配 | username || id::TEXT |
function upper(boolean) does not exist | 函数参数错 | UPPER(username::TEXT) |
division by zero | 除数为 0 | NULLIF(denominator, 0) |
subquery returns more than one row | 标量子查询返回多行 | 用 LIMIT 1 或聚合 |
十二、最佳实践 Checklist
| 项目 | 建议 |
|---|---|
| 类型一致 | 避免隐式转换 |
| NULL 安全 | 用 COALESCE、NULLIF |
| 可读性 | 复杂表达式用 () |
| 性能 | 避免在列上用函数 |
| 索引 | 常用表达式建索引 |
| 生成列 | 频繁计算字段用 GENERATED ALWAYS |
十三、一键生成 100 条测试数据(含表达式)
INSERT INTO users (username, email, age, salary, department, is_active)
SELECT
'user_' || n,
'user_' || n || '@test.com',
(random() * 50 + 18)::INT,
(random() * 100000 + 30000)::NUMERIC(10,2),
CASE WHEN n % 3 = 0 THEN 'IT' ELSE 'HR' END,
n % 5 != 0
FROM generate_series(1, 100) AS n;
十四、快速上手:5 分钟掌握表达式
-- 1. 算术 + 字符串
SELECT username, salary * 1.1 AS bonus, 'Bonus: $' || (salary * 0.1) FROM users;
-- 2. 条件 + NULL
SELECT COALESCE(email, phone, 'N/A'),
CASE WHEN is_active THEN 'Yes' ELSE 'No' END
FROM users;
-- 3. JSON + 数组
SELECT profile ->> 'level', tags[1] FROM users;
-- 4. 子查询 + 窗口
SELECT username, salary,
(SELECT AVG(salary) FROM users) AS avg,
RANK() OVER (ORDER BY salary DESC)
FROM users;
现在就动手:
在 users 表中:
- 查询
salary * 1.1且username以a开头(忽略大小写)的用户 - 用
CASE给age < 30的用户打上Young标签 - 查询
profile中level > 5且tags包含dev的用户 - 添加生成列
year_of_birth = EXTRACT(YEAR FROM now()) - age
需要我生成:
- 1000 条复杂测试数据(含 JSON/数组)?回复
测试数据 - 动态分区表达式建表示例?回复
分区表达式 - Python 中使用表达式查询?回复
Python 表达式 - 表达式 vs 函数性能对比?回复
性能对比
随时告诉我!