使用SELECT和group更新而不使用GROUP BY

时间:2009-10-08 20:47:34

标签: mysql sql mysql-error-1093

我有一个这样的表(MySQL 5.0.x,MyISAM):

response{id, title, status, ...} (status: 1 new, 3 multi)

如果至少有20个具有相同的标题,我想将所有回复的状态从新(status=1)更新为多个(status=3)。

我有这个,但它不起作用:

UPDATE response SET status = 3 WHERE status = 1 AND title IN (
  SELECT title FROM (
   SELECT DISTINCT(r.title) FROM response r WHERE EXISTS (
     SELECT 1 FROM response spam WHERE spam.title = r.title LIMIT 20, 1)
   )
  as u)

请注意:

  • 我执行嵌套选择以避免使用着名的You can't specify target table 'response' for update in FROM clause
  • 出于性能原因,我无法使用GROUP BY。使用LIMIT的解决方案的查询成本更好(但不太可读)。

编辑:

  • 可以在MySQL中执行SELECT FROM UPDATE目标。 See solution here
  • 问题在于选择的数据完全错误。
  • 我找到的唯一解决方案是使用GROUP BY

    UPDATE response SET status = 3
     WHERE status = 1 AND title IN (SELECT title 
                                      FROM (SELECT title 
                                              FROM response 
                                          GROUP BY title 
                                            HAVING COUNT(1) >= 20) 
    

as derived_response)

感谢您的帮助! :)

4 个答案:

答案 0 :(得分:1)

当您尝试在一个查询中从同一个表中进行UPDATE和SELECT时,MySQL不喜欢它。它与锁定优先级等有关。

以下是我将如何解决这个问题:

SELECT CONCAT('UPDATE response SET status = 3 ',
  'WHERE status = 1 AND title = ', QUOTE(title), ';') AS sql
FROM response
GROUP BY title
HAVING COUNT(*) >= 20;

此查询生成一系列UPDATE语句,引用的标题值得嵌入更新。捕获结果并将其作为SQL脚本运行。

我知道MySQL中的GROUP BY经常会产生一个临时表,这可能会很昂贵。但这是一个交易破坏者吗?您需要多久运行一次此查询?此外,任何其他解决方案也可能需要临时表。


我可以想到一种不使用GROUP BY来解决这个问题的方法:

CREATE TEMPORARY TABLE titlecount (c INTEGER, title VARCHAR(100) PRIMARY KEY);

INSERT INTO titlecount (c, title)
 SELECT 1, title FROM response
 ON DUPLICATE KEY UPDATE c = c+1;

UPDATE response JOIN titlecount USING (title)
SET response.status = 3
WHERE response.status = 1 AND titlecount.c >= 20;

但是这也使用临时表,这就是为什么你试图避免首先使用GROUP BY

答案 1 :(得分:0)

这是一个有趣的MySQL特性 - 我想不出在单个语句中做到这一点的方法(GROUP BY或没有GROUP BY)。

您可以先在临时表中选择相应的响应行,然后从该临时表中进行选择来进行更新。

答案 2 :(得分:0)

你必须使用临时表:

create temporary table r_update (title varchar(10));

insert r_update
select title
  from response
group
    by title
having count(*) < 20;

update response r
left outer
  join r_update ru
    on ru.title = r.title
   set status = case when ru.title is null then 3 else 1;

答案 3 :(得分:0)

我会像下面那样直截了当地写一些

UPDATE `response`, (
  SELECT title, count(title) as count from `response`
     WHERE status = 1
     GROUP BY title
  ) AS tmp
  SET response.status = 3
  WHERE status = 1 AND response.title = tmp.title AND count >= 20;

使用GROUP BY真的那么慢吗?您尝试实现的解决方案看起来像在同一个表上一次又一次地请求,并且如果它工作的话应该比使用GROUP BY慢。