Updating display order of multiple MySQL rows in one or very few queries

前端 未结 11 1230
迷失自我
迷失自我 2021-02-01 07:43

I have a table with say 20 rows each with a number for display order (1-20).

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

From an adm

相关标签:
11条回答
  • 2021-02-01 07:47

    Add an id (or other key) to the table, and update where id (or key) = id (or key) of changed row.

    Of course, you'll have to make sure that either there are no duplicate display_order values, or that you're OK with ties in display_order displaying in any order, or you'll introduce a second, tie-breaker to the order by list.

    0 讨论(0)
  • 2021-02-01 07:49

    soulmerge's answer made me think and I think this is a better solution. What you need to do is select the rows with the id using IN() and then use CASE to set the value.

    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)
    

    I have a need for this in my current app. In PHP, I'm getting a serialized (and ordered) set of id's from jQuery UI's built-in Sortable feature. So the array looks like this:

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

    To generate the single MySQL update query, I do this:

    $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) . ")";
    

    The "implode" at the end just gives me my ID list for IN(). This works beautifully for me.

    0 讨论(0)
  • 2021-02-01 07:49

    You could try to wrap it into a few statements, I don't think it's possible in a single one. So for example, let's say you are going to update the 10th row. You want every record after 10 to be bumped up.

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

    But it's really tough to roll in all logic required. Because some records maybe need a decrement, etc.. You want to avoid duplicates, etc..

    Think about this in terms of developer time vs. gain.

    Because even if someone sorts this once per day, the overhead is minimal, compared to fixing it in a stored procedure, or pseudo-optimizing this feature so you don't run 20 queries. If this doesn't run 100 times a day, 20 queries are perfectly fine.

    0 讨论(0)
  • 2021-02-01 07:53

    This is a great way of sorting items on a database. Exactly what i was looking for ;)

    Here's an improved version of Jamon Holmgren's version. This function is totally dynamic:

    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);
    }
    

    This assumes that you have a column $ordercol on your table $tablename just for ordering purposes.

    The $idsarray is an array in the format array("current_order_num" => "updated_order_num").

    So if you just want to swap two lines in your table (imagine that you have for example a "move up" and "move down" icons on your webpage), you can use this function on top of the previous one:

    function swapRows($tablename, $ordercol, $firstid, $secondid){
        $swaparray = array("$firstid" => "$secondid", "$secondid" => "$firstid");
        reOrderRows($tablename, $ordercol, $swaparray); 
    }
    
    0 讨论(0)
  • 2021-02-01 07:53

    You could create a temporary table and populate with the primary key of the rows you want to change, and the new values of display_order for those rows, then use the multi-table version of UPDATE to update the table in one go. I wouldn't assume that this is faster, however, not without testing both approaches.

    0 讨论(0)
  • 2021-02-01 07:55

    If you need to drag you rows, this is a good implementation for a linked list.

    Having your rows ordered with a linked list means that you will update at most 3 rows at a time -- even if you move the whole block (as long as it's contiguous).

    Create a table of your rows like this:

    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
    

    and use this MySQL query to select the rows in order:

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

    This will traverse your linked list and return the ordered items:

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

    To move a row, you'll need to update the parent of the row, the parent of its current child, and the parent of the row you're inserting before.

    See this series of articles in my blog on how to do it efficiently in MySQL:

    • Sorting lists - how to select ordered items from a linked list
    • Sorting lists: moving items - how to move a single item
    • Sorting lists: adding items - how to insert a single item
    • Sorting lists: deleting items - how to delete a single item
    • Sorting lists: moving blocks - how to move a contiguous block
    • Sorting lists: deleting blocks - how to delete a contiguous block

    There are lots of articles because there are some issues with row locking which should be handled slightly differently for each case.

    0 讨论(0)
提交回复
热议问题