Database 如何设计可以重新排序的表?

Database 如何设计可以重新排序的表?,database,sorting,sequence,Database,Sorting,Sequence,我需要做一个关于数据库的设计决策。要求是一个数据库表有一个名为id的自动递增主键字段。默认情况下,每一行都显示给用户(在web中),并按id进行排序。例如,如果表中有4条记录。UI将按0、1、2、3的顺序显示行 现在,用户需要在UI中拖放行来更改顺序。比方说,用户拖动rom 3并将其放到0之前。因此,显示顺序变为3,0,1,2。此序列应持久保存到数据库中 我想知道如何设计数据库表以使其持久化和可伸缩。我的第一个想法是,每行都有一个“序列””字段,指示显示序列。默认情况下,该值应与id相同。从数据

我需要做一个关于数据库的设计决策。要求是一个数据库表有一个名为id自动递增主键字段。默认情况下,每一行都显示给用户(在web中),并按id进行排序。例如,如果表中有4条记录。UI将按0、1、2、3的顺序显示行

现在,用户需要在UI中拖放行来更改顺序。比方说,用户拖动rom 3并将其放到0之前。因此,显示顺序变为3,0,1,2。此序列应持久保存到数据库中

我想知道如何设计数据库表以使其持久化和可伸缩。我的第一个想法是,每行都有一个“序列””字段,指示显示序列。默认情况下,该值应与id相同。从数据库中选择要显示的数据时,行按顺序升序排序,而不是按id排序。

如果序列发生更改,则会将其更新为新值。结果是,它可能涉及到其他行中的许多更改。以上面的示例为例,该表最初如下所示:

|id   | sequence |
|0    | 0        |
|1    | 1        |
|2    | 2        |
|3    | 3        |
现在,将id为3的行拖动到第一行。其序列更新为0。同时,id为0、1、2的行也应更新

|id   | sequence |
|0    | 1        |
|1    | 2        |
|2    | 3        |
|3    | 0        |
我担心这种方法会使重新排序耗费大量资源,而且无法扩展。因此,我假设序列可以通过将id乘以K(比如10)来初始化。这会在插入的序列值之间留下间隙。但是,如果将K+1行移动到此间隙,间隙仍然会被消耗

|id   | sequence |
|0    | 0        |
|1    | 10       |
|2    | 20       |
|3    | 30       |

这似乎是数据库设计的一个常见问题。有谁有更好的办法来实现这一点吗?

链表怎么样?:-)

事实上,我手头上没有PostgreSQL来测试这是否真的有效(MySQL不支持SQL-99的递归
),我也不认真推荐它。

--编辑:就其他阅读这篇文章的人而言,我被一些奇怪的个人怨恨所击倒,而不是与这个主题相关的一般错误,阅读时要考虑到这一点,然后按照自己的意愿投票

--旧的:

典型的方法是使用“序列号”(我称之为“排序器”)

这真的很容易

“可伸缩性”不包括在内;因为你已经有了一个你正在处理的所有节点的列表(如你所说的拖放),所以你真正要做的就是交换这些数字


琐碎。

ID和序列/排序器是分开的,不应该相互依赖

对于上移/下移功能: 您可以交换序列/排序器值

对于拖放功能:

1) 为所选记录建立新序列/订单号

2) 按当前顺序获取所选记录,然后用新编号更新所选记录

(三) a) 如果新序列号低于当前序列号,则增加序列号>=新序列号(不包括所选序列号)的记录的所有序列号

b) 如果新序列号高于当前序列号,则减小新选定序列号下方和当前序列号上方的所有序列号

希望这是有意义的,我已经找到了正确的方法(下面是实际的实现)

我已经在一个SQL语句中实现了这一点,该语句有少量的逻辑,不适用于纯粹主义者,但它工作得很好

下面是一个示例(OP:您将希望将GUID ID更改为INTs):

创建过程[proc\u UpdateCountryRowOrder]
@ID唯一标识符,
@新位置INT
作为
不计较
声明@CurrentPosition INT
声明@MaximumPosition INT
如果(@NewPosition<1)设置@NewPosition=1
选择@CurrentPosition=[Countries].[Order]
来自[国家]
其中[Countries].[ID]=@ID
选择@MaximumPosition=MAX([Countries].[Order])
来自[国家]
如果(@NewPosition>@MaximumPosition)设置@NewPosition=@MaximumPosition
如果(@NewPosition@CurrentPosition)
开始
如果(@NewPosition<@CurrentPosition)
开始
开始训练
更新[国家]
设置[Countries].[Order]=[Countries].[Order]+1
其中[国家].[订单]>=@NewPosition
和[国家][订单]<@CurrentPosition
更新[国家]
设置[国家].[顺序]=@NewPosition
其中ID=@ID
提交传输
结束
其他的
开始
开始训练
更新[国家]
设置[Countries].[Order]=[Countries].[Order]-1
其中[国家].[订单]@CurrentPosition
更新[国家]
设置[国家].[顺序]=@NewPosition
其中ID=@ID
提交传输
结束
结束
去

