在同一 SQL 中先查詢再更新同一張表,MySQL 禁止這類自我引用以避免語義模糊。常見於 DELETE ... WHERE id IN (SELECT ... FROM same_table) 或 UPDATE ... SET col = (SELECT ... FROM same_table)。
DELETE FROM tbl WHERE id IN (
SELECT MAX(id) FROM tbl a WHERE EXISTS (
SELECT 1 FROM tbl b WHERE a.tac = b.tac GROUP BY tac HAVING COUNT(1) > 1
) GROUP BY tac
);
-- ERROR 1093 (HY000): You can't specify target table 'tbl' for update in FROM clause
MySQL 執行計畫中,子查詢若引用目標表,可能導致「讀取與寫入同一表」的競態條件。為保證確定性,MySQL 直接禁止。Oracle/PostgreSQL/SQL Server 允許此寫法 (採用一致性快照)。
DELETE FROM tbl WHERE id IN (
SELECT a.id FROM (
SELECT MAX(id) AS id FROM tbl a WHERE EXISTS (
SELECT 1 FROM tbl b WHERE a.tac = b.tac GROUP BY tac HAVING COUNT(1) > 1
) GROUP BY tac
) a
);
原理:內層子查詢結果物化為臨時表 a,外層引用臨時表而非原表,繞過限制。
-- 刪除重複 tac 組中 id 非最大者 DELETE t1 FROM tbl t1 JOIN ( SELECT tac, MAX(id) AS max_id FROM tbl GROUP BY tac HAVING COUNT(*) > 1 ) t2 ON t1.tac = t2.tac AND t1.id < t2.max_id;
優點:單語句、可用索引、無臨時表開銷、標準 SQL 寫法。
-- 步驟 1:建立臨時表存重複 id CREATE TEMPORARY TABLE tmp_dup_ids AS SELECT MAX(id) AS id FROM tbl a WHERE EXISTS ( SELECT 1 FROM tbl b WHERE a.tac = b.tac GROUP BY tac HAVING COUNT(1) > 1 ) GROUP BY tac; -- 步驟 2:刪除 DELETE FROM tbl WHERE id IN (SELECT id FROM tmp_dup_ids); -- 步驟 3:清理 DROP TEMPORARY TABLE tmp_dup_ids;
-- 錯誤 UPDATE t SET val = (SELECT MAX(val) FROM t WHERE type = t.type); -- 解法一:衍生表 UPDATE t JOIN (SELECT type, MAX(val) AS max_val FROM t GROUP BY type) dt ON t.type = dt.type SET t.val = dt.max_val; -- 解法二:中間表 UPDATE t SET val = (SELECT max_val FROM (SELECT type, MAX(val) AS max_val FROM t GROUP BY type) dt WHERE dt.type = t.type);
WITH cte AS (...) DELETE ... USING cte (CTE 不支援直接 DELETE,需 JOIN)。