Comment trouvez-vous le plus petit nombre inutilisé dans une colonne SQL Server?
Je suis sur le point d'importer un grand nombre d'enregistrements enregistrés manuellement depuis Excel dans une table SQL Server. Ils ont tous un ID numérique (appelé numéro de document), mais ils n'ont pas été attribués séquentiellement pour des raisons qui ne s'appliquent plus, ce qui signifie que lorsque mon site Web enregistre un nouvel enregistrement, il doit lui attribuer le plus petit numéro de document possible ( supérieur à zéro) qui n'a pas encore été pris.
Existe-t-il un moyen de le faire via SQL simple ou est-ce un problème pour TSQL/code?
Merci!
[~ # ~] modifier [~ # ~]
Un merci spécial à WW pour avoir soulevé le problème de la concurrence. Étant donné qu'il s'agit d'une application Web, elle est multi-thread par définition et toute personne confrontée à ce même problème devrait envisager un code ou un verrou de niveau DB pour éviter un conflit.
[~ # ~] linq [~ # ~]
FYI - cela peut être accompli via LINQ avec le code suivant:
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
Trouver la première ligne où il n'existe pas de ligne avec 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
Éditer:
Pour gérer le cas spécial où l'ID existant le plus bas n'est pas 1, voici une solution laide:
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
Si vous les triez par ID numérique, le numéro que vous recherchez sera le premier pour lequel la fonction ROW_NUMBER () ne correspond pas à l'ID.
Aucune mention de verrouillage ou d'accès simultané dans aucune des réponses à ce jour.
Considérez ces deux utilisateurs ajouter un document presque en même temps: -
User 1 User 2
Find Id
Find Id
Id = 42
Id = 42
Insert (42..)
Insert (42..)
Error!
Vous devez soit: a) Gérer cette erreur et faire à nouveau le tour de la boucle à la recherche de l'ID disponible suivant, OR b) Prendre un verrou au début du processus afin qu'un seul utilisateur soit recherche d'identifiants à un moment particulier
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;
Ceci est une alternative aux réponses utilisant des sous-requêtes corrélées données par @Jeffrey Hantlin et @Darrel Miller.
Cependant, la politique que vous décrivez n'est vraiment pas une bonne idée. Les valeurs d'ID doivent être uniques, mais ne doivent pas nécessairement être consécutives.
Que se passe-t-il si vous envoyez un e-mail à quelqu'un avec un lien vers le document n ° 42, puis supprimez le document par la suite? Plus tard, vous réutilisez l'ID # 42 pour un nouveau document. Maintenant, le destinataire de l'e-mail suivra le lien vers le mauvais document!
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
Est-ce que 1 table analyse plutôt que 2 scanne une correspondance de hachage et une jointure comme la première réponse
S'il y a des lacunes dans la séquence, vous pouvez trouver la première lacune avec quelque chose comme ceci:
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
En d'autres termes, recherchez le moins ID dont le successeur n'existe pas et renvoyez ce successeur. S'il n'y a pas d'espace, il renvoie un supérieur au plus grand ID existant. Un ID d'espace réservé de 0 est inséré pour garantir que les ID commençant par 1 sont pris en compte.
Notez que cela prendra au moins n log n fois.
Microsoft SQL autorise l'utilisation d'une clause from
dans une instruction insert
, vous n'aurez donc pas besoin de recourir au code procédural.
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
Voici une approche simple. Ce ne sera peut-être pas rapide. Il ne trouvera pas les numéros manquants au début.
SELECT MIN(MT1.MyInt+1)
FROM MyTable MT1
LEFT OUTER JOIN MyTable MT2 ON (MT1.MyInt+1)=MT2.MyInt
WHERE MT2.MyInt Is Null
Y a-t-il une raison pour que ce soit le plus petit nombre possible? Pourquoi avez-vous besoin de remplir les trous?
Modifiez pour afficher la réponse, car il s'agit d'une règle commerciale.
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
(Je n'ai pas de base de données à portée de main, donc ce n'est peut-être pas précis à 100%, mais vous devriez pouvoir l'obtenir à partir de là)
Supposons que vos identifiants doivent toujours commencer par 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
Cela gère tous les cas auxquels je peux penser - y compris aucun enregistrement existant du tout.
La seule chose que je n'aime pas dans cette solution, c'est que des conditions supplémentaires doivent être incluses deux fois, comme ça:
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
Veuillez également noter les commentaires sur le verrouillage et la concurrence - l'exigence de combler les lacunes est dans la plupart des cas une mauvaise conception et peut causer des problèmes. Cependant, I avait une bonne raison de le faire: les identifiants doivent être imprimés et tapés par des humains et nous ne voulons pas avoir d'identifiants avec plusieurs chiffres après un certain temps, alors que tous les bas sont gratuits ...
J'ai fait face à un problème similaire et j'ai trouvé ceci:
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
Je sais que cette réponse est en retard mais vous pouvez trouver le plus petit nombre inutilisé en utilisant une expression de table récursive:
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
)
Vous devriez vraiment essayer de convertir la colonne en IDENTITÉ. SAUVEGARDER d'abord, puis utiliser ROW_NUMBER pour mettre à jour l'ID du document afin qu'ils commencent à partir de 1 jusqu'au nombre de documents. Vous devez le faire en même temps, car si la colonne numérique est utilisée comme référence dans d'autres tables (clés étrangères), SQL Server essaiera de mettre à jour les clés étrangères et échouera peut-être en raison de conflits. Au final, il suffit d'activer les spécifications d'identité pour la colonne.
:) C'est plus de travail maintenant mais cela vous évitera beaucoup de problèmes plus tard.
Exemple de fonction 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;
Pour Oracle DB, cela devrait faire le travail:
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