【问题标题】:Eliminate sequence-numbering 'gaps' when deleting in SQL Server在 SQL Server 中删除时消除序列编号“间隙”
【发布时间】:2014-09-29 17:43:18
【问题描述】:

我在 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 下面聪明而简洁的“配方”不仅解决了最初的问题,而且通过一些调整还允许在必要时进行局部重新排序。

【问题讨论】:

  • 如果序列号与该行中的实际值完全无关,您为什么还要费心存储序列号?只需在运行时确定数字(例如使用ROW_NUMBER()),而不是尝试在数据库中维护这些无意义的数据。在有人真正查询他们运行查询时实际存在的数据之前,这真的没有任何意义,对吧?那么,为什么要竭尽全力将存储的数据保存在一些神奇的、无缝的必杀技中呢?
  • 你也应该搜索并阅读“古怪更新”——恕我直言,这不是一个好主意。
  • 古怪的更新肯定有两个阵营的人。在您尝试之前,有一些非常严格的要求。仅这一点就足以让许多人回避它。随着 LEAD 和 LAG 的加入,它似乎真的不像以前那样需要了。
  • @AaronBertrand: ROW_NUMBER() 等人给了我数据库中的项目序列,无论是“自然”顺序还是基于某种 ORDER BY——我应该更多地强调我的声明,这些“需要待排序列表”……实际上是用户排序的。 “序列”列不是无意义的数据,它是用户之前在富客户端应用程序中拖动项目的顺序。

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


【解决方案1】:

也许使用这样按顺序在 TableA_PK 上分区的 row_number() 会起作用:

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显示删除和更新前后

【讨论】:

  • 但是为什么要更新表呢? 每次触摸任何行时,您都必须执行此维护。将ROW_NUMBER() 表达式放在一个视图中,然后您可以在运行时获取实时序列,而无需为每次写入进行所有这些额外的维护,甚至不必首先在作为移动目标的列上浪费空间.
  • @AaronBertrand 我同意,这将是一个更好的解决方案,但也许有超出我理解的原因,为什么 OP 想要这样做,即使其他选项会更好。我只是展示了一种可能的方法(我认为应该可行)。
  • @jpw 感谢您为将这些放在一起所做的努力,感谢您对代码始终适合更大的上下文的尊重理解,并且我们经常发现自己在网站上寻找的不是“正确”答案,但我们已经画到一个角落的出路。这个解决方案准确、优雅、简洁,并且(参见我上面的“更新”)还为我提供了至少一种解决丑陋边缘情况的潜在方法。干得好!!!
  • @RiqueW 很高兴有帮助:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-04-10
  • 2011-06-07
  • 1970-01-01
  • 1970-01-01
  • 2016-11-12
相关资源
最近更新 更多