带有自定义排序的SQL Server表包含以下列: ID (PK,自动增量), OrderNumber , Col1 , Col2 ..
默认情况下,插入触发器会根据建议here将值从ID复制到OrderNumber。 使用一些可视化界面,用户可以通过递增或递减OrderNumber值来对记录进行排序。
但是,如何在此期间处理被删除的记录?
示例: 假设您添加PK ID的记录:1,2,3,4,5 - OrderNumber接收相同的值。然后删除ID = 4,ID = 5的记录。下一条记录的ID = 6,OrderNumber将获得相同的值。 拥有2个缺少OrderNumbers的范围会强制用户减少ID = 6的记录,如3次更改它的顺序(即按下3x按钮)。
或者,可以将select count(*) from table
插入OrderNumber,但是当删除一些旧行时,它允许在表中有几个类似的值。
如果一个人不删除记录,但只是“停用”它们,它们仍然按排序顺序包含在内,对用户来说是不可见的。目前,需要Java解决方案,但我认为这个问题与语言无关。
这方面有更好的方法吗?
答案 0 :(得分:1)
我只是修改了切换OrderNumber
值的脚本,以便正确地执行,而依赖于它们没有间隙。
我不知道你的脚本接受什么参数以及它如何使用它们,但是我最终想出的那个参数接受要移动的项目的ID和要移动的位置的数量(负值)意味着“朝着较低的OrderNumber
值”,而积极的意味着相反的方向。)
这个想法如下:
查找指定项目的OrderNumber
。
按照第二个参数确定的方向对从OrderNumber
开始的所有项目进行排名。因此,指定的项目将获得1
。
选择排名从1
到第二个参数绝对值加1的项目。 (即,最后一项是指定项目移动到的项目。)
将结果集合与自身连接,以便每一行与下一行连接,最后一行与第一行连接,从而使用一组行来更新另一行。
这是实现上述内容的查询,其中的注释解释了一些棘手的部分:
已编辑:修正了错误重新排序的问题
/* 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无法完全按指定的位置移动(例如,如果要将最上面的行向上移动任意数量的位置),则不会正确“移动”行职位,或第二行由两个或更多职位等。)。
答案 1 :(得分: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)
答案 2 :(得分: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
每次删除后触发器都会触发,并更正已删除的所有OrderNumbers(无间隙)。这意味着我可以通过切换OrderNumbers来简单地改变2条记录的顺序。
对于我的问题,这是一个有效的解决方案,但this one也非常好,可能对其他人更有用。