2012-07-03 55 views
0

SQL Server表中的列:ID(PK,自动递增),ORDERNUMBERCol1中col2的 ..SQL Server自定义排序记录表,允许删除记录与自定义排序

默认情况下,插入触发器将建议的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

我意识到这可能听起来毫不相关,但您现在执行的订单改变如何?你在谈论多次递减'OrderNumber'值的必要性,而不是一次。但是,如果您只需要将其减少一次,那将如何工作?当然,你还需要在另一行增加* OrderNumber(可能是'OrderNumber = 5'的那个),对吗?那是你怎么做的?如果是这样,*你在SQL中还是在应用程序中执行此操作?如果在SQL中,你能显示脚本吗? –

+0

@AndriyM在检查(在java servlet中)如果该行不是第一个或最后一个,我正在运行两个SQL UPDATE查询来切换这2个元素的OrderNumber。所以,现在,这只是一个元素的转变。我猜如果我要将元素移动几个地方,我需要在起点和终点之间更新所有的OrderNumbers。 – yosh

回答

1

我想简单地修改,以便它正确做它没有依靠他们的存在无间隙用于切换OrderNumber值脚本。

我不知道你的脚本接受什么样的参数以及它如何使用他们,但我已经最终一个想出接受该项目的ID移动和位置被移动的数量(负价值意味着“朝向更低的OrderNumber值”,而正值意味着相反的方向)。

的思路如下:

  1. 查找指定项目的OrderNumber

  2. 排名均来自于由第二个参数确定的方向OrderNumber开始的项目。指定的项目因此得到1的排名。

  3. 挑选排名从1到第二个参数的绝对值加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, 
      [email protected], 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无法完全移动指定的位置数量,它将不会正确“移动”行(例如,如果要将最上面一排向上移动任何数字的位置,或第二排两个或更多个位置等)。

+0

令人印象深刻的排序解决方案,但插入和删除呢?当我从几个小时前删除一些记录。如果存在差距,我将什么OrderNumber分配给新记录?不能真的是ID,不能算(*)我猜。 – yosh

+0

插入行时,使用它们的ID来初始化它们的OrderNumbers,就像我现在明白的那样。如果行被删除,则对OrderNumbers执行任何操作。即使有差距,这个解决方案也应该像OrderNumbers一样处理它们。即如果OrderNumber的值为10,而紧接着的OrderNumber的值为7,并且您需要两者交换位置,则此脚本将正确执行此操作。您只需指定两个项目之一的ID和要移动的位置的数量(在此情况下为1或-1,具体取决于您指定的项目)。 –

+0

对,它完全忽略了差距。与我正在考虑的方法有点不同。我会尽快检查,谢谢。 – yosh

0

好了 - 如果我没有记错的话,要进行碎片整理您的订单号码。 如果您为此使用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) 
+0

我应该在每次删除之后运行它来“修补”缺失的记录。但它并没有真正解决很多..我如何获得下一个OrderNumber输入下一个插入? ID可能比当前的OrderNumber更高,并且具有多个相似值的count(*)问题并没有真正解决。 – yosh

+0

也许这些行应该按照'OrderNumber'而不是'ID'来排列,否则顺序将随着每个INSERT被重置(或者您打算如何运行它,当然不是在DELETE上,通过使用来判断插入“)。 –

+0

也许这将是一个解决方案..每次删除(更可能最多5个记录每周删除)再次分配OrderNumber到每个元素。但是,像@AndriyM注意到的那样,行应该按照旧的OrderNumber列排序。虽然,我不确定是否每次删除更新数千行都不会有点过分。 – yosh

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也是很不错的一个,也许对别人更有用。