mysql - 在数据库中维护行顺序字段的更有效方法?

标签 mysql mariadb

我有一个 MariaDB(想想 MySQL)数据库,其中的行有一个 position 字段。这个位置可以改变,但必须始终是连续的,并且从 1 开始。

简化表架构:

CREATE TABLE `ordered_data` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `owner` int(11) NOT NULL,
  `data` varchar(300) COLLATE utf8_unicode_ci NOT NULL,
  `position` int(11) NOT NULL
  PRIMARY KEY (`id`)
) 

因此,例如,用户可能希望 ID 10 的行移动到位置 #1。这当然需要对所有后续项目重新排序,否则 position 字段将出现重复。我无法在少于 4 个查询中找到执行此操作的方法。

我当前的解决方案如下,它非常简单,但我不禁觉得有一种更优雅的方式来执行重新排序。在此示例中,我将用户 20 的 ID32 行移动到位置 3。

  1. 通过 SELECT position FROM ordered_data WHERE id = 32 获取 currentPosition
  2. UPDATE ordered_data SET position = position - 1 WHERE position > currentPosition AND owner = 20 填补我们即将填补的空白。
  3. 使用 UPDATE ordered_data SET position = position + 1 WHERE position >= 3 AND owner = 20 为行的新位置 (3) 腾出空间。
  4. 更新行的位置以适应间隙 UPDATE ordered_data SET position = 3 WHERE id = 32

非常欢迎所有建议。将前 2 个语句与子查询结合使用会使 MySQL 提示使用同一个表进行更新和查询。

每个所有者不太可能超过 10 行。

最佳答案

改变步骤顺序并不像我想象的那么简单。但是再多做一步,您就可以让您的算法使用 (owner, position) 上的唯一键。为避免在第 2 步出现重复输入错误,您可以暂时将 possition = 0 分配给您要移动的项目。完整的算法如下所示:

set @owner = 20;
set @id = 32;
set @new_pos = 3;

-- 1. get current position
set @old_pos = (select position from ordered_data where id = @id);

-- 1.1 "remove" the item from its old position
update ordered_data set position = 0 where id = @id;

-- 2. close the gap at the old position
update ordered_data
set position = position - 1
where position owner = @owner
  and position > @old_pos
order by position asc -- important for unique key

-- 3. make space at the new position
update ordered_data
set position = position + 1
where position owner = @owner
  and position >= @new_pos
order by position desc -- important for unique key

-- 4. set new position
update ordered_data set position = @new_pos where id = @id;

此方法(恕我直言)适用于小团体。对于更大的数据集,步骤 3. 和 4. 可以优化并一步完成。看下面的例子:你有一组一百万的项目,你想把一个项目从位置 7 移动到位置 3。在从它的位置“移除”项目之后,我们将更新位置 8 到 1000000 并将它们递减 1 到缩小差距。然后我们将位置 3 更新为 999999 并递增它们以腾出空间。这将几乎更新整个组两次,而我们所需要的只是将位置 3 增加到 6。为此,可以将步骤 2 和 3 替换为

if (@new_pos < @old_pos)
    update ordered_data
    set position = position + 1
    where position owner = @owner
      and position between @new_pos and @old_pos
    order by position desc
else if (@new_pos > @old_pos)
    update ordered_data
    set position = position - 1
    where position owner = @owner
      and position between @old_pos and @new_pos
    order by position asc
else
    -- do nothing

注意:这是伪代码。您需要在应用程序站点选择正确的查询。

您甚至可以将它们组合成一个查询:

    update ordered_data
    set position = position + sign(@new_pos - @old_pos)
    where position owner = @owner
      and position between @new_pos and @old_pos
    order by position * sign(@new_pos - @old_pos) desc

但在这种情况下,引擎可能无法为 GROUP BY 子句使用索引。

关于mysql - 在数据库中维护行顺序字段的更有效方法?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45472587/

相关文章:

sql - 基于其他字段更改 SQL CONSTRAINT

php - 基于基础数据的动态计算

mysql - 在 MySQL 中将天数转换为秒数

mysql/mariadb information_schema View 创建时间

mysql - 使用 MySQL 和 MariaDB 创建表 Order 出现 1064 错误

mysql - 将小查询组合成更大的 mysql 查询

mysql - left join 哪里是 null 并且可能 count() = 0?

mysql - 检索每组中的最后一条记录 - MySQL

mysql - 修复因在文本编辑器中编辑 MySQL 数据库而损坏的序列化数据?

mysql - sql : cannot drop foreign key due to auto-generated constraint