【问题标题】:Updating display order of multiple MySQL rows in one or very few queries在一个或很少的查询中更新多个 MySQL 行的显示顺序
【发布时间】:2010-10-19 10:08:00
【问题描述】:

我有一张表,每行有 20 行,每行都有一个显示顺序的数字 (1-20)。

SELECT * FROM `mytable` ORDER BY `display_order` DESC;

您可以从管理区域拖动行或为每行手动输入一个新数字。

当然,为每一行循环一个 UPDATE 查询是不好的,在一个或很少的查询中,有什么替代方法适合更新 20 行甚至更多行中的一个单元格,50-200+?


编辑:很多好的回应和想法。我可能会扩展我目前考虑的想法:

一个数组字符串:我可以在一个字符串中按我想要的顺序列出唯一行 ID 的顺序 - 例如第 1、9、2、6、23 行。当订单更新时,隐藏字段会使用 JavaScript 更新,并在完成后将其添加到数据库或文本文件中:

UPDATE `my_dispaly_order_table` SET `display_order`='1,9,2,6,23';

单独更新每一行:这是我试图避免的,但它只会很少更改,因此每周或每月一次点击 20-30 次调用可能不是问题,所以我通常会在每一行上简单地调用 UPDATE:

UPDATE `mytable` SET `display_order`='1' WHERE `rowId` = 1;
UPDATE `mytable` SET `display_order`='2' WHERE `rowId` = 9;
UPDATE `mytable` SET `display_order`='3' WHERE `rowId` = 2;
UPDATE `mytable` SET `display_order`='4' WHERE `rowId` = 6;
UPDATE `mytable` SET `display_order`='5' WHERE `rowId` = 23;

【问题讨论】:

  • 这是一个链表问题。可能意味着架构更改。

标签: php mysql sorting


【解决方案1】:

soulmerge 的回答让我思考,我认为这是一个更好的解决方案。您需要做的是使用 IN() 选择具有 id 的行,然后使用 CASE 设置值。

UPDATE mytable SET display_order =
  CASE id
    WHEN 10 THEN 1
    WHEN 23 THEN 2
    WHEN 4 THEN 3
  END CASE
WHERE id IN (10, 23, 4)

我当前的应用程序需要此功能。在 PHP 中,我从 jQuery UI 的内置可排序功能中获得了一组序列化(和有序)的 id。所以数组看起来像这样:

$new_order = array(4, 2, 99, 15, 32); // etc

要生成单个 MySQL 更新查询,我这样做:

$query = "UPDATE mytable SET display_order = (CASE id ";
foreach($new_order as $sort => $id) {
  $query .= " WHEN {$id} THEN {$sort}";
}
$query .= " END CASE) WHERE id IN (" . implode(",", $new_order) . ")";

最后的“内爆”只是给了我 IN() 的 ID 列表。这对我来说效果很好。

【讨论】:

  • 这很聪明!即使列表已排序或从列表中删除元素,我也会触发更新,只需一个查询。
  • 我认为这里的语法不正确。例如,它应该只是“END”而不是“END CASE”。
  • 对于 MySQL,您需要使用END CASE。参考:dev.mysql.com/doc/refman/5.7/en/case.html
  • 对于 MySQL,您必须使用 END CASE 表示 CASE statementEND 表示 CASE expression 语句:dev.mysql.com/doc/refman/5.7/en/case.html 表达式:@987654323 @
【解决方案2】:

你应该首先确保该列没有UNIQUE索引,否则mysql会告诉你在查询过程中约束被破坏。之后,您可以执行以下操作:

-- Move #10 down (i.e. swap #10 and #11)
UPDATE mytable SET display_order =
  CASE display_order
    WHEN 10 THEN 11
    WHEN 11 THEN 10
  END CASE
WHERE display_order BETWEEN 10 AND 11;

-- Move #4 to #10
UPDATE mytable SET display_order
  CASE display_order
    WHEN 4 THEN 10
    ELSE display_order - 1
  END CASE
WHERE display_order BETWEEN 4 AND 10;

