【问题标题】:Using a sort order column in a database table在数据库表中使用排序顺序列
【发布时间】:2011-12-22 17:58:43
【问题描述】:

假设我在购物网站的数据库中有一个Product 表来保存商店产品的描述、价格等。让我的客户能够重新订购这些产品的最有效方法是什么?

我创建了一个Order 列(整数)用于对记录进行排序,但这让我在性能方面有些头疼,因为我使用原始方法来更改我实际需要更改的每条记录之后的顺序。一个例子:

Id    Order
5     3
8     1
26    2
32    5
120   4

现在我该怎么做才能将 ID=26 的记录顺序更改为 3?

我所做的是创建一个程序来检查目标顺序 (3) 中是否有记录,如果没有则更新行的顺序 (ID=26)。如果目标顺序中有一条记录,则该过程会自行执行,并以target order + 1 作为参数发送该行的ID。

这会导致在我想要更改以腾出空间之后更新每条记录:

Id    Order
5     4
8     1
26    3
32    6
120   5

那么聪明的人会怎么做呢?

  • 我使用 SQL Server 2008 R2。

编辑:

我需要一个项目的 order 列足以进行排序,而不涉及辅助键。单独的订单列必须为其记录指定一个唯一的位置。

除此之外,我想知道是否可以实现类似链接列表的功能:使用“下一个”列而不是“订单”列来保留下一个项目的 ID。但我不知道如何编写以正确顺序检索记录的查询。如果有人也对这种方法有想法,请分享。

【问题讨论】:

  • 更有经验的人不会调用列 Order,因为这已经是 SQL 中的保留关键字 .... :-)

标签: sql-server database sorting


【解决方案1】:
Update product set order = order+1 where order >= @value changed

虽然随着时间的推移,您的订单中的“空格”会越来越大,但它仍然会“排序”

这将在一个语句中将被更改的值和其后的每个值加 1,但上述语句仍然成立。越来越大的“空格”将在您的订单中形成,可能会超过 INT 值。

没有空格的替代解决方案:

想象一个过程:UpdateSortOrder,参数为@NewOrderVal、@IDToChange、@OriginalOrderVal

两步过程取决于新/旧订单是向上还是向下排序。

If @NewOrderVal < @OriginalOrderVal --Moving down chain 

--Create space for the movement; no point in changing the original 
    Update product set order = order+1 
    where order BETWEEN @NewOrderVal and @OriginalOrderVal-1;

end if

If @NewOrderVal > @OriginalOrderVal --Moving up chain

--Create space  for the momvement; no point in changing the original  
  Update product set order = order-1 
  where order between @OriginalOrderVal+1 and @NewOrderVal
end if

--Finally update the one we moved to correct value

    update product set order = @newOrderVal where ID=@IDToChange;

关于最佳实践;我所经历的大多数环境通常都希望按类别分组并按字母顺序或基于“促销人气”排序,从而无需提供用户定义的排序。

【讨论】:

  • 好吧,您可以让用户单击“完成”按钮,然后根据按“订单”排序的查询设置“订单”字段 = rownum
  • 确实是一个很好的解决方案。谢谢你。不过,我还有一个问题:在更新第二个示例中需要更改的记录之前,您更新了两者之间的记录。这意味着暂时会有两行共享相同的订单值。如果我对 Order 列有唯一约束,我怎么能做到这一点?
  • 不知道唯一性约束;可以在完成此操作时暂时将其关闭:或者通过设置update product set order = -1 where ID = @IDtoChange; 来启动该过程,然后我们要做的最后一件事是将-1 设置为我们关心的值。通过上面的更新。可悲的是,这假设 -1 是一个允许的值,并且在这种情况下现在具有“特殊含义”。 (但在这个过程(应该在事务逻辑中)运行时只有几分之一秒)但是由于我们的更新是基于 ID 的,并且在事务中,-1 不应该导致违反约束。
  • 'BETWEEN' 默认情况下是不对称的,例如BETWEEN 3 AND 2 返回一个空结果。我编辑了答案以反映这一点。
  • 不完全理解,这是您需要使用 foreach 函数调用并循环所有项目的东西?它会遍历所有值吗?
【解决方案2】:

使用 BASIC 程序(以及其他地方)使用的旧技巧:将订单列中的数字跳跃 10 或其他一些方便的增量。然后,您可以在两个现有数字(相距 10)之间插入一行(实际上,最多 9 行,如果幸运的话)。或者,您可以将第 370 行移动到第 565 行,而无需将任何行从 570 向上更改。

【讨论】:

    【解决方案3】:

    这是使用公用表表达式 (CTE) 的替代方法。

    此方法尊重 SortOrder 列上的唯一索引,并将填补排序顺序序列中可能从早期的 DELETE 操作中遗留下来的任何间隙。

    /* For example, move Product with id = 26 into position 3 */
    DECLARE @id int = 26
    DECLARE @sortOrder int = 3
    
    
    ;WITH Sorted AS (
        SELECT  Id,
                ROW_NUMBER() OVER (ORDER BY SortOrder) AS RowNumber
        FROM    Product
        WHERE   Id <> @id
    )
    
    UPDATE  p
    SET     p.SortOrder = 
            (CASE 
                WHEN p.Id = @id THEN @sortOrder
                WHEN s.RowNumber >= @sortOrder THEN s.RowNumber + 1
                ELSE s.RowNumber
            END)
    FROM    Product p
            LEFT JOIN Sorted s ON p.Id = s.Id 
    

    【讨论】:

      【解决方案4】:

      我过去使用的一种解决方案取得了一些成功,即使用“重量”而不是“订单”。重量是显而易见的,越重的物品(即:数字越小)沉到底部,越轻(数字越大)上升到顶部。

      如果我有多个重量相同的物品,我会假设它们具有相同的重要性并按字母顺序排列。

      这意味着您的 SQL 将如下所示:

      ORDER BY 'weight', 'itemName'
      

      希望有帮助。

      【讨论】:

      • 谢谢。这是一个解决方案,但它与我目前的方法没有什么不同。这是我的错,因为我应该指定我需要订单列是唯一的。 (我会立即更新问题)我需要客户能够精确地选择哪个项目首先出现。我没有像名称这样的次要条件,在这种情况下,订单必须足够。如果我有两个项目的权重为 10 和 11,我如何才能在不改变它们的权重(以及它们之后的权重)的情况下在它们之间显示记录是我的实际问题。
      • 订单必须是唯一的且没有间隙吗?如果是这样,为什么?概述的技术可以很好地对列具有唯一约束,因此顺序是唯一的。
      • 对不起,我不明白。差距不是问题。我只是不明白当两个项目的重量相同时会发生什么,或者我怎样才能让一个记录出现在两个加权 10 和 11 的记录之间(就像我在之前的评论中提到的那样)。特别是如果我对列有唯一约束。在这种情况下,我必须更改其他物品的重量之一。您是否建议我不应该将体重增加/减少 1?如果我在这里遗漏了一些明显的东西,我深表歉意。
      • 我通常以 10 为增量进行加权,如果我确实需要在某个特别的地方插入一些东西,我有空间。
      【解决方案5】:

      我目前正在开发一个需要排序的具有树形结构的数据库。我使用将在客户端(而不是数据库)上订购的链接列表类型的方法。也可以通过递归查询在数据库中进行排序,但这对于本项目来说不是必需的。

      我制作了这个文档来描述我们将如何实现排序顺序的存储,包括 postgresql 中的一个示例。请随时发表评论!

      https://docs.google.com/document/d/14WuVyGk6ffYyrTzuypY38aIXZIs8H-HbA81st-syFFI/edit?usp=sharing

      【讨论】:

      • +1,很好的样本。我有两个问题:第一个: 为什么你同时拥有ParentPreceeding - 为什么使用双链表而不是链表? 第二个:你如何选择有序的记录:例如,选择前100条按父/子关系排序的记录。
      • parent 指的是树形结构中的父节点。前一列定义了节点的顺序。我最近使用 SELECT 更新了文档中的示例,该 SELECT 通过递归公用表表达式以正确的顺序获取节点。
      【解决方案6】:

      这很简单。你需要有“基数洞”。

      结构:你需要有 2 列:

      1. pk = 32bit int

      2. order = 64bit bigint(BIGINT,NOT DOUBLE!!!)

      插入/更新L

      1. 当您插入第一条新记录时,您必须设置order = round(max_bigint / 2)

      2. 如果在表格开头插入,必须设置order = round("order of first record" / 2)

      3. 如果在表尾插入,必须设置order = round("max_bigint - order of last record" / 2)

      4. 如果中间插入,必须设置order = round("order of record before - order of record after" / 2)

      这个方法有很大的基数。如果您有约束错误,或者您认为您的基数很小,您可以重建顺序列(规范化)。

      在标准化的最大值情况下(使用这种结构),您可以在 32 位中出现“基数洞”。

      非常简单快捷!

      记住不要加倍!!!只有 INT - order 是精度值!

      【讨论】:

      • @AndyM:这不是一个新问题,而是试图回答给定问题
      • 但是在插入 64 行之后,您将用完“订单”的值(订单值将小于 1)
      猜你喜欢
      • 2017-06-07
      • 2019-04-13
      • 1970-01-01
      • 2017-03-07
      • 1970-01-01
      • 2013-05-07
      • 2015-02-25
      • 1970-01-01
      • 2016-08-26
      相关资源
      最近更新 更多