【问题标题】:Update with SELECT and group without GROUP BY使用 SELECT 更新和不使用 GROUP BY 的组
【发布时间】:2010-12-05 03:36:53
【问题描述】:

我有一个这样的表(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) 
    

作为派生响应)

感谢您的帮助! :)

【问题讨论】:

  • 我不太明白第二点。 GROUP BY 将比 EXISTS 的两个子查询更简单、更快。
  • 在这种情况下,有很多记录,group by 的查询成本要高得多。即使有适当的索引。 :(
  • 如果在 GROUP BY 请求中添加过滤条件 WHERE STATUS = 1,结果应该更快(无需重新检查状态已设置为 3 的结果)。我在下面提出了另一种稍微简单的变体(只有一个 SELECT),但性能应该大致相同。
  • @kriss:在我的具体情况下,结果会不一样。已经标记的记录也必须计算在内。但是它们没有更新,因为它们具有正确的状态。 :)

标签: mysql sql mysql-error-1093


【解决方案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

【讨论】:

    【解决方案2】:

    我会写一些像下面这样简单的东西

    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 慢得多。

    【讨论】:

    • 您好,感谢您的回复。以下是一些结果(您的查询的第一个值,第二个“我的”):**Created_tmp_tables** 3,1 - Handler_read_key 3415 0 - Handler_read_next: 3414,10003 - Handler_read_rnd: 393,0 - Handler_read_rnd_next: 788,2092544 - Handler_update: 3021,0 - Handler_write: 786 609 - Key_read_requests 259,746 - **Last_query_cost**: 12396,162 - Select_scan: 1,2
    • 使用临时表以及标题上的设置和索引,您的请求可能会更快。这应该足以降低 Handler_read_round_next。
    【解决方案3】:

    这是 MySQL 的一个有趣的特性——我想不出一种方法可以在单个语句中完成(GROUP BY 或没有 GROUP BY)。

    您可以先将适当的响应行选择到临时表中,然后通过从该临时表中进行选择来进行更新。

    【讨论】:

    • 其实一个查询就可以做到:xaprb.com/blog/2006/06/23/… :)
    • @Toto - 我认为您的情况属于“这无法避免的问题”情况。不过我可能弄错了。
    【解决方案4】:

    您必须使用临时表:

    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;
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-10-19
      • 1970-01-01
      • 2023-04-11
      • 1970-01-01
      • 2023-04-02
      • 2012-08-26
      • 1970-01-01
      • 2011-02-01
      相关资源
      最近更新 更多