web-dev-qa-db-fra.com

Trouver le plus petit nombre inutilisé dans SQL Server

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

41
Michael La Voie

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
59
Darrel Miller

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.

12
MarkusQ

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

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;

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!

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

Est-ce que 1 table analyse plutôt que 2 scanne une correspondance de hachage et une jointure comme la première réponse

5
user6586

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.

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

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
2
Rich Garrett

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à)

2
Matt Grande

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 ...

2
maf-soft

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
1
William Mueller

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
)
1
John Odom

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.

1
Ovidiu Pacurar

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;

0
Enes Kalajac

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
0
Patrick Eckert