0

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

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

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

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

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

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

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

4

3 回答 3

1

我会简单地修改切换OrderNumber值的脚本,以便它正确地执行它而不依赖它们没有间隙。

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

思路如下:

  1. 查找指定项目的OrderNumber.

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

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

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

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

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

/* 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 不能精确地移动指定数量的位置(例如,如果您想将最上面的行向上移动任意数量的位置,或者第二行两个或多个位置等)。

于 2012-07-04T13:46:37.770 回答
0

好的 - 如果我没记错的话,你想对你的 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)
于 2012-07-03T09:35:05.233 回答
0

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

插入查询:

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 条记录的顺序。


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

于 2012-07-09T14:20:13.377 回答