PostgreSQL 数据更新 #

UPDATE 概述 #

UPDATE 语句用于修改表中的现有数据。

text
┌─────────────────────────────────────────────────────────────┐
│                    UPDATE 语句结构                           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   UPDATE table_name                                         │
│   SET column1 = value1, column2 = value2, ...              │
│   [ WHERE condition ]                                       │
│   [ RETURNING * | column1, column2, ... ];                 │
│                                                             │
│   组成部分:                                                 │
│   ├── UPDATE table_name  指定要更新的表                    │
│   ├── SET column = value 指定要更新的列和新值              │
│   ├── WHERE condition    指定更新条件(重要!)            │
│   └── RETURNING          返回更新后的数据                  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

基本更新 #

更新单列 #

sql
-- 创建示例表
CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    price DECIMAL(10, 2),
    stock INTEGER DEFAULT 0,
    status VARCHAR(20) DEFAULT 'active',
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

INSERT INTO products (name, price, stock) VALUES
    ('Product A', 100.00, 50),
    ('Product B', 200.00, 30),
    ('Product C', 150.00, 20);

-- 更新单个列
UPDATE products 
SET price = 120.00 
WHERE id = 1;

-- 不带 WHERE 条件会更新所有行(危险!)
-- UPDATE products SET price = 100.00;  -- 更新所有行

更新多列 #

sql
-- 更新多个列
UPDATE products 
SET 
    price = 180.00,
    stock = 100,
    status = 'featured'
WHERE id = 2;

-- 使用表达式更新
UPDATE products 
SET 
    price = price * 1.1,
    updated_at = CURRENT_TIMESTAMP
WHERE id = 3;

使用表达式 #

sql
-- 数值运算
UPDATE products 
SET price = price * 1.1  -- 价格上涨 10%
WHERE status = 'active';

-- 字符串操作
UPDATE products 
SET name = UPPER(name)
WHERE id = 1;

-- 使用函数
UPDATE products 
SET updated_at = CURRENT_TIMESTAMP
WHERE id IN (1, 2, 3);

-- 使用 CASE 表达式
UPDATE products 
SET status = CASE 
    WHEN stock = 0 THEN 'out_of_stock'
    WHEN stock < 10 THEN 'low_stock'
    ELSE 'in_stock'
END;

条件更新 #

WHERE 条件 #

sql
-- 根据条件更新
UPDATE products 
SET status = 'inactive'
WHERE stock = 0;

-- 多条件更新
UPDATE products 
SET price = price * 0.9
WHERE status = 'active' 
  AND stock > 50;

-- 使用 IN
UPDATE products 
SET status = 'featured'
WHERE id IN (1, 2, 3);

-- 使用 BETWEEN
UPDATE products 
SET price = price * 1.05
WHERE price BETWEEN 100 AND 200;

-- 使用 LIKE
UPDATE products 
SET status = 'special'
WHERE name LIKE 'Special%';

-- 使用子查询
UPDATE products 
SET status = 'popular'
WHERE id IN (
    SELECT product_id 
    FROM order_items 
    GROUP BY product_id 
    HAVING COUNT(*) > 100
);

使用子查询更新 #

sql
-- 从另一个表获取更新值
CREATE TABLE price_updates (
    product_id INTEGER,
    new_price DECIMAL(10, 2)
);

INSERT INTO price_updates VALUES
    (1, 150.00),
    (2, 250.00);

-- 使用子查询更新
UPDATE products 
SET price = (
    SELECT new_price 
    FROM price_updates 
    WHERE price_updates.product_id = products.id
)
WHERE id IN (SELECT product_id FROM price_updates);

-- 使用 FROM 子句(PostgreSQL 特有)
UPDATE products 
SET price = price_updates.new_price
FROM price_updates
WHERE products.id = price_updates.product_id;

RETURNING 子句 #

sql
-- 返回更新后的所有列
UPDATE products 
SET price = 160.00 
WHERE id = 1
RETURNING *;

