在一个或几个查询中更新多个MySQL行的显示顺序

时间:2009-04-14 18:04:37

标签: php mysql sorting

我有一张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;

11 个答案:

答案 0 :(得分:13)

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的内置Sortable功能中获取了一组序列化(和有序)的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列表。这对我很有效。

答案 1 :(得分:10)

首先应确保该列没有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的引用。

答案 2 :(得分:4)

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

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

答案 3 :(得分:1)

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

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

但要完成所有需要的逻辑真的很难。因为有些记录可能需要减量等等。你想避免重复等等。

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

因为即使有人每天对此进行一次排序,与在存储过程中修复它相比,开销很小,或者伪优化此功能,因此您不会运行20个查询。如果每天不运行100次,则20个查询完全没问题。

答案 4 :(得分:1)

我正在考虑这个问题,我提出的解决方案是将十进制数作为订单,并更改下一个和上一个项之间的数字项目更改的数量

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

如果将项目4更改为第二个位置,则会得到:

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

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

这样您只需要更新正在重新排序的行。

答案 5 :(得分:1)

如果您需要拖动行,这是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

中有效执行此操作的系列文章

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

答案 6 :(得分:1)

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

这是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 是格式数组中的数组(“current_order_num”=> “updated_order_num”)。

因此,如果你只想在表格中交换两行(想象一下,例如你的网页上有一个“向上移动”和“向下移动”图标),你可以在前一个上面使用这个功能:

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

答案 7 :(得分:0)

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

答案 8 :(得分:0)

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

当然,您必须确保没有重复的display_order值,或者您可以按任意顺序显示display_order中的关系,或者您将引入第二个,按列表排序。

答案 9 :(得分:0)

您可以删除重新插入的所有行 - 只需两次查询即可完成整个操作(如果需要选择所有数据,则可以执行三次操作)。我不会指望它更快,你必须在交易中做到这一点,否则你将在不久之前前往备份。它也可能导致表格碎片化。

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

例如,位置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]

答案 10 :(得分:0)

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

您将获得更好的响应时间,可撤消的更改,更少的修改行(因为在dbms的低级别,实际上几乎没有更新可用,但保存整个行的新实例并删除旧的实例)。

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