PostgreSQL VALUES 列表详解

概述

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);

最佳实践

✅ 推荐做法

  1. 始终使用别名:提高代码可读性

    sql 复制代码
    SELECT * FROM (VALUES (1, 'A')) AS t(id, code);
  2. 显式类型转换:避免类型推断问题

    sql 复制代码
    VALUES (NULL::INT, 'text'::TEXT);
  3. 保持行数合理:小数据集使用 VALUES,大数据集使用临时表

  4. 格式化代码:每行一个元组,便于阅读和维护

    sql 复制代码
    VALUES 
        (1, 'Alice'),
        (2, 'Bob'),
        (3, 'Charlie');
  5. 添加注释:说明 VALUES 的业务含义

    sql 复制代码
    -- 有效的订单状态列表
    VALUES ('pending'), ('paid'), ('shipped');

❌ 避免的做法

  1. 不要滥用:复杂逻辑优先考虑临时表或 CTE

  2. 避免硬编码大量数据:超过 100 行考虑其他方案

  3. 不要在循环中使用:批量操作优于逐行处理

  4. 不要忽略类型安全:始终确保类型兼容性

常见问题 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 查询更加简洁、高效和易于维护。


相关推荐
Nightwish51 小时前
Oracle 数据库巡检检查清单
数据库·oracle·ffmpeg
吴声子夜歌1 小时前
SQL经典实例——插入、更新和删除
数据库·sql
基德爆肝c语言1 小时前
MySQL:数据库基础
数据库·mysql
倒流时光三十年1 小时前
PostgreSQL GREATEST 条件表达式函数详解
服务器·数据库·postgresql
山峰哥1 小时前
VBA数据结构之争:Dictionary vs Collection,性能差3倍!
服务器·数据结构·数据库·windows·sql·算法·哈希算法
火山上的企鹅11 小时前
Codex实战:APP远程升级服务搭建(三)后台管理页面(APK 上传、版本管理、多应用页签)
服务器·网络·数据库·oracle·qgc
阿狸猿12 小时前
论 NoSQL 数据库技术及其应用
数据库·nosql
FBI HackerHarry浩12 小时前
DataGrip2023.2.3默认保存的数据库和.sql文件在哪里?怎么修改默认路径?
数据库
袁小皮皮不皮12 小时前
3.HCIP OSPF补充知识(优化版)
服务器·网络·数据库·网络协议·智能路由器