2016年3月9日 星期三

You can't specify target table '資料庫表名' for update in FROM clause

這是最近使用 Hibernate 時遇到的錯誤訊息
查了一下,結果這是 MySQL 特有的問題。說是不能夠在變更資料時 select 同一個 table……

舉例來說,「將使用者 m 底下所有專案(Project)的『最後一個排程(Schedule)』刪除」。
以 HQL 來寫的話:

UPDATE Schedule s 
SET s.status = :Delete
WHERE s IN (
    SELECT MAX(s2) FROM Project p
    LEFT JOIN p.schedules as s2
    LEFT JOIN p.manager as m
    WHERE m.id = :ID
      AND s2 = (
        SELECT MAX(schedules) FROM Project 
        WHERE id = p.id
    )
)

以SQL來寫的話:
UPDATE Schedule s
SET s.status = :Delete
WHERE s.id IN(
    SELECT s2.id FROM Project p
    LEFT JOIN Schedule s2 ON s2.project_id = p.id
    LEFT JOIN User m ON p.manager_id = m.id
    WHERE m.id = :ID
      AND s2.id = (
        SELECT MAX(id) FROM Schedule WHERE project_id = p.id
    )
)

像這樣就會出現錯誤訊息:You can't specify target table 'Schedule' for update in FROM clause
根據Stack Overflow的討論,看來是「insert」、「update」、「delete」都會遇到這種 BUG。

回到剛才的例子,若是要「將使用者 m 底下所有專案(Project)的『最後一個排程(Schedule)』刪除」,若是使用 Hibernate,最簡單的辦法就是先取得要刪除的 Schedule list ,如下所示:

org.hibernate.Session session;

List<Schedule> schedules = session.createQuery("SELECT MAX(s) FROM Project p" +
    "LEFT JOIN Schedule s ON p.schedule_id = s.id  " +
    "LEFT JOIN User m ON p.manager_id = m.id       " +
    "WHERE m.id = :ID")
  .setParameter("ID", 1234567890)
  .list();

session.createQuery("UPDATE Schedule s    " +  
    "SET s.status = :Delete               " +
    "WHERE s IN :List")
  .setParameter("Delete", "DELETE")
  .setParameterList("List", schedules)
  .executeUpdate();

// 關閉 session

在「博客园」中,有個人提出一種比較複雜的解法,他的方法是將變成子集,然後 update。算是個挺麻煩又複雜的解法。有興趣可以點超連結自行觀看

沒有留言:

張貼留言