How do you find the smallest unused number in a SQL Server column?
I am about to import a large number of manually recorded records from Excel into a SQL Server table. They all have a numeric ID (called document number), but they weren't assigned sequentially for reasons that no longer apply, meaning from now on when my web site records a new record, it needs to assign it the smallest possible document number (greater than zero) that has not already been taken.
Is there a way to do this through plain SQL or is this a problem for TSQL/code?
Thanks!
EDIT
Special thanks to WW for raising the issue of concurrency. Given that this is a web app, it is multi-threaded by definition and anyone faced with this same problem should consider either a code or DB level lock to prevent a conflict.
LINQ
FYI - this can be accomplished via LINQ with the following code:
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
Find the first row where there does not exist a row with 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
Edit:
To handle the special case where the lowest existing id is not 1, here is a ugly solution:
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
If you sort them by numeric ID, the number you are looking for will be the first one for which the ROW_NUMBER() function doesn't equal the ID.
No mention of locking or concurrency in any of the answers so far.
Consider these two users adding a document at nearly the same time:-
User 1 User 2
Find Id
Find Id
Id = 42
Id = 42
Insert (42..)
Insert (42..)
Error!
You either need to: a) Handle that error and go around the loop again looking for the next available Id, OR b) Take a lock out at the start of the process so only 1 user is looking for Ids at a particular time
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;
This is an alternative to the answers using correlated subqueries given by @Jeffrey Hantlin and @Darrel Miller.
However, the policy you're describing is really not a good idea. ID values should be unique, but should not be required to be consecutive.
What happens if you email someone with a link to document #42, and then subsequently delete the document? Later, you re-use the id #42 for a new document. Now the recipient of the email will follow the link to the wrong document!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With