Database 如何设计可以重新排序的表?
我需要做一个关于数据库的设计决策。要求是一个数据库表有一个名为id的自动递增主键字段。默认情况下,每一行都显示给用户(在web中),并按id进行排序。例如,如果表中有4条记录。UI将按0、1、2、3的顺序显示行 现在,用户需要在UI中拖放行来更改顺序。比方说,用户拖动rom 3并将其放到0之前。因此,显示顺序变为3,0,1,2。此序列应持久保存到数据库中 我想知道如何设计数据库表以使其持久化和可伸缩。我的第一个想法是,每行都有一个“序列””字段,指示显示序列。默认情况下,该值应与id相同。从数据库中选择要显示的数据时,行按顺序升序排序,而不是按id排序。 如果序列发生更改,则会将其更新为新值。结果是,它可能涉及到其他行中的许多更改。以上面的示例为例,该表最初如下所示: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 | 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++;
}
}
}