-- 输出:
--  id |   name    | price  | stock | status  |         updated_at
-- ----+-----------+--------+-------+---------+----------------------------
--   1 | Product A | 160.00 |    50 | active  | 2026-03-29 10:00:00.000000

-- 返回特定列
UPDATE products 
SET price = price * 1.1
WHERE id = 2
RETURNING id, name, price;

-- 返回更新前后的值
UPDATE products 
SET price = price * 1.1
WHERE id = 3
RETURNING 
    id, 
    name, 
    price AS new_price,
    (price / 1.1) AS old_price;

-- 在应用中使用
-- UPDATE products SET stock = stock - $1 WHERE id = $2 RETURNING stock;

FROM 子句更新 #

PostgreSQL 允许使用 FROM 子句从其他表获取数据:

sql
-- 创建订单表
CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    product_id INTEGER,
    quantity INTEGER,
    status VARCHAR(20) DEFAULT 'pending'
);

INSERT INTO orders (product_id, quantity, status) VALUES
    (1, 5, 'pending'),
    (2, 3, 'pending'),
    (1, 2, 'completed');

-- 从订单表更新库存
UPDATE products 
SET stock = stock - orders.quantity
FROM orders
WHERE products.id = orders.product_id
  AND orders.status = 'pending';

-- 多表关联更新
CREATE TABLE categories (
    id SERIAL PRIMARY KEY,
    name VARCHAR(50),
    discount DECIMAL(3, 2)
);

INSERT INTO categories (name, discount) VALUES
    ('Electronics', 0.10),
    ('Books', 0.05);

ALTER TABLE products ADD COLUMN category_id INTEGER;

UPDATE products 
SET category_id = 1
WHERE id IN (1, 2);

-- 根据分类折扣更新价格
UPDATE products 
SET price = price * (1 - categories.discount)
FROM categories
WHERE products.category_id = categories.id;

更新 JSON 数据 #

sql
-- 创建带 JSON 列的表
CREATE TABLE user_settings (
    id SERIAL PRIMARY KEY,
    user_id INTEGER,
    settings JSONB
);

INSERT INTO user_settings (user_id, settings) VALUES
    (1, '{"theme": "light", "language": "en", "notifications": true}'),
    (2, '{"theme": "dark", "language": "zh", "notifications": false}');

-- 更新整个 JSON 值
UPDATE user_settings 
SET settings = '{"theme": "dark", "language": "zh", "notifications": true}'
WHERE user_id = 1;

-- 更新 JSON 中的特定字段
UPDATE user_settings 
SET settings = settings || '{"theme": "dark"}'::jsonb
WHERE user_id = 1;

-- 使用 jsonb_set 更新嵌套值
UPDATE user_settings 
SET settings = jsonb_set(
    settings, 
    '{preferences,fontSize}', 
    '"16px"'
)
WHERE user_id = 1;

-- 删除 JSON 字段
UPDATE user_settings 
SET settings = settings - 'notifications'
WHERE user_id = 2;

更新数组数据 #

sql
-- 创建带数组列的表
CREATE TABLE articles (
    id SERIAL PRIMARY KEY,
    title VARCHAR(200),
    tags VARCHAR(50)[]
);

INSERT INTO articles (title, tags) VALUES
    ('Article 1', ARRAY['tech', 'programming']),
    ('Article 2', ARRAY['lifestyle', 'travel']);

-- 更新整个数组
UPDATE articles 
SET tags = ARRAY['tech', 'programming', 'database']
WHERE id = 1;

-- 添加元素到数组
UPDATE articles 
SET tags = array_append(tags, 'featured')
WHERE id = 1;

-- 删除数组元素
UPDATE articles 
SET tags = array_remove(tags, 'travel')
WHERE id = 2;

-- 更新数组特定位置
UPDATE articles 
SET tags[1] = 'technology'
WHERE id = 1;

批量更新 #

sql
-- 使用 CASE 批量更新不同值
UPDATE products 
SET status = CASE id
    WHEN 1 THEN 'featured'
    WHEN 2 THEN 'sale'
    WHEN 3 THEN 'new'
    ELSE status
END
WHERE id IN (1, 2, 3);

-- 使用 VALUES 列表批量更新
UPDATE products AS p
SET 
    price = v.price,
    stock = v.stock
FROM (VALUES
    (1, 150.00, 60),
    (2, 220.00, 40),
    (3, 180.00, 25)
) AS v(id, price, stock)
WHERE p.id = v.id;

-- 使用临时表批量更新
CREATE TEMP TABLE product_updates (
    product_id INTEGER,
    new_price DECIMAL(10, 2),
    new_stock INTEGER
);

INSERT INTO product_updates VALUES
    (1, 160.00, 55),
    (2, 230.00, 35),
    (3, 170.00, 22);

UPDATE products 
SET 
    price = pu.new_price,
    stock = pu.new_stock
FROM product_updates pu
WHERE products.id = pu.product_id;

性能优化 #

使用索引 #

sql
-- 确保 WHERE 条件使用索引
-- 创建索引
CREATE INDEX idx_products_status ON products(status);

-- 这个更新会使用索引
UPDATE products 
SET price = price * 1.1
WHERE status = 'active';

-- 查看执行计划
EXPLAIN UPDATE products 
SET price = price * 1.1
WHERE status = 'active';

分批更新 #

sql
-- 大批量更新时分批进行
-- 方法1:使用 LIMIT(需要循环)
UPDATE products 
SET status = 'processed'
WHERE status = 'pending'
  AND id IN (
      SELECT id FROM products 
      WHERE status = 'pending' 
      LIMIT 1000
  );

-- 方法2:使用 ID 范围
UPDATE products 
SET status = 'processed'
WHERE status = 'pending'
  AND id BETWEEN 1 AND 10000;

-- 方法3:使用时间范围
UPDATE products 
SET status = 'archived'
WHERE created_at < '2025-01-01'
  AND id IN (
      SELECT id FROM products 
      WHERE created_at < '2025-01-01' 
      LIMIT 10000
  );

避免全表扫描 #

sql
-- 不推荐:没有 WHERE 条件
UPDATE products SET updated_at = CURRENT_TIMESTAMP;

-- 推荐:有明确的 WHERE 条件
UPDATE products 
SET updated_at = CURRENT_TIMESTAMP
WHERE id IN (SELECT id FROM recent_updates);

常见错误 #

违反约束 #

sql
-- 违反唯一约束
-- ERROR: duplicate key value violates unique constraint
UPDATE users SET email = 'existing@example.com' WHERE id = 2;

-- 违反外键约束
-- ERROR: update or delete on table "users" violates foreign key constraint
UPDATE users SET id = 999 WHERE id = 1;

-- 违反检查约束
-- ERROR: new row for relation "products" violates check constraint
UPDATE products SET price = -10 WHERE id = 1;

类型不匹配 #

sql
-- 类型转换错误
-- ERROR: invalid input syntax for type numeric
UPDATE products SET price = 'not_a_number' WHERE id = 1;

-- 正确做法
UPDATE products SET price = '100.00'::DECIMAL WHERE id = 1;

安全更新 #

使用事务 #

sql
-- 使用事务确保安全
BEGIN;

-- 先查询要更新的数据
SELECT * FROM products WHERE status = 'inactive';

-- 确认无误后更新
UPDATE products 
SET status = 'archived'
WHERE status = 'inactive';

-- 确认结果
SELECT * FROM products WHERE status = 'archived';

-- 确认无误后提交
COMMIT;

-- 如果有问题,回滚
-- ROLLBACK;

使用 LIMIT #

sql
-- 限制更新行数(PostgreSQL 需要子查询)
UPDATE products 
SET status = 'processed'
WHERE id IN (
    SELECT id FROM products 
    WHERE status = 'pending' 
    LIMIT 100
);

学习路径 #

text
基础阶段
├── 数据库操作
├── 表操作
├── 数据插入
├── 数据更新(本文)
└── 数据删除

下一步 #

掌握了数据更新后,接下来学习 数据删除,了解如何删除数据!

最后更新:2026-03-29