2009-03-26 22 views
34

Come si trova il numero inutilizzato più piccolo in una colonna di SQL Server?Trova il numero inutilizzato più piccolo in SQL Server

Sto per importare un numero elevato di record registrati manualmente da Excel in una tabella di SQL Server. Hanno tutti un ID numerico (chiamato numero del documento), ma non sono stati assegnati in sequenza per ragioni che non si applicano più, ovvero da ora in poi quando il mio sito web registra un nuovo record, è necessario assegnargli il numero di documento più piccolo possibile (maggiore di zero) che non è già stato preso.

C'è un modo per farlo tramite semplice SQL o si tratta di un problema per TSQL/codice?

Grazie!

EDIT

Un ringraziamento speciale a WW per sollevare la questione della concorrenza. Poiché si tratta di un'app Web, è multithreading per definizione e chiunque debba affrontare questo stesso problema deve considerare un codice o un blocco del livello di DB per prevenire un conflitto.

LINQ

CRONACA - questo può essere realizzato tramite LINQ con il seguente codice:

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

risposta

48

trovare la prima riga in cui non esiste una riga con 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:

per gestire il caso particolare in cui l'id più basso esistente non è 1, ecco un brutto soluzione:

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 
+0

Mancherà qualsiasi blocco contiguo di ID che inizia all'inizio dell'intervallo. Ad esempio, se 'table' ha id (5,6,8,9,10) questo restituirà 7, non uno qualsiasi di 1-4. – joshperry

+0

@joshperry Hai ragione. Ho perso il commento sul voler riempire tutti gli id ​​più di zero. Ho aggiunto una brutta soluzione. Forse qualcuno suggerirà un miglioramento. –

+0

+1 Molto utile, grazie! La maggior parte delle altre risposte qui è stata "non è necessario farlo, lasciare che il sistema incrementi la chiave", ma il mio caso non è per la chiave primaria, ma invece un altro campo numerico univoco in cui gli slot vuoti sono un problema ma possono si verificano, e non come risultato di una cancellazione. –

2

c'è un motivo che deve essere il più piccolo numero possibile? Perché hai bisogno di riempire i buchi?

Modificare la data per rispondere, poiché si tratta di una regola aziendale.

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 

(non ho un db a portata di mano, quindi questo non può essere accurato al 100%, ma si dovrebbe essere in grado di arrivare da lì)

+0

Si tratta di una regola aziendale. Questi numeri di documento vengono quindi distribuiti agli utenti e effettivamente utilizzati. Ho fatto la stessa domanda, ma sono fermi su questo. :) –

+0

Questo è un peccato ... L'unico modo che conosco sarebbe quello di scorrere tutti loro fino a trovare un ID inutilizzato. Mi dispiace per la tua fortuna. –

3

Se ci sono lacune nella sequenza, si riesce a trovare il primo gap con qualcosa di simile:

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 

In altre parole, trovare il minimo ID il cui successore non esiste, e restituire che il successore. Se non ci sono spazi vuoti, restituisce uno più grande del più grande ID esistente. Viene inserito un ID segnaposto di 0 per assicurare che vengano presi in considerazione gli ID che iniziano con 1.

Si noti che ciò richiederà almeno n log n time.

Microsoft SQL consente l'utilizzo di una clausola from in una dichiarazione insert, pertanto potrebbe non essere necessario ricorrere al codice procedurale.

11

Se si ordinano per ID numerico, il numero che si sta cercando sarà il primo per il quale la funzione ROW_NUMBER() non corrisponde all'ID.

+0

+1 buon trucco specifico per SQL Server. Questo potrebbe essere fatto in una sottoselezione per selezionare il primo non-matching, quindi unionare con max (id) +1 per farlo in un colpo solo? – bobince

9
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; 

Questa è un'alternativa alle risposte utilizzando subquery correlate in @Jeffr ey Hantlin e @Darrel Miller.

Tuttavia, la politica che stai descrivendo non è davvero una buona idea. I valori ID devono essere univoci, ma non dovrebbe essere necessario che siano consecutivi.

Cosa succede se invii un'email a qualcuno con un collegamento al documento n. 42 e successivamente cancelli il documento? Successivamente, riutilizzerai l'id # 42 per un nuovo documento. Ora il destinatario dell'e-mail seguirà il collegamento al documento errato!

+0

Ammetto che questo non trova un valore mancante di 1. Tuttavia, è un problema fasullo, che è il mio vero punto, quindi non sono interessato a trovare una soluzione! :-P –

+0

I numeri dei documenti non vengono mai cancellati. Sono d'accordo con te, tuttavia, che questo è un modo povero per identificare i documenti. Sto prendendo le mie battaglie, però e ci sono pesci più grandi da friggere. –

12

Nessuna menzione del blocco o della concorrenza in nessuna delle risposte finora.

Considerare questi due utenti aggiungendo un documento quasi allo stesso tempo: -

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

Si sia necessario: a) Maniglia che l'errore e fare il giro del circuito di nuovo alla ricerca della prossima Id disponibili, OR b) Togliere un blocco all'inizio del processo in modo che solo 1 utente cerchi ID in un determinato momento

1

Dovresti davvero provare a convertire la colonna in IDENTITÀ. BACKUP quindi utilizza ROW_NUMBER per aggiornare l'ID del documento in modo che inizi da 1 e fino al conteggio dei documenti. Si dovrebbe farlo in uno WHILE alla volta perché se la colonna numerica viene utilizzata come riferimento in altre tabelle (chiavi esterne), SQL Server proverà ad aggiornare le chiavi esterne e potrebbe fallire a causa di conflitti. Alla fine basta abilitare le specifiche di identità per la colonna.

:) È più lavoro ora ma ti farà risparmiare un sacco di problemi dopo.

2
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 
3
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 

Does 1 tabella di scansione, piuttosto che 2 scans una corrispondenza hash e un join come la risposta in alto

+1

molto più veloce della risposta Top +1! –

2

Ecco un approccio semplice. Potrebbe non essere veloce. All'inizio non troverà numeri mancanti.

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

So che questa risposta è in ritardo ma è possibile trovare il numero superiore utilizzando una ricorsiva un'espressione di tabella:

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

Supponiamo gli ID dovrebbe sempre iniziare con 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 

Questo gestisce tutti i casi a cui riesco a pensare, tra cui nessun record esistente.

L'unica cosa che non mi piace di questa soluzione è che le condizioni supplementari devono essere inclusi due volte, come quella:

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 

Si prega di notare anche i commenti sul blocco e concorrenza - l'obbligo di colmare le lacune è nella maggior parte dei casi un cattivo design e può causare problemi. Tuttavia, I aveva una buona ragione per farlo: gli ID devono essere stampati e digitati dagli umani e non vogliamo avere ID con molte cifre dopo un po 'di tempo, mentre tutti quelli bassi sono gratuiti ...

0

ho affrontato un problema simile e arrivato fino a questo:

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 
Problemi correlati