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
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
それらを数値IDでソートすると、探している番号は、ROW_NUMBER()関数がIDと等しくない最初の番号になります。
これまでのところ、回答のロックまたは同時実行については言及されていません。
これらの2人のユーザーがほぼ同時にドキュメントを追加することを検討してください。
User 1 User 2
Find Id
Find Id
Id = 42
Id = 42
Insert (42..)
Insert (42..)
Error!
次のいずれかを行う必要があります:a)そのエラーを処理し、ループを再度利用して、次に利用可能なIDを探しますOR b)プロセスの開始時にロックアウトして、1人のユーザーのみが特定の時間にIDを探している
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を新しいドキュメントに再利用します。これで、電子メールの受信者は間違ったドキュメントへのリンクをたどります。
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つのテーブルをスキャンして、ハッシュの一致と上位の回答のような結合をスキャンしますか?
シーケンスにギャップがある場合は、次のようなもので最初のギャップを見つけることができます。
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
句を使用できるため、手続き型コードを使用する必要がない場合があります。
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
ここに簡単なアプローチがあります。速くないかもしれません。最初は欠けている数字は見つかりません。
SELECT MIN(MT1.MyInt+1)
FROM MyTable MT1
LEFT OUTER JOIN MyTable MT2 ON (MT1.MyInt+1)=MT2.MyInt
WHERE MT2.MyInt Is Null
それが可能な限り少ない数でなければならない理由はありますか?なぜ穴を埋める必要があるのですか?
編集ビジネスルールなので、答えを広告します。
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%正確ではないかもしれませんが、そこから取得できるはずです)
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は必要ありません。しばらくすると、低いものはすべて無料ですが...
私は同様の問題に直面し、これを思いつきました:
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
私はこの答えが遅いことを知っていますが、再帰的なテーブル式を使用することで、未使用の最小数を見つけることができます。
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
)
本当に列をIDENTITYに変換する必要があります。最初にBACKUPを使用し、次にROW_NUMBERを使用してドキュメントIDを更新して、ドキュメントIDが1から始まり、ドキュメント数に達するまで更新します。数値列が他のテーブル(外部キー)で参照として使用されている場合、SQL Serverは外部キーを更新しようとし、競合のために失敗する可能性があるため、一度に1つずつWHILEで行う必要があります。最後に、列のID仕様を有効にします。
:)作業が増えましたが、後で多くの問題を解決できます。
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;
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