我有一个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个查询中做到这一点。
我目前的解决方案如下,而且非常简单,但我不禁觉得有一种更优雅的方式来执行重新排序。在这个例子中,我用ID32将用户20的行移动到位置3。
currentPosition
获取SELECT position FROM ordered_data WHERE id = 32
。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
。非常欢迎所有建议。将前两个语句与子选择相结合使MySQL抱怨使用相同的表进行更新并选择。
每个所有者不可能超过10行。
答案 0 :(得分:1)
更改步骤的顺序并不像我想象的那么简单。但是,再过一步,您可以使用(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子句的索引。