【问题标题】:SQL Server custom record sort in table, allowing to delete recordsSQL Server 自定义记录排序在表中,允许删除记录
【发布时间】:2012-07-03 17:14:57
【问题描述】:

具有自定义排序的 SQL Server 表具有列:ID(PK,自动递增)、OrderNumberCol1Col2 ..

默认情况下,插入触发器会按照建议 here 将值从 ID 复制到 OrderNumber。 使用一些可视化界面,用户可以通过增加或减少 OrderNumber 值来对记录进行排序。

但是,如何处理同时被删除的记录?

示例: 假设您添加 PK ID 的记录:1,2,3,4,5 - OrderNumber 接收相同的值。然后删除 ID=4,ID=5 的记录。下一条记录的 ID=6 并且 OrderNumber 将收到相同的值。 缺少 2 个 OrderNumber 的跨度将迫使用户将 ID=6 的记录减少 3 次以更改其订单(即按下 3 次按钮)。

或者,可以将select count(*) from table 插入OrderNumber,但是当删除一些旧行时,它允许在表中具有多个相似的值。

如果不删除记录,而只是“停用”它们,它们仍然包含在排序顺序中,只是对用户不可见。目前,需要 Java 中的解决方案,但我认为这个问题与语言无关。

在这方面有更好的方法吗?

【问题讨论】:

  • 我知道这听起来可能无关紧要,但目前您的订单变更是如何实施的?您正在谈论多次而不是一次减少 OrderNumber 值的必要性。但是如果你只需要减少一次,那将如何工作?当然,您还需要在不同的行中增加 OrderNumber(可能是带有OrderNumber=5 的行),不是吗?你是这样做的吗?如果是这样,您在在哪里这样做,是在 SQL 中还是在应用程序中?如果在 SQL 中,你能显示脚本吗?
  • @AndriyM 在检查(在 java servlet 中)该行是否不是第一个或最后一个之后,我正在运行两个 SQL UPDATE 查询来切换这两个元素的 OrderNumber。所以,就目前而言,它只是 1 元素移位。我想如果我要将元素移动几个位置,我需要更新起点和终点之间的所有 OrderNumber。

标签: sql sql-server tsql sorting


【解决方案1】:

我会简单地修改切换OrderNumber 值的脚本,使其正确 不依赖于它们的无间隙。

我不知道您的脚本接受哪些参数以及如何使用它们,但我最终想出的那个接受要移动的项目的 ID 和要移动的位置数(负值表示“朝向较低的OrderNumber 值”,正值表示相反方向)。

思路如下:

  1. 查找指定项目的OrderNumber

  2. 按照第二个参数确定的方向排列从OrderNumber 开始的所有项目。指定项目因此获得1的排名。

  3. 选择排名从1 到第二个参数的绝对值加一的项目。 (即最后一项是指定项被移动到的项。)

  4. 将结果集与自身连接,以便每一行与下一行连接,最后一行与第一行连接,从而使用一组行更新另一行。

    李>

这是实现上述内容的查询,cmets 解释了一些棘手的部分:

已编辑:修复了错误重新排序的问题

/* these are the arguments of the query */
DECLARE @ID int, @JumpBy int;
SET @ID = ...
SET @JumpBy = ...

DECLARE @OrderNumber int;
/* Step #1: Get OrderNumber of the specified item */
SELECT @OrderNumber = OrderNumber FROM atable WHERE ID = @ID;

WITH ranked AS (
  /* Step #2: rank rows including the specified item and those that are sorted
     either before or after it (depending on the value of @JumpBy */
  SELECT
    *,
    rnk = ROW_NUMBER() OVER (
      ORDER BY OrderNumber * SIGN(@JumpBy)
      /* this little "* SIGN(@JumpBy)" trick ensures that the
         top-ranked item will always be the one specified by @ID:
         * if we are selecting rows where OrderNumber >= @OrderNumber,
           the order will be by OrderNumber and @OrderNumber will be
           the smallest item (thus #1);
         * if we are selecting rows where OrderNumber <= @OrderNumber,
           the order becomes by -OrderNumber and @OrderNumber again
           becomes the top ranked item, because its negative counterpart,
           -@OrderNumber, will again be the smallest one
      */
    )
  FROM atable
  WHERE OrderNumber >= @OrderNumber AND @JumpBy > 0
     OR OrderNumber <= @OrderNumber AND @JumpBy < 0
),
affected AS (
  /* Step #3: select only rows that need be affected */
  SELECT *
  FROM ranked
  WHERE rnk BETWEEN 1 AND ABS(@JumpBy) + 1
)
/* Step #4: self-join and update */
UPDATE old
SET OrderNumber = new.OrderNumber
FROM affected old
  INNER JOIN affected new ON old.rnk = new.rnk % (ABS(@JumpBy) + 1) + 1
            /* if old.rnk = 1, the corresponding new.rnk is N,
               because 1 = N MOD N + 1  (N is ABS(@JumpBy)+1),
               for old.rnk = 2 the matching new.rnk is 1: 2 = 1 MOD N + 1,
               for 3, it's 2 etc.
               this condition could alternatively be written like this:
               new.rnk = (old.rnk + ABS(@JumpBy) - 1) % (ABS(@JumpBy) + 1) + 1
             */