但实际上,您应该确保一步到位。如果不使用 id,分两步交换将导致编号损坏。即:

-- Swap in two steps will not work as demostrated here:

UPDATE mytable SET display_order = 10 WHERE display_order = 11;
-- Now you have two entries with display_order = 10

UPDATE mytable SET display_order = 11 WHERE display_order = 10;
-- Now you have two entries with display_order = 11 (both have been changed)

这里是对CASE statement of mysql的引用。

【讨论】:

  • 我一直在尝试使用 PHP 解决这个逻辑问题。我应该像你说的那样删除那个索引!我只是担心将错误引入数据库。
【解决方案3】:

有点晚了,但可能对其他人有用:

UPDATE mytable SET display_order = FIND_IN_SET(rowId, '1,9,2,6,23') WHERE rowId in (1,9,2,6,23)

【讨论】:

  • 此代码行为我完成了这项工作 ;-)
【解决方案4】:

您可以尝试将其包装成几个语句,我认为这不可能在单个语句中。例如,假设您要更新第 10 行。您希望 10 之后的每条记录都被提升。

UPDATE table SET col=col+1 WHERE col > 10
UPDATE table SET col=10 WHERE id = X
... 

但是,要引入所有所需的逻辑确实很难。因为有些记录可能需要递减等等。你想避免重复等等。

从开发人员时间与收益的角度来考虑这一点。

因为即使有人每天排序一次,与在存储过程中修复它或伪优化此功能相比,开销也很小,因此您不会运行 20 次查询。如果这不是每天运行 100 次,那么 20 次查询就可以了。

