概述
VALUES 是 PostgreSQL 中一个强大但常被忽视的功能,它允许你创建一个临时的行集合(表),而无需创建实际的表。VALUES 列表可以看作是一个"内联表"或"虚拟表",在查询中非常有用。
基本语法
sql
VALUES
(value1_1, value1_2, ...),
(value2_1, value2_2, ...),
(value3_1, value3_2, ...);
参数说明
- 每行数据用括号包裹
- 多行之间用逗号分隔
- 每行的列数必须相同
- 对应列的数据类型必须兼容
核心特点
1. 生成临时表
VALUES 创建一个临时的、内存中的表结构,不需要 CREATE TABLE。
sql
-- 创建一个包含 3 行 2 列的临时表
VALUES
(1, 'Alice'),
(2, 'Bob'),
(3, 'Charlie');
返回结果:
column1 | column2
---------+----------
1 | Alice
2 | Bob
3 | Charlie
2. 自动列命名
如果不指定别名,PostgreSQL 会自动生成 column1, column2, column3 等列名。
sql
SELECT * FROM (VALUES (1, 'A'), (2, 'B')) AS t;
-- 列名为: column1, column2
3. 支持别名定义
可以为表和列指定有意义的别名。
sql
SELECT * FROM (
VALUES
(1, 'Alice', 25),
(2, 'Bob', 30),
(3, 'Charlie', 35)
) AS users(id, name, age);
返回结果:
id | name | age
----+---------+-----
1 | Alice | 25
2 | Bob | 30
3 | Charlie | 35
4. 数据类型推断
PostgreSQL 会根据每列的第一个非 NULL 值推断数据类型。
sql
-- id 列为 integer,name 列为 text
SELECT pg_typeof(id), pg_typeof(name)
FROM (VALUES (1, 'Alice')) AS t(id, name);
常见使用场景
1. 作为子查询的数据源
VALUES 最常见的用途是作为子查询提供临时数据。
sql
-- 从临时数据中查询
SELECT * FROM (
VALUES
('北京', '中国'),
('东京', '日本'),
('巴黎', '法国')
) AS cities(city, country)
WHERE country = '中国';
2. INSERT 批量插入
VALUES 最初设计用于 INSERT 语句的批量插入。
sql
-- 单条插入
INSERT INTO users (id, name, age) VALUES (1, 'Alice', 25);
-- 批量插入(更高效)
INSERT INTO users (id, name, age)
VALUES
(1, 'Alice', 25),
(2, 'Bob', 30),
(3, 'Charlie', 35);
3. JOIN 临时数据
将 VALUES 与现有表进行 JOIN 操作。
sql
-- 假设有一个 products 表
SELECT p.product_name, v.category_name
FROM products p
INNER JOIN (
VALUES
(1, '电子产品'),
(2, '图书'),
(3, '服装')
) AS v(category_id, category_name)
ON p.category_id = v.category_id;
4. UPDATE 批量更新
使用 VALUES 进行基于条件的批量更新。
sql
-- 根据 ID 批量更新不同值
UPDATE employees
SET salary = v.new_salary
FROM (
VALUES
(101, 8000),
(102, 9000),
(103, 8500)
) AS v(emp_id, new_salary)
WHERE employees.id = v.emp_id;
5. 测试和原型开发
快速创建测试数据,无需创建实际表。
sql
-- 测试查询逻辑
SELECT
name,
age,
CASE
WHEN age < 30 THEN '青年'
WHEN age < 50 THEN '中年'
ELSE '老年'
END AS age_group
FROM (
VALUES
('Alice', 25),
('Bob', 45),
('Charlie', 60)
) AS people(name, age);
6. 模拟 UNION ALL
VALUES 可以作为 UNION ALL 的替代方案,更简洁。
sql
-- 使用 UNION ALL(冗长)
SELECT 1 AS id, 'A' AS name
UNION ALL
SELECT 2, 'B'
UNION ALL
SELECT 3, 'C';
-- 使用 VALUES(简洁)
SELECT * FROM (
VALUES
(1, 'A'),
(2, 'B'),
(3, 'C')
) AS t(id, name);
7. 提供枚举值列表
为 IN 子句或 CASE 表达式提供值列表。
sql
-- 检查状态是否在有效列表中
SELECT * FROM orders
WHERE status IN (
SELECT status FROM (
VALUES ('pending'), ('processing'), ('completed')
) AS valid_statuses(status)
);
8. 数据转换和映射
创建临时的映射表进行数据转换。
sql
-- 状态码转换为中文描述
SELECT
o.order_id,
o.status_code,
m.status_desc
FROM orders o
LEFT JOIN (
VALUES
(0, '待支付'),
(1, '已支付'),
(2, '已发货'),
(3, '已完成'),
(4, '已取消')
) AS m(code, status_desc)
ON o.status_code = m.code;
高级用法
1. 与 CTE(公用表表达式)结合
sql
WITH temp_data AS (
VALUES
(1, 'Product A', 100.00),
(2, 'Product B', 200.00),
(3, 'Product C', 150.00)
) AS products(id, name, price)
SELECT
name,
price,
ROUND(price / SUM(price) OVER() * 100, 2) AS percentage
FROM temp_data;
2. 嵌套 VALUES
可以在子查询中嵌套多个 VALUES。
sql
SELECT * FROM (
SELECT * FROM (
VALUES (1, 'A'), (2, 'B')
) AS t1(id, code)
) AS subquery;
3. 与窗口函数结合
sql
SELECT
name,
score,
RANK() OVER (ORDER BY score DESC) as rank
FROM (
VALUES
('Alice', 95),
('Bob', 87),
('Charlie', 92),
('David', 88)
) AS students(name, score);
4. 动态生成序列
sql
-- 生成数字序列
SELECT * FROM (
VALUES (1), (2), (3), (4), (5)
) AS numbers(n);
-- 更推荐使用 generate_series
SELECT generate_series(1, 5) AS n;
5. 构建 JSON 数据
sql
SELECT json_agg(row_to_json(t))
FROM (
VALUES
('Alice', 25),
('Bob', 30)
) AS t(name, age);
返回:
json
[{"name":"Alice","age":25},{"name":"Bob","age":30}]
性能特点
1. 内存效率
- VALUES 列表存储在内存中,不涉及磁盘 I/O
- 适合小到中等规模的数据集(通常几百行以内)
- 大数据集建议使用临时表或 UNLOGGED 表
2. 查询优化
PostgreSQL 优化器会将 VALUES 视为普通表,可以进行:
- 索引扫描(如果有索引)
- 哈希连接
- 合并连接
- 并行查询(PostgreSQL 11+)
3. 执行计划分析
sql
EXPLAIN ANALYZE
SELECT * FROM (
VALUES
(1, 'Alice'),
(2, 'Bob'),
(3, 'Charlie')
) AS t(id, name)
WHERE id = 1;
典型输出:
Values Scan on "*VALUES*" (cost=0.00..0.04 rows=1 width=36)
Filter: ("*VALUES*".column1 = 1)
4. 性能对比
sql
-- 方式 1: VALUES(适合小数据集)
SELECT * FROM (VALUES (1), (2), (3), ..., (100)) AS t(n);
-- 方式 2: generate_series(更适合大规模序列)
SELECT generate_series(1, 100) AS n;
-- 方式 3: 临时表(适合大数据集且需要重复使用)
CREATE TEMP TABLE temp_data AS SELECT ...;
建议:
- < 100 行:VALUES 最佳
- 100 - 10000 行:根据情况选择
-
10000 行:考虑临时表或物化视图
与其他方法的对比
VALUES vs UNION ALL
sql
-- VALUES(推荐,更简洁)
SELECT * FROM (VALUES (1), (2), (3)) AS t(n);
-- UNION ALL(冗长)
SELECT 1 AS n
UNION ALL SELECT 2
UNION ALL SELECT 3;
优势:VALUES 更简洁、更易读、性能略优
VALUES vs 临时表
sql
-- VALUES(轻量级,一次性使用)
SELECT * FROM (VALUES (1, 'A'), (2, 'B')) AS t(id, code);
-- 临时表(重量级,可重复使用、可建索引)
CREATE TEMP TABLE temp_t (id INT, code TEXT);
INSERT INTO temp_t VALUES (1, 'A'), (2, 'B');
SELECT * FROM temp_t;
DROP TABLE temp_t;
选择建议:
- 一次性使用 → VALUES
- 多次引用 → 临时表
- 需要索引 → 临时表
- 数据量大 → 临时表
VALUES vs ARRAY
sql
-- VALUES(返回多行多列)
SELECT * FROM (VALUES (1, 'A'), (2, 'B')) AS t(id, code);
-- ARRAY(返回单行数组)
SELECT ARRAY[1, 2, 3] AS ids;
区别:VALUES 返回表结构,ARRAY 返回数组类型
实际应用示例
示例 1:批量状态更新
sql
-- 根据不同条件批量更新订单状态
UPDATE orders
SET status = v.new_status,
updated_at = NOW()
FROM (
VALUES
(1001, 'shipped'),
(1002, 'delivered'),
(1003, 'cancelled'),
(1004, 'refunded')
) AS v(order_id, new_status)
WHERE orders.id = v.order_id;
示例 2:数据验证和过滤
sql
-- 验证输入数据是否在允许的范围内
SELECT input_data.*
FROM (
VALUES
('user1', 25),
('user2', 150), -- 无效年龄
('user3', 45)
) AS input_data(name, age)
WHERE age BETWEEN 0 AND 120;
示例 3:报表数据填充
sql
-- 确保报表包含所有月份,即使某些月份没有数据
SELECT
m.month,
COALESCE(s.total_sales, 0) AS total_sales
FROM (
VALUES
(1), (2), (3), (4), (5), (6),
(7), (8), (9), (10), (11), (12)
) AS m(month)
LEFT JOIN (
SELECT EXTRACT(MONTH FROM sale_date) AS month,
SUM(amount) AS total_sales
FROM sales
WHERE EXTRACT(YEAR FROM sale_date) = 2024
GROUP BY month
) AS s ON m.month = s.month
ORDER BY m.month;
示例 4:权限配置模板
sql
-- 为新用户批量分配默认权限
INSERT INTO user_permissions (user_id, permission_code, granted_at)
SELECT
:new_user_id,
p.permission_code,
NOW()
FROM (
VALUES
('read:articles'),
('write:comments'),
('read:profile'),
('update:profile')
) AS p(permission_code);
示例 5:多语言翻译映射
sql
-- 临时翻译表
SELECT
p.product_name,
COALESCE(t.translation, p.product_name) AS display_name
FROM products p
LEFT JOIN (
VALUES
('Laptop', '笔记本电脑'),
('Phone', '手机'),
('Tablet', '平板电脑')
) AS t(original, translation)
ON p.product_name = t.original
WHERE p.language = 'zh-CN';
限制和注意事项
1. 行数限制
虽然没有硬性限制,但建议:
- 生产环境:不超过 1000 行
- 测试环境:不超过 10000 行
- 超大数据集:使用临时表或 COPY 命令
2. 列数一致性
每行必须有相同数量的列。
sql
-- 错误:列数不一致
VALUES
(1, 'Alice'),
(2, 'Bob', 30); -- 报错!
-- 正确:使用 NULL 填充
VALUES
(1, 'Alice', NULL),
(2, 'Bob', 30);
3. 类型兼容性
对应列的类型必须兼容。
sql
-- 错误:类型不兼容
VALUES
(1, 'Alice'),
('two', 'Bob'); -- 第一列类型冲突
-- 正确:显式类型转换
VALUES
(1::TEXT, 'Alice'),
('two', 'Bob');
4. NULL 值处理
sql
-- NULL 值的类型推断可能不符合预期
SELECT pg_typeof(col1)
FROM (VALUES (NULL, 'text')) AS t(col1, col2);
-- 可能推断为 unknown 类型
-- 解决方案:显式类型转换
SELECT pg_typeof(col1)
FROM (VALUES (NULL::INT, 'text')) AS t(col1, col2);
5. 不能直接用于 DDL
sql
-- 错误:不能在 CREATE TABLE 中直接使用
CREATE TABLE test AS VALUES (1, 'A'); -- 这是 SELECT 的简写
-- 正确:需要 SELECT
CREATE TABLE test AS
SELECT * FROM (VALUES (1, 'A')) AS t(id, code);
最佳实践
✅ 推荐做法
-
始终使用别名:提高代码可读性
sqlSELECT * FROM (VALUES (1, 'A')) AS t(id, code); -
显式类型转换:避免类型推断问题
sqlVALUES (NULL::INT, 'text'::TEXT); -
保持行数合理:小数据集使用 VALUES,大数据集使用临时表
-
格式化代码:每行一个元组,便于阅读和维护
sqlVALUES (1, 'Alice'), (2, 'Bob'), (3, 'Charlie'); -
添加注释:说明 VALUES 的业务含义
sql-- 有效的订单状态列表 VALUES ('pending'), ('paid'), ('shipped');
❌ 避免的做法
-
不要滥用:复杂逻辑优先考虑临时表或 CTE
-
避免硬编码大量数据:超过 100 行考虑其他方案
-
不要在循环中使用:批量操作优于逐行处理
-
不要忽略类型安全:始终确保类型兼容性
常见问题 FAQ
Q1: VALUES 和 SELECT 有什么区别?
A: VALUES 直接构造数据,SELECT 从表中查询数据。VALUES 更适合创建临时数据集。
Q2: VALUES 可以用于 DELETE 语句吗?
A: 可以,通过 JOIN 实现:
sql
DELETE FROM users
USING (VALUES (1), (2), (3)) AS v(id)
WHERE users.id = v.id;
Q3: VALUES 支持表达式吗?
A: 支持:
sql
VALUES
(1 + 2, UPPER('hello')),
(3 + 4, UPPER('world'));
Q4: 如何在应用程序中使用 VALUES?
A: 通过参数化查询:
python
# Python 示例
values = [(1, 'Alice'), (2, 'Bob')]
query = "SELECT * FROM (VALUES %s) AS t(id, name)"
cursor.execute(query, [psycopg2.extras.values_list(values)])
Q5: VALUES 的性能如何?
A: 对于小数据集(< 1000 行),VALUES 性能优秀;大数据集建议使用临时表。
总结
VALUES 列表是 PostgreSQL 中一个灵活强大的工具,具有以下优势:
- ✨ 简洁性:快速创建临时数据集,无需建表
- 🚀 性能:内存操作,无磁盘 I/O
- 🔧 灵活性:可用于 SELECT、INSERT、UPDATE、DELETE、JOIN 等多种场景
- 📦 标准化:符合 SQL 标准,可移植性好
适用场景:
- 批量数据操作
- 测试数据生成
- 临时映射表
- 数据验证和过滤
- 报表数据填充
不适用场景:
- 超大数据集(> 10000 行)
- 需要重复使用的数据
- 需要索引优化的查询
- 持久化数据存储
合理使用 VALUES 可以让 SQL 查询更加简洁、高效和易于维护。