我通过按用户选择的顺序将ID(db密钥)的CSV字符串返回到服务器来完成此操作。我的数据库上有一个函数,它将一个csv字符串转换成一个包含两个字段的表——id和序列(实际上是一个带有标识的int)。此临时表中的序列字段值反映CSV字符串中项目的顺序。然后,我使用与ID匹配的新序列字段值更新数据表

编辑:赏金积分看起来很美味,我想我会提供一些关于我答案的细节。代码如下:

declare @id_array varchar(1000)
set @id_array = '47,32,176,12,482'

declare @id_list_table table ([id] int, [sequence] int)

insert @id_list_table ([id], [sequence])
  select [id], [sequence]
  from get_id_table_from_list (@id_array)

update date_table
  set [sequence] = id_list.[sequence]
  from date_table
    inner join @id_list_table as id_list
      on (id_list.[id] = date_table.[id])
我将
@id\u array
设置为一个变量进行测试-通常您的UI会按照修改后的顺序获取id值,并将其作为参数传递给存储的进程。
get\u id\u table\u from\u list
函数将csv字符串解析为包含两个“int”列:[id]和[sequence]的表。[sequence]列是一个标识。该函数处理我的测试数据的结果如下所示:

id seq 47 1 32 2 176 3 12 4 482 5 id seq 47 1 32 2 176 3 12 4
declare @id_array varchar(1000)
set @id_array = '47,32,176,12,482'

declare @id_list_table table ([id] int, [sequence] int)

insert @id_list_table ([id], [sequence])
  select [id], [sequence]
  from get_id_table_from_list (@id_array)

update date_table
  set [sequence] = id_list.[sequence]
  from date_table
    inner join @id_list_table as id_list
      on (id_list.[id] = date_table.[id])
id seq 47 1 32 2 176 3 12 4 482 5

CREATE TABLE #TempData
(
NewSequence bigint identity(1,1),
[Id] BigInt
)

INSERT INTO #TempData ([Id])
SELECT [Id] 
FROM TableNameGoesHere
ORDER BY Sequence

UPDATE TableNameGoesHere
SET Sequence = t2.NewSequence
FROM TableNameGoesHere t1
INNER JOIN #TempData t2
ON t1.[Id] = t2.[Id]

DROP TABLE #TempData
  public abstract class Entity : IIdentifiableEntity
  {
    public int Id { get; set; }
  }

  public interface IOrderedEntity
  {
    float DisplayOrder { get; set; }
  }

  public interface IRepository<TEntity>
  {
    TEntity FindById(int id);
    bool InsertOrUpdate(TEntity entity);
    // More repository methods here...
  }

public static class RepositoryExtenstions
{
  public static void MoveDisplayOrder<T>(IRepository<T> repository, int id, bool moveUp) where T : Entity, IOrderedEntity
  {
    var currentStatus = repository.FindById(id);
    IQueryable<IOrderedEntity> adjacentStatuses;
    if (moveUp)
      adjacentStatuses = repository.All().OrderByDescending(ms => ms.DisplayOrder).Where(ms => ms.DisplayOrder < currentStatus.DisplayOrder);
    else
      adjacentStatuses = repository.All().OrderBy(ms => ms.DisplayOrder).Where(ms => ms.DisplayOrder > currentStatus.DisplayOrder);

    var adjacentTwoDisplayOrders = adjacentStatuses.Select(ms => ms.DisplayOrder).Take(2).ToList();
    float averageOfPreviousTwoDisplayOrders;
    switch (adjacentTwoDisplayOrders.Count)
    {
      case 0:
        // It's already at the top or bottom, so don't move it
        averageOfPreviousTwoDisplayOrders = currentStatus.DisplayOrder;
        break;
      case 1:
        // It's one away, so just add or subtract 0.5 to the adjacent value
        if (moveUp)
          averageOfPreviousTwoDisplayOrders = adjacentTwoDisplayOrders[0] - 0.5F;
        else
          averageOfPreviousTwoDisplayOrders = adjacentTwoDisplayOrders[0] + 0.5F;
        break;
      default: // 2
        // Otherwise, just average the adjacent two values
        averageOfPreviousTwoDisplayOrders = adjacentTwoDisplayOrders.Average();
        break;
    }

    currentStatus.DisplayOrder = averageOfPreviousTwoDisplayOrders;
    repository.InsertOrUpdate(currentStatus);
    var floatPrecision = currentStatus.DisplayOrder.ToString().Substring(currentStatus.DisplayOrder.ToString().IndexOf('.') + 1).Length;
    if(floatPrecision > 5)
      ReorganizeDisplayOrder(repository);
  }

  public static void ReorganizeDisplayOrder<T>(IRepository<T> repository) where T : Entity, IOrderedEntity
  {
    var entities = repository.All().OrderBy(ms => ms.DisplayOrder).ToList();
    float counter = 1F;
    foreach (var entity in entities)
    {
      entity.DisplayOrder = counter;
      repository.InsertOrUpdate(entity);
      counter++;
    }
  }
}