Contents

SQL: UPDATE ... SET ... FROM 用法

在實際開發中,常常需要根據另一張表的資料來更新目前資料表的欄位。各家資料庫對這種「多表 UPDATE」的語法略有不同,本文整理 SQL Server、MySQL、PostgreSQL 和 Oracle 的寫法差異。

SQL Server 的 UPDATE … FROM

SQL Server 支援 UPDATE ... FROM 語法,可以 JOIN 其他表格來更新:

1
2
3
4
5
6
7
-- 根據 source 表的資料更新 target 表
UPDATE t
SET t.price = s.new_price,
    t.updated_at = GETDATE()
FROM products AS t
INNER JOIN price_updates AS s ON t.product_id = s.product_id
WHERE s.effective_date <= GETDATE();

SQL Server 也支援 CTE(Common Table Expression)搭配 UPDATE:

1
2
3
4
5
6
WITH cte AS (
    SELECT t.price, s.new_price
    FROM products t
    INNER JOIN price_updates s ON t.product_id = s.product_id
)
UPDATE cte SET price = new_price;

MySQL 的 UPDATE … JOIN

MySQL 不支援 UPDATE ... FROM,但有自己的 UPDATE ... JOIN 語法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
-- MySQL 語法:UPDATE 後面直接接 JOIN
UPDATE products t
INNER JOIN price_updates s ON t.product_id = s.product_id
SET t.price = s.new_price,
    t.updated_at = NOW()
WHERE s.effective_date <= NOW();

-- 也可以使用 LEFT JOIN(保留沒有對應的記錄,不更新)
UPDATE products t
LEFT JOIN price_updates s ON t.product_id = s.product_id
SET t.price = COALESCE(s.new_price, t.price);

PostgreSQL 的 UPDATE … FROM

PostgreSQL 支援 UPDATE ... FROM,語法與 SQL Server 類似但略有差異:

1
2
3
4
5
6
7
-- PostgreSQL 語法
UPDATE products AS t
SET price = s.new_price,
    updated_at = NOW()
FROM price_updates AS s
WHERE t.product_id = s.product_id
  AND s.effective_date <= NOW();

注意:PostgreSQL 的 UPDATE ... FROM 的 JOIN 條件寫在 WHERE 子句,而非 ON

Oracle 的 MERGE INTO

Oracle 通常使用 MERGE INTO(也稱為 UPSERT)來達成多表更新:

1
2
3
4
5
6
7
MERGE INTO products t
USING price_updates s ON (t.product_id = s.product_id)
WHEN MATCHED THEN
    UPDATE SET
        t.price = s.new_price,
        t.updated_at = SYSDATE
    WHERE s.effective_date <= SYSDATE;

Oracle 也可以用子查詢的方式:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
UPDATE products t
SET (t.price, t.updated_at) = (
    SELECT s.new_price, SYSDATE
    FROM price_updates s
    WHERE s.product_id = t.product_id
      AND s.effective_date <= SYSDATE
)
WHERE EXISTS (
    SELECT 1 FROM price_updates s
    WHERE s.product_id = t.product_id
);

各資料庫語法對比

資料庫 語法形式 備註
SQL Server UPDATE t SET ... FROM ... JOIN 支援多種 JOIN 類型
MySQL UPDATE t JOIN s SET ... JOIN 在 UPDATE 後
PostgreSQL UPDATE t SET ... FROM s WHERE JOIN 條件在 WHERE
Oracle MERGE INTO 功能最完整的 UPSERT

注意事項:一對多關係的不確定性

當 JOIN 的結果是一對多時(一筆 target 對應多筆 source),不同資料庫的行為可能不同:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
-- 假設 price_updates 有多筆相同 product_id 的資料
-- 最終哪筆 new_price 會被使用,結果是不確定的!

-- 建議先用子查詢確定唯一值
UPDATE products t
INNER JOIN (
    SELECT product_id, MAX(new_price) AS new_price
    FROM price_updates
    WHERE effective_date <= NOW()
    GROUP BY product_id
) s ON t.product_id = s.product_id
SET t.price = s.new_price;

參考資料