在實際開發中,常常需要根據另一張表的資料來更新目前資料表的欄位。各家資料庫對這種「多表 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;
|
參考資料