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

时间:2017-08-02 23:36:11

标签: 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个查询中做到这一点。

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

  1. currentPosition获取SELECT position FROM ordered_data WHERE id = 32
  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
  5. 非常欢迎所有建议。将前两个语句与子选择相结合使MySQL抱怨使用相同的表进行更新并选择。

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

1 个答案:

答案 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子句的索引。