在SQL Server中删除时消除序列号“间隙”

时间:2014-09-29 17:43:19

标签: sql-server tsql sql-update sequence sql-delete

我在SQL Server数据库中有两个表,它们是多对多关系,TableA中的行表示业务逻辑中的“容器”结构,TableB中的“子”对象可以是包含在任意数量的容器中。我创建了一个链接表TableA_X_TableB,其中包含以下列:

TableA_PK UNIQUEIDENTIFIER, TableB_PK INT, Sequence INT

...用于记录TableA'容器'中TableB项目序列的最后一列 - 因为这些确实需要是有序列表。

我的所有CRUD都非常简单,除此之外:当我从列表中间删除一个项目时,我希望SQL Server在与特定TableA序列相关的序列号中“密封间隙” 。即,如果我有六个与特定TableA_PK相关联的条目...

TableA_PK                            | TableB_PK | Sequence |
=============================================================
AD7D5099-A14D-48D4-9860-6578EDF7C006 |     10389 |        0 |
AD7D5099-A14D-48D4-9860-6578EDF7C006 |      9368 |        1 |
AD7D5099-A14D-48D4-9860-6578EDF7C006 |      9537 |        2 |
AD7D5099-A14D-48D4-9860-6578EDF7C006 |     18499 |        3 |
AD7D5099-A14D-48D4-9860-6578EDF7C006 |     15759 |        4 |
AD7D5099-A14D-48D4-9860-6578EDF7C006 |      5872 |        5 |

...并执行:

DELETE TableA_X_TableB
WHERE TableA_PK = 'AD7D5099-A14D-48D4-9860-6578EDF7C006'
AND TableB_PK = 9537

...我希望序列值读取0 1 2 3 4,而不是0 1 3 4 5

我尝试了很多方法。我不会列出所有这些,但作为一个例子,在很多失败的实验之后似乎很可能(并且也失败了,要清楚!):

DECLARE @seq INT
SET @seq = -1;
WITH listEntries AS  (
SELECT TOP 100 PERCENT *
FROM TableA_X_TableB
WHERE TableA_PK = 'AD7D5099-A14D-48D4-9860-6578EDF7C006'
ORDER BY Sequence ASC)
UPDATE listEntries
SET @seq = listEntries.Sequence = @seq + 1
FROM listEntries;

这会输入一个更新的,数字正确的序列号列表,好吧......但是基于内部数据库顺序,而不是前一个序列号的排序。结果是用户仔细排序的列表在项目被淘汰的那一刻变得混乱。

我可以想到几个解决方法,但是

  • 让我觉得在T-SQL中应该有一种相当优雅的方式来做这件事;
  • 此问题已经出现过,很可能会再次出现。我宁愿以'正确'的方式解决它,而不是继续应用相同的精巧黑客。

谢谢!

更新 ======================================= ================

一个难题是这张表还有几个索引;一个用于确保TableA_PKTableB_PK的任意组合是唯一的,还有一些用于加速跨大量数据集的某些连接。没有索引,上面的示例代码工作正常,但是一旦它们到位,我的天真ORDER BY子句将始终被它们覆盖。 (这里可能有一个解决方案,但接受的解决方案更优雅。)

此外,正如我所怀疑的那样,总体要求中存在某些边缘情况,其中序列间隙允许的 - 但仅限于最终用户这样说的情况。我无法看到一个直接的基于ROW_NUMBER()的解决方案可以解决这个问题; @jpw下面的巧妙和简洁的“配方”不仅解决了原始问题,而且通过一些调整也允许在必要时进行本地重新排序。

1 个答案:

答案 0 :(得分:1)

也许使用row_number()分区的TableA_PK按顺序排序,就像这样可以工作:

UPDATE Table1 SET Sequence = rn
FROM Table1 
INNER JOIN (
    SELECT 
       [TableA_PK], 
       [TableB_PK] , 
       rn= ROW_NUMBER() OVER (PARTITION BY tablea_pk ORDER BY tablea_pk, sequence) -1 
    FROM Table1 
    WHERE TableA_PK = 'AD7D5099-A14D-48D4-9860-6578EDF7C006' 
) derived ON table1.TableA_PK = derived.TableA_PK and Table1.TableB_PK = derived.TableB_PK
删除和更新前后显示

Sample SQL Fiddle