SQL 服务器:用于查找计数器列中的间隙的脚本或存储过程

SQL Server : script or stored procedure to find gaps in a counter column

我们有一个 SQL 服务器 table amPOrder,其中有一列 PONumber VARCHAR(30),其中包含 COM###### 形式的采购订单编号(即前缀 COM 后跟 8 个字符长度的前导零填充整数。PO 编号是连续的但不一定是唯一的(可以有一个有效的 PO 和草稿共享相同的 PONumber)。

PONumber
---
COM142069
COM142082
COM142082
COM142083
COM142088
COM142090
COM142090
COM142090
COM142110
COM142111
COM142113
COM142113
COM142115
COM142116
COM142307
[...]
COM820111
COM820112
COM820113
COM820114
COM820116
COM820121
COM820122

由于最近在软件中发现了一个错误,PONumber 被错误地创建并造成了以下影响:

我们想要识别“自由”PONumber 值,即从 100000 到 900000 的整数值,在 amPOrder 中没有匹配的 PONumber 值,并将它们插入新的 table(我们将能够从中“消耗”间隙)。

CREATE TABLE fix_amPOrder_PONumber 
(
    lValue INT PRIMARY KEY, 
    luserspid INT
)

如果没有匹配的 PONumber,有人可以帮助在两个整数之间插入当前值的“循环”吗?

已解决

根据@john-joseph 的回答,我们做了以下工作:

  1. 创建一个 fillGap 存储过程,它采用起始值和要插入的行数:
CREATE PROCEDURE fillGap
    @lValue INT,
    @times INT
AS
BEGIN
    WHILE @times > 0 BEGIN
        INSERT INTO fix_amPOrder_PONumber
            (lValue)
        VALUES
            (@lValue)
        SET @lValue = @lValue + 1
        SET @times = @times - 1
    END
END
GO
  1. 使用游标遍历已识别的间隙并为每个间隙执行 fillGap
DECLARE @lValue INT, @times INT
 
DECLARE cursorElement CURSOR FOR
SELECT try_cast(replace([PreviousPONumber],'COM','') AS INT) + 1 AS freeValue, Gap - 1 AS times
FROM (
    SELECT *, try_cast(replace([PONumber],'COM','') AS INT) - try_cast(replace([PreviousPONumber],'COM','') AS INT) AS Gap
    FROM (
        SELECT [PONumber], lag([PONumber], 1) OVER (ORDER BY [PONumber]) AS PreviousPONumber
        FROM [amPOrder]) src1
    ) src2
WHERE Gap > 1
 
OPEN cursorElement
FETCH NEXT FROM cursorElement INTO @lValue, @times
WHILE ( @@FETCH_STATUS = 0 )
BEGIN
    EXEC fillGap @lValue, @times
    FETCH NEXT FROM cursorElement INTO @lValue, @times
END
CLOSE cursorElement
DEALLOCATE cursorElement
GO

这种方法可能对某些人有用,特别是因为识别差距和处理差距明显不同。

已解决 2

感谢@larnu 提出最快的解决方案:

--DELETE FROM fix_amPOrder_PONumber
WITH
    N AS (SELECT N FROM (VALUES (NULL), (NULL), (NULL), (NULL), (NULL), (NULL), (NULL), (NULL), (NULL), (NULL)) AS N(N)),
    Tally AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) -1 AS I FROM N N1, N N2, N N3, N N4, N N5, N N6),
    ALLCOMs AS (SELECT CONCAT('COM', RIGHT(CONCAT('000000', I), 6)) AS COM FROM Tally),
    COMs AS (
        SELECT COM FROM ALLCOMs
        WHERE COM > (
            SELECT TOP(1) PONumber FROM amPOrder WHERE PONumber IS NOT NULL ORDER BY PONumber ASC
        ) AND COM < (
            SELECT TOP(1) PONumber FROM amPOrder WHERE PONumber IS NOT NULL ORDER BY PONumber DESC
        )
    )
INSERT INTO fix_amPOrder_PONumber
    (lValue)
    SELECT try_cast(replace(C.COM,'COM','') AS INT)
    FROM COMs AS C
    LEFT JOIN amPOrder AS PO ON C.COM = PO.PONumber
    WHERE PO.PONumber IS NULL
    ORDER BY C.COM

GO

感谢大家的大力帮助。

查找缺失值最快的方法是使用 Tally 生成所有值,然后 LEFT JOIN:

WITH N AS(
    SELECT N
    FROM (VALUES(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL))N(N)),
Tally AS(
    SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) -1 AS I
    FROM N N1, N N2, N N3, N N4, N N5, N N6),
COMs AS(
    SELECT CONCAT('COM',RIGHT(CONCAT('000000',I),6)) AS COM
    FROM Tally)
SELECT C.COM
FROM COMs C
     LEFT JOIN dbo.YourTable YT ON C.COM = YT.PONumber
WHERE YT.PONumber IS NULL;

然后您可以轻松地将其操作为 INSERT 值,或者执行某种 UPDATE.

您可以使用 SQL LAG() 函数在按 PO 编号对记录集进行排序后访问以前的 PO 编号。将其与一些算术相结合,找出你的差距...

    select *
from 
(
    select
         *
        ,try_cast(replace([PONumber],'COM','') as int) - try_cast(replace([PreviousPONumber],'COM','') as int) as Gap
    from
    (
        select 
             [PONumber]
            ,lag([PONumber],1) over (order by [PONumber]) as PreviousPONumber
        from [amPOrder]
    ) src1
) src2
where Gap > 1

这给你...