項目中有一個功能變更上線,其中有一張表ttt的字段cc,歷史數據須要處理,把字段cc中爲’xxx’的值替換爲'yyy'。html
表A結構以下:sql
CREATE TABLE `ttt` ( `id` bigint NOT NULL, `aa` int NOT NULL COMMENT 'xxx', `bb` int(11) NOT NULL COMMENT 'xxx', `cc` varchar(64) NOT NULL COMMENT 'xxx', ... PRIMARY KEY (`id`), UNIQUE KEY `uk_aa_bb_cc` (`aa`,`bb`,`cc`) USING BTREE, ) ENGINE=InnoDB DEFAULT CHARSET=utf8
更新sql以下:code
UPDATE ttt SET cc='yyy' WHERE cc='xxx';
執行報錯:
Duplicate entry 'xx-xx-yyy' for key 'uk_aa_bb_cc'htm
由於相同的aa、bb下可能已經有cc值爲'yyy'的數據了,
好比已有歷史數據:
aa bb cc
1 1 xxx
1 1 yyy
這個時候執行更新sql,就會有2條1 1 yyy,因爲字段aa、bb、cc因業務屬性設置爲惟一索引,因此更新失敗。blog
修改更新sql,將有相同yyy的數據排除掉:索引
UPDATE ttt a SET a.cc='yyy' WHERE a.cc='xxx' AND NOT EXISTS (SELECT 1 FROM ttt b WHERE a.aa=b.aa AND a.bb=b.bb AND b.cc='yyy');
執行報錯:
You can't specify target table 'a' for update in FROM clauseci
改爲IN條件:get
UPDATE ttt a SET a.cc='yyy' WHERE a.cc='xxx' AND NOT IN (SELECT id FROM ttt b WHERE a.aa=b.aa AND a.bb=b.bb AND b.cc='yyy');
也報一樣的錯。table
這個錯誤在MySQL會出現,在SQLServer、Oracle上沒有,意思是:
不能先select出同一表中的某些值,再update這個表(在同一語句中),即不能依據某字段值作判斷再來更新某字段的值。date
修改sql以下:
UPDATE ttt a SET a.cc='yyy' WHERE a.cc='xxx' AND a.id NOT IN ( SELECT id FROM(SELECT id FROM ttt b WHERE a.aa=b.aa AND a.bb=b.bb AND b.cc='yyy')c );
執行仍是報錯:
Unknown column 'a.aa' in 'where clause'
看來直接嵌套一層查詢也不行。
再次修改sql,使用UPDATE JOIN語法:
UPDATE ttt a LEFT JOIN ttt b ON a.aa=b.aa AND a.bb=b.bb AND b.cc='yyy' SET a.cc='yyy' WHERE a.cc='xxx' AND b.id IS NULL
由於須要排除掉相同記錄,這裏用LEFT JOIN而且條件爲IS NULL的方式。
再次執行sql成功。
在執行sql先後能夠用sql進行數據查詢,好比:
查詢沒有相同aa、bb且ccc僅有'xxx'的數據(即預先驗證上面的更新sql影響的數據狀況):
SELECT * FROM ttt a LEFT JOIN ttt b ON a.aa=b.aa AND a.bb=b.bb AND b.cc='yyy' WHERE a.cc='xxx' AND b.id IS NULL
查詢有相同aa、bb且cc存在xxx、yyy值的數據:
SELECT aa,bb,COUNT(*) FROM ttt WHERE cc IN('xxx','yyy') GROUP BY aa,bb HAVING COUNT(*)>1
經過aa、bb條件查詢:
SELECT * FROM ttt WHERE aa='xx' AND bb='yy';
參考:
MySQL 中 You can't specify target table '表名' for update in FROM clause錯誤解決辦法
MySQL update join語句