我有一个 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。
- 通过
SELECT position FROM ordered_data WHERE id = 32
获取currentPosition
。 - 用
UPDATE ordered_data SET position = position - 1 WHERE position > currentPosition AND owner = 20
填补我们即将填补的空白。 - 使用
UPDATE ordered_data SET position = position + 1 WHERE position >= 3 AND owner = 20
为行的新位置 (3) 腾出空间。 - 更新行的位置以适应间隙
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/