注意:这里假定 SQL Server 2005 或更高版本。

此解决方案的一个已知问题是,如果指定的 ID 不能精确地移动指定数量的位置(例如,如果您想将最上面的行向上移动任意数量的位置,或第二行两个或多个位置等)。

【讨论】:

  • 令人印象深刻的排序解决方案,但是插入和删除呢?当我从几个小时前删除一些记录时。如果有空白,我应该为新记录分配什么 OrderNumber?不能真的是ID,也不能算数(*)我猜。
  • 插入行时,用它们的 ID 初始化它们的 OrderNumber,就像我知道你现在正在做的那样。如果行被删除,对 OrderNumbers 不做任何事情。即使存在间隙,该解决方案也应该按原样处理 OrderNumber。 IE。如果 OrderNumber 的值为 10 并且前面的 OrderNumber 是 7 并且您需要这两个来交易位置,则此脚本将正确执行此操作。您只需指定两个项目之一的 ID 和要移动的位置数(在这种情况下为 1 或 -1,具体取决于您指定的项目)。
  • 对,它完全忽略了差距。与我想的有点不同的方法。我会尽快检查,谢谢。
  • 不知何故无法正常工作.. 适用于一些简单的情况,但是当 ID 混淆时,它们要么根本不移动,要么移动不同的行,即 ID=1,JumpBy=2将 ID 顺序从 1,2,3 更改为 3,1,2(虽然它应该是 2,3,1 - ID 有间隙)。我想我找到了不同的解决方案。
  • @yosh:我在 UPDATE 语句的连接条件中混淆了别名 oldnew。修复了代码并添加了关于已知问题的注释。
【解决方案2】:

好的 - 如果我没记错的话,您想对 OrderNumber 进行碎片整理。 如果您为此使用ROW_NUMBER() 会怎样?

例子:

;WITH calc_cte AS (
  SELECT
    ID
    , OrderNumber
    , RowNo = ROW_NUMBER() OVER (ORDER BY ID)
  FROM
    dbo.Order    
)
UPDATE
  c
SET
  OrderNumber = c.RowNo
FROM
  calc_cte c
WHERE EXISTS (SELECT * FROM inserted i WHERE c.ID = i.ID)

【讨论】:

  • 我应该在每次删除后运行它以“修补”丢失的记录。但它并没有真正解决太多..如何让下一个 OrderNumber 输入下一个插入? ID 可能比当前的 OrderNumber 高得多,并且具有多个相似值的 count(*) 问题并没有真正解决。
  • 也许行应该按OrderNumber而不是ID排序,或者每次插入都会重置顺序(或者你打算在什么上运行,当然不是在 DELETE 上,通过使用 inserted 判断。
  • 也许这将是一个解决方案。每次删除后(更可能每周删除最多 5 条记录)再次为每个元素分配 OrderNumber。但是,就像@AndriyM 注意到的那样,行应该按旧的 OrderNumber 列排序。不过,我不确定每次删除更新几千行是否有点矫枉过正..
【解决方案3】:

不想回答我自己的问题,但我相信我找到了解决方案。

插入查询:

INSERT INTO table (OrderNumber, col1, col2) 
VALUES ((select count(*)+1 from table),val1,val2)

删除触发器:

CREATE TRIGGER Cleanup_After_Delete ON table
AFTER DELETE AS
BEGIN
  WITH rowtable AS (SELECT [ID], OrderNumber, rownum = ROW_NUMBER() 
                    OVER (ORDER BY OrderNumber ASC) FROM table)
  UPDATE rt SET OrderNumber = rt.rownum FROM rowtable rt 
  WHERE OrderNumber >= (SELECT OrderNumber FROM deleted)
END

触发器在每次删除后触发,并更正已删除订单号之上的所有订单号(无间隙)。这意味着我可以通过切换它们的 OrderNumber 来简单地更改 2 条记录的顺序。


这是解决我的问题的有效解决方案,但是 this one 也是非常好的解决方案,也许对其他人更有用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-28
    • 1970-01-01
    • 1970-01-01
    • 2018-12-01
    • 1970-01-01
    • 2016-05-27
    相关资源
    最近更新 更多