【讨论】:

    【解决方案5】:

    您可以删除重新插入所有行 - 这将在两个查询中完成整个操作(如果您需要选择所有数据,则三个查询)。我不会指望它会更快,而且你必须在事务中完成它,否则你很快就会去备份。它还可能导致表碎片。

    更好的选择可能是将每个更改记录为历史记录,然后执行以下操作:

    例如,位置 10 向下移动了 2 位到第 12 位

    UPDATE table SET display_order = display_order -1 WHERE display_order BETWEEN 10 AND 12
    UPDATE table SET display_order = 12 WHERE row_id = [id of what was row 10]
    

    【讨论】:

      【解决方案6】:

      我正在考虑这个问题,我想出的解决方案是使用十进制数作为顺序,并将项目的编号更改为下一项和上一项之间的数字

      Order    Item
      -----    ----
      1        Original Item 1
      2        Original Item 2
      3        Original Item 3
      4        Original Item 4
      5        Original Item 5
      

      如果您将第 4 项更改为第 2 位,您会得到:

      Order    Item
      -----    ----
      1        Original Item 1
      1.5      Original Item 4
      2        Original Item 2
      3        Original Item 3
      5        Original Item 5
      

      如果将第 3 项更改为第 3 位,则会得到:

      Order    Item
      -----    ----
      1        Original Item 1
      1.5      Original Item 4
      1.75     Original Item 3
      2        Original Item 2
      5        Original Item 5
      

      理论上,两位小数之间总有一个小数,但您可能会面临一些存储限制。

      这样你只需要更新被重新排序的行。

      【讨论】:

      • 我想这有两个问题是你可能仍然需要更新一些字段,但至少不是全部......它可能会在一段时间后变得有点毛茸茸!我有一个类似的概念,但以 100 为单位开始,所以它至少有整数而不是小数,但差别不大......我会说你仍然想不时更新订单以成为正常一致的数字。感谢您的贡献。
      • 它对于频繁排序可能效果不佳,因此您不断添加或移动项目到顶部(例如新闻项目),因此您最终会得到 0.1、0.0001 - 但我猜它可以进入数千个......虽然这似乎是一个稍微偏左的想法,但我实际上倾向于这个解决方案......
      • 我同意它会在一段时间后变得有点毛茸茸,但我不确定用户是否会重新订购那么多(我想这取决于网站类型等)和你可以每个月左右运行一次“规范化”查询以保持更干净。
      【解决方案7】:

      如果您需要拖动行,这是 linked list 的一个很好的实现。

      使用linked list 对行进行排序意味着一次最多更新3 行——即使您移动整个块(只要它是连续的)。

      像这样创建一个包含行的表:

      CREATE TABLE t_list (
              id INT NOT NULL PRIMARY KEY,
              parent INT NOT NULL,
              value VARCHAR(50) NOT NULL,
              /* Don't forget to create an index on PARENT */
              KEY ix_list_parent ON (parent)
      )
      
      id   parent  value
      
      1    0       Value1
      2    3       Value2
      3    4       Value3
      4    1       Value4
      

      并使用此MySQL 查询按顺序选择行:

      SELECT  @r := (
              SELECT  id
              FROM    t_list
              WHERE   parent = @r
              ) AS id
      FROM    (
              SELECT  @r := 0
              ) vars,
              t_list
      

      这将遍历您的链表并返回已订购的项目:

      id   parent  value
      
      1    0       Value1
      4    1       Value4
      3    4       Value3
      2    3       Value2
      

      要移动一行,您需要更新该行的parent、其当前子行的parent,以及您之前插入的行的parent

      在我的博客中查看这一系列文章,了解如何在MySQL 中高效地做到这一点:

      有很多文章,因为行锁定存在一些问题,每种情况的处理方式应略有不同。

      【讨论】:

      • 这似乎很复杂......我的主要想法是它增加了更多的工作而不是更新一个项目的订单它为每个订单更改更新 3 个字段......
      • 3 个字段而不是 1 个在 2009 年不是问题 :) 如果您将有超过 1,000 行并且频繁更新,那么链表是最好的决定,因为无论如何它总是 3 个字段.其他解决方案将需要更新所有字段或可能会用完值的二进制排序。
      【解决方案8】:

      这是对数据库中的项目进行排序的好方法。 正是我要找的东西;)

      这是 Jamon Holmgren 版本的改进版本。这个函数是完全动态的:

      function reOrderRows($tablename, $ordercol, $idsarray){
      
          $query = "UPDATE $tablename SET $ordercol = (CASE $ordercol ";
          foreach($idsarray as $prev => $new) {
            $query .= " WHEN $prev THEN $new\n";
          }
          $query .= " END) WHERE $ordercol IN (" . implode(",", array_keys($idsarray)) . ")";
      
          mysql_query($query);
      }
      

      这假设您的表 $tablename 上有一列 $ordercol 仅用于排序目的。

      $idsarray 是格式为 array("current_order_num" => "updated_order_num")。

      因此,如果您只想交换表格中的两行(例如,假设您的网页上有“上移”和“下移”图标),则可以在前一个之上使用此功能:

      function swapRows($tablename, $ordercol, $firstid, $secondid){
          $swaparray = array("$firstid" => "$secondid", "$secondid" => "$firstid");
          reOrderRows($tablename, $ordercol, $swaparray); 
      }
      

      【讨论】:

        【解决方案9】:

        您可以创建一个临时表并使用要更改的行的主键以及这些行的 display_order 的新值进行填充,然后使用多表版本的 UPDATE 一次性更新表。但是,如果不测试这两种方法,我不会认为这会更快。

        【讨论】:

          【解决方案10】:

          向表中添加一个id(或其他键),并更新其中id(或键)=更改行的id(或键)。

          当然,您必须确保没有重复的 display_order 值,或者您可以接受 display_order 中的平局以任何顺序显示,或者您将在按列表排序。

          【讨论】:

            【解决方案11】:

            在临时变量中收集新订单并将“保存此订单”按钮放到管理区域。然后用一轮保存行的顺序。

            您将获得更好的响应时间、可撤消的更改、更少的修改行(因为在 dbms 的低级别,过去几乎不可能进行更新,但保存整行的新实例并删除旧实例)。

            毕竟,对于整个重新排序来说,这将是一个成本更低的解决方案,并且会节省一些更新优化方面的代码。

            【讨论】:

              猜你喜欢
              • 2020-10-14
              • 2014-06-26
              • 1970-01-01
              • 1970-01-01
              • 2021-08-20
              相关资源
              最近更新 更多