web-dev-qa-db-ja.com

SQL Serverで未使用の最小数を見つける

SQL Server列で未使用の最小数をどのように見つけますか?

手動で記録した多数のレコードをExcelからSQL Serverテーブルにインポートしようとしています。それらはすべて数値ID(ドキュメント番号と呼ばれます)を持っていますが、適用されなくなった理由で順番に割り当てられませんでした。つまり、私のWebサイトが新しいレコードを記録するとき、可能な限り最小のドキュメント番号を割り当てる必要があります(ゼロより大きい)まだ取得されていません。

これをプレーンSQLで行う方法はありますか、それともTSQL /コードの問題ですか?

ありがとう!

[〜#〜]編集[〜#〜]

同時実行の問題を提起してくれた [〜#〜] ww [〜#〜] に特に感謝します。これはWebアプリであり、定義によりマルチスレッド化されており、この同じ問題に直面している場合は、競合を防ぐためにコードまたはDBレベルのロックを検討する必要があります。

[〜#〜] linq [〜#〜]

FYI-これはLINQを介して次のコードで実現できます。

var nums = new [] { 1,2,3,4,6,7,9,10};

int nextNewNum = (
    from n in nums
    where !nums.Select(nu => nu).Contains(n + 1)
    orderby n
    select n + 1
).First();

nextNewNum == 5

41
Michael La Voie

Id + 1の行が存在しない最初の行を見つけます

SELECT TOP 1 t1.Id+1 
FROM table t1
WHERE NOT EXISTS(SELECT * FROM table t2 WHERE t2.Id = t1.Id + 1)
ORDER BY t1.Id

編集:

最小の既存IDが1ではない特殊なケースを処理するために、醜い解決策を次に示します。

SELECT TOP 1 * FROM (
    SELECT t1.Id+1 AS Id
    FROM table t1
    WHERE NOT EXISTS(SELECT * FROM table t2 WHERE t2.Id = t1.Id + 1 )
    UNION 
    SELECT 1 AS Id
    WHERE NOT EXISTS (SELECT * FROM table t3 WHERE t3.Id = 1)) ot
ORDER BY 1
59
Darrel Miller

それらを数値IDでソートすると、探している番号は、ROW_NUMBER()関数がIDと等しくない最初の番号になります。

12
MarkusQ

これまでのところ、回答のロックまたは同時実行については言及されていません。

これらの2人のユーザーがほぼ同時にドキュメントを追加することを検討してください。

User 1                User 2
Find Id               
                      Find Id
Id = 42               
                      Id = 42
Insert (42..)  
                      Insert (42..)
                      Error!

次のいずれかを行う必要があります:a)そのエラーを処理し、ループを再度利用して、次に利用可能なIDを探しますOR b)プロセスの開始時にロックアウトして、1人のユーザーのみが特定の時間にIDを探している

12
WW.
SELECT TOP 1 t1.id+1
FROM mytable t1
 LEFT OUTER JOIN mytable t2 ON (t1.id + 1 = t2.id)
WHERE t2.id IS NULL
ORDER BY t1.id;

これは、@ Jeffrey Hantlinおよび@Darrel Millerによって提供された相関サブクエリを使用した回答の代替手段です。

しかし、あなたが説明しているポリシーは本当に良い考えではありません。 ID値は一意でなければなりませんが、連続している必要はありません。

ドキュメント#42へのリンクをメールで送信し、その後ドキュメントを削除するとどうなりますか?その後、ID#42を新しいドキュメントに再利用します。これで、電子メールの受信者は間違ったドキュメントへのリンクをたどります。

10
Bill Karwin
declare @value int

select @value = case 
                  when @value is null or @value + 1 = idcolumn 
                    then idcolumn 
                  else @value end
   from table
   order by idcolumn

select @value + 1

2つではなく1つのテーブルをスキャンして、ハッシュの一致と上位の回答のような結合をスキャンしますか?

5
user6586

シーケンスにギャップがある場合は、次のようなもので最初のギャップを見つけることができます。

select top 1 (found.id + 1) nextid from (select id from items union select 0) found
    where not exists (select * from items blocking
                          where blocking.id = found.id + 1)
    order by nextid asc

つまり、サクセサが存在しない最小のIDを見つけて、そのサクセサを返します。ギャップがない場合、最大の現存するIDよりも1つ大きい値を返します。 1で始まるIDが確実に考慮されるように、0のプレースホルダーIDが挿入されます。

これには少なくともn log n時間かかることに注意してください。

Microsoft SQLでは、from文でinsert句を使用できるため、手続き型コードを使用する必要がない場合があります。

3
Jeffrey Hantin
select
    MIN(NextID) NextUsableID
from (
    select (case when c1 = c2 then 0 
            else c1 end) NextID 
    from (  select ROW_NUMBER() over (order by record_id) c1, 
                   record_id c2
            from   myTable)
)
where NextID > 0
2
Sony

ここに簡単なアプローチがあります。速くないかもしれません。最初は欠けている数字は見つかりません。

SELECT MIN(MT1.MyInt+1)
FROM MyTable MT1
LEFT OUTER JOIN MyTable MT2 ON (MT1.MyInt+1)=MT2.MyInt
WHERE MT2.MyInt Is Null
2
Rich Garrett

それが可能な限り少ない数でなければならない理由はありますか?なぜ穴を埋める必要があるのですか?

編集ビジネスルールなので、答えを広告します。

DECLARE @counter int
DECLARE @max
SET @counter = 0
SET @max = SELECT MAX(Id) FROM YourTable
WHILE @counter <= @max
BEGIN
    SET @counter = @counter + 1
    IF NOT EXISTS (SELECT Id FROM YourTable WHERE Id = @counter)
        BREAK
    END
END

(私は便利なdbを持っていないので、これは100%正確ではないかもしれませんが、そこから取得できるはずです)

2
Matt Grande

IDは常に1から始める必要があるとします。

SELECT MIN(a.id) + 1 AS firstfree
FROM (SELECT id FROM table UNION SELECT 0) a
LEFT JOIN table b ON b.id = a.id + 1
WHERE b.id IS NULL

これは、私が考えることができるすべてのケースを処理します-既存のレコードをまったく含めません。

このソリューションについて私が気に入らない唯一のことは、次のように追加の条件を2回含める必要があることです。

SELECT MIN(a.id) + 1 AS firstfree
FROM (SELECT id FROM table WHERE column = 4711 UNION SELECT 0) a
LEFT JOIN table b ON b.column = 4711 AND b.id = a.id + 1
WHERE b.id IS NULL

ロックと同時実行性に関するコメントにも注意してください。ギャップを埋める要件は、ほとんどの場合、設計が不適切であり、問​​題を引き起こす可能性があります。ただし、[〜#〜] i [〜#〜]を実行するのには十分な理由がありました。IDは人間が印刷して入力するため、多くの桁のIDは必要ありません。しばらくすると、低いものはすべて無料ですが...

2
maf-soft

私は同様の問題に直面し、これを思いつきました:

Select Top 1 IdGapCheck
From (Select Id, ROW_NUMBER() Over (Order By Id Asc) AS IdGapCheck
    From dbo.table) F
Where Id > IdGapCheck
Order By Id Asc
1
William Mueller

私はこの答えが遅いことを知っていますが、再帰的なテーブル式を使用することで、未使用の最小数を見つけることができます。

CREATE TABLE Test
(
    ID int NOT NULL
)

--Insert values here

;WITH CTE AS
(
    --This is called once to get the minimum and maximum values
    SELECT nMin = 1, MAX(ID) + 1 as 'nMax' 
    FROM Test
    UNION ALL
    --This is called multiple times until the condition is met
    SELECT nMin + 1, nMax 
    FROM CTE
    WHERE nMin < nMax
)

--Retrieves all the missing values in the table. Removing TOP 1 will
--list all the unused numbers up to Max + 1
SELECT TOP 1 nMin
FROM CTE
WHERE NOT EXISTS
(
    SELECT ID
    FROM Test
    WHERE nMin = ID
)
1
John Odom

本当に列をIDENTITYに変換する必要があります。最初にBACKUPを使用し、次にROW_NUMBERを使用してドキュメントIDを更新して、ドキュメントIDが1から始まり、ドキュメント数に達するまで更新します。数値列が他のテーブル(外部キー)で参照として使用されている場合、SQL Serverは外部キーを更新しようとし、競合のために失敗する可能性があるため、一度に1つずつWHILEで行う必要があります。最後に、列のID仕様を有効にします。

:)作業が増えましたが、後で多くの問題を解決できます。

1
Ovidiu Pacurar

ROW_NUMBER()関数の例:

IF NOT EXISTS (SELECT TOP 1 row_num FROM (SELECT ROW_NUMBER() OVER (ORDER BY Id) row_num, Id FROM table) t WHERE t.Id > t.row_num) SELECT MAX (Id)+1 FROM table ELSE SELECT TOP 1 row_num FROM (SELECT ROW_NUMBER() OVER (ORDER BY Id) row_num, Id FROM table) t WHERE t.Id > t.row_num;

0
Enes Kalajac

Oracle DBの場合、これでジョブが実行されます。

SELECT MIN(NI) FROM
        (SELECT ROWNUM AS NI,YOUR_ID
         FROM (SELECT YOUR_ID
               FROM YOUR_TABLE 
               ORDER BY YOUR_ID ASC))
WHERE NI<>YOUR_ID
0
Patrick Eckert