65.9K
CodeProject 正在变化。 阅读更多。
Home

确定 IDENTITY 列中的空洞

starIconstarIcon
emptyStarIcon
starIcon
emptyStarIconemptyStarIcon

2.75/5 (6投票s)

2007年9月29日

CPOL

1分钟阅读

viewsIcon

22176

一篇关于确定 IDENTITY() 序列中缺失值的有效方法。

目的

本文档基本上描述了一种确定包含 IDENTITY() 列的表中空洞或缺失值的有效方法。 例如,假设您有一个如下定义的表

CREATE TABLE Main
(
    id int IDENTITIY(1,1),
    someOtherValue varchar(20) NULL
)

INSERT INTO Main VALUES (NULL)
INSERT INTO Main VALUES (NULL)
INSERT INTO Main VALUES (NULL)
INSERT INTO Main VALUES (NULL)
INSERT INTO Main VALUES (NULL)

这基本上会产生以下表结构

id someOtherValue
1 NULL
2 NULL
3 NULL
4 NULL
5 NULL

现在假设您删除了第 2 行和第 3 行。 您将剩下 ID 1、4 和 5。 此代码基本上会告诉您 2 和 3 缺失了。

引言

最初,我在 Google 上做了一些快速研究,遇到了像这个页面描述的方法:http://www.sql-server-helper.com/tips/determine-missing-identity-values.aspx。 基本上,他们想要创建一个临时透视表,其中包含所有可能的身份值,从 1 到 MAX(),并对透视表和目标表执行 LEFT JOIN。 主表中的任何 NULL 值都等同于缺失的身份。

对于涉及超过 10,000 行的表来说,这很好。

代码

SET NOCOUNT ON 

IF OBJECT_ID('tempdb..#Main') IS NOT NULL
    DROP TABLE #Main -- Oops, something went wrong

CREATE TABLE #Main
(
    id int IDENTITY(1,1), 
    someOtherValue varchar(10) NULL
)

INSERT INTO #Main 
SELECT NULL 
UNION ALL SELECT NULL 
UNION ALL SELECT NULL 
UNION ALL SELECT NULL 
UNION ALL SELECT NULL 
UNION ALL SELECT NULL
UNION ALL SELECT NULL
UNION ALL SELECT NULL
UNION ALL SELECT NULL
UNION ALL SELECT NULL

SELECT * FROM #Main

DELETE FROM #Main WHERE id IN (1, 2, 4, 5, 7, 9, 10)

SELECT * FROM #Main

DECLARE @temp TABLE (firstMiss int NOT NULL, nextValue int NOT NULL)
DECLARE @maxId INT 
DECLARE @lastId INT
DECLARE @increment INT
DECLARE @lowerBound INT

SELECT @maxId = MAX(id) FROM #Main
SELECT @lastId = IDENT_CURRENT('tempdb..#Main')
SELECT @increment = IDENT_INCR('tempdb..#Main')
SELECT @lowerBound = IDENT_SEED('tempdb..#Main')

-- Get the lower-bound misses
INSERT INTO @temp
SELECT
    @lowerBound, -- lower-bound
    (SELECT TOP 1 id FROM #Main WHERE id > @lowerBound ORDER BY id ASC)

-- Get the middle misses
INSERT INTO @temp
SELECT 
    id + @increment,
    COALESCE(
        (
            SELECT TOP 1 
                id 
            FROM 
                #Main 
            WHERE 
                id > (A.id + @increment) 
            ORDER BY 
                id ASC
        ), 
        @maxId)
FROM 
    #Main A
WHERE 
    id + @increment NOT IN (SELECT id FROM #Main) 
    AND (id + @increment) <= @maxId 

-- Get the upper-bound misses
INSERT INTO @temp
SELECT
    (SELECT TOP 1 id + @increment FROM #Main WHERE id < @lastId ORDER BY id DESC),
    @lastId + @increment -- The next value will be this

SELECT * FROM @temp

DECLARE @missingId TABLE (id int)
DECLARE @maxFirstMiss INT
DECLARE @firstMiss INT
DECLARE @nextValue INT

SELECT @firstMiss = MIN(firstMiss) FROM @temp
SELECT @maxFirstMiss = MAX(firstMiss) FROM @temp

WHILE @firstMiss <= @maxFirstMiss
BEGIN
    SELECT @nextValue = nextValue FROM @temp WHERE firstMiss = @firstMiss

    WHILE @firstMiss < @nextValue
    BEGIN
        INSERT INTO @missingId VALUES (@firstMiss)
        SET @firstMiss = @firstMiss + @increment
    END
        
    SELECT @firstMiss = MIN(firstMiss) FROM @temp WHERE firstMiss > @firstMiss
END

SELECT * FROM @missingId

DROP TABLE #Main

假设

首先,我只使用 IDENTITY(1,1) 场景进行了测试。 我真的不处理非 1 的增量或种子值。 它被编码为适用于您的表允许的任何内容,但逻辑可能无法实现。

其次,您的性能可能会因您拥有的间隙数量和表的大小而异。 我的测试是在大约 300,000 行的表上进行的,它的速度比透视表方法快得多。 因此,请注意。

© . All rights reserved.