1

这是一个有效的 SQL 语句(实际上是两个语句)——它获取一系列匹配的行并delivery_number为每一行添加一个增量:

SELECT @i:=0;
UPDATE pipeline_deliveries AS d
SET d.delivery_number = @i:=@i+1
WHERE d.pipelineID = 11
ORDER BY d.setup_time;

但是现在,客户不再希望它们按setup_time. 他们需要根据出发时间排序,这是另一个表中的一个字段。我不知道该怎么做。

MySQL 文档以及这个 answer建议在 4.0 及更高版本(我们正在运行 MySQL 5.0)中我应该能够做到这一点:

SELECT @i:=0;
UPDATE pipeline_deliveries AS d RIGHT JOIN pipeline_routesXdeliveryID AS rXd
    ON d.pipeline_deliveryID = rXd.pipeline_deliveryID
LEFT JOIN pipeline_routes AS r
    ON rXd.pipeline_routeID = r.pipeline_routeID
SET d.delivery_number = @i:=@i+1
WHERE d.pipelineID = 11
ORDER BY r.departure_time,d.pipeline_deliveryID;

但我得到了错误#1221 - Incorrect usage of UPDATE and ORDER BY

那么正确的用法是什么呢?

4

3 回答 3

2

您不能混合UPDATE加入 2 个(或更多)表和ORDER BY.

您可以通过以下方式绕过限制:

UPDATE 
    pipeline_deliveries AS upd
  JOIN
    ( SELECT t.pipeline_deliveryID, 
             @i := @i+1 AS row_number 
      FROM 
          ( SELECT @i:=0 ) AS dummy
        CROSS JOIN 
          ( SELECT d.pipeline_deliveryID
            FROM 
                pipeline_deliveries AS d 
              JOIN 
                pipeline_routesXdeliveryID AS rXd
                  ON d.pipeline_deliveryID = rXd.pipeline_deliveryID
              LEFT JOIN 
                pipeline_routes AS r
                  ON rXd.pipeline_routeID = r.pipeline_routeID
            WHERE 
                d.pipelineID = 11
            ORDER BY 
                r.departure_time, d.pipeline_deliveryID
          ) AS t
    ) AS tmp
      ON tmp.pipeline_deliveryID = upd.pipeline_deliveryID
SET 
    upd.delivery_number = tmp.row_number ;

上面使用了 MySQL 的两个特性,用户定义的变量和派生表内的排序。因为后者不是标准 SQL,它很可能会在 MySQL 的一个功能版本中中断(当优化器足够聪明地发现派生表中的排序是无用的,除非有LIMIT子句)。事实上,在最新版本的 MariaDB(5.3 和 5.5)中,查询将完全做到这一点。它会像ORDER BY不存在一样运行,结果不会是预期的。请参阅 MariaDB 站点上的相关问题:GROUP BY 技巧已被优化掉

同样的情况很可能在未来的任何主流 MySQL 版本中发生(可能在 5.6 中,有人愿意测试这个吗?),这将改进优化器代码。

所以,最好用标准 SQL 来编写。最好的是尚未实现的窗口功能。但是您也可以使用自联接,这在效率方面不会很糟糕,只要您处理一小部分受更新影响的行。

UPDATE 
    pipeline_deliveries AS upd
  JOIN
    ( SELECT t1.pipeline_deliveryID
           , COUNT(*) AS row_number
      FROM
          ( SELECT d.pipeline_deliveryID
                 , r.departure_time
            FROM 
                pipeline_deliveries AS d 
              JOIN 
                pipeline_routesXdeliveryID AS rXd
                  ON d.pipeline_deliveryID = rXd.pipeline_deliveryID
              LEFT JOIN 
                pipeline_routes AS r
                  ON rXd.pipeline_routeID = r.pipeline_routeID
            WHERE 
                d.pipelineID = 11
          ) AS t1
        JOIN
          ( SELECT d.pipeline_deliveryID
                 , r.departure_time
            FROM 
                pipeline_deliveries AS d 
              JOIN 
                pipeline_routesXdeliveryID AS rXd
                  ON d.pipeline_deliveryID = rXd.pipeline_deliveryID
              LEFT JOIN 
                pipeline_routes AS r
                  ON rXd.pipeline_routeID = r.pipeline_routeID
            WHERE 
                d.pipelineID = 11
          ) AS t2
          ON t2.departure_time < t2.departure_time
          OR t2.departure_time = t2.departure_time 
             AND t2.pipeline_deliveryID <= t1.pipeline_deliveryID
          OR t1.departure_time IS NULL
             AND ( t2.departure_time IS NOT NULL
                OR t2.departure_time IS NULL
                   AND t2.pipeline_deliveryID <= t1.pipeline_deliveryID
                 )
      GROUP BY
          t1.pipeline_deliveryID  
    ) AS tmp
      ON tmp.pipeline_deliveryID = upd.pipeline_deliveryID
SET 
    upd.delivery_number = tmp.row_number ;
于 2013-01-14T18:47:31.357 回答
1

基于此文档

对于多表语法,UPDATE 更新 table_references 中命名的每个表中满足条件的行。在这种情况下,不能使用 ORDER BY 和 LIMIT。

在不太了解 MySQL 的情况下,您可以打开一个游标并逐行处理此问题,或者将其传递回您维护以处理此处理的客户端代码(PHP、Java 等)。

经过更多挖掘

要消除优化不佳的子查询,您需要将子查询重写为连接,但如何做到这一点并保留 LIMIT 和 ORDER BY?一种方法是在 FROM 子句中的子查询中查找要更新的行,因此 LIMIT 和 ORDER BY 可以嵌套在子查询中。通过这种方式,work_to_do 与自身的 10 个最高优先级无人认领的行相连接。通常,您不能在多表 UPDATE 中自加入更新目标,但由于它位于 FROM 子句的子查询中,因此在这种情况下它可以工作。

update work_to_do as target
   inner join (
      select w. client, work_unit
      from work_to_do as w
         inner join eligible_client as e on e.client = w.client
      where processor = 0
      order by priority desc
      limit 10
   ) as source on source.client = target.client
      and source.work_unit = target.work_unit
   set processor = @process_id;

有一个缺点:行没有按主键顺序锁定。这可能有助于解释我们在这张桌子上偶尔遇到的死锁

于 2013-01-14T18:32:36.917 回答
0

艰难的方式: -

    ALTER TABLE eav_attribute_option
        添加 temp_value TEXT NOT NULL
        排序顺序之后;
    更新 eav_attribute_option o
        加入 eav_attribute_option_value ov ON o.option_id=ov.option_id
        SET o.temp_value = ov.value
        WHERE o.attribute_id=90;
    设置@x = 0;
    更新 eav_attribute_option
        SET 排序顺序 = (@x:=@x+1)
        哪里属性_id = 90
        按温度值 ASC 排序;
    ALTER TABLE eav_attribute_option
        删除临时值;

于 2017-12-10T23:15:59.930 回答