2009-05-25 13 views
6

Devo scrivere una query in cui ho bisogno di allocare un ID (chiave univoca) per un particolare record che non viene utilizzato/non viene generato/non esiste nel database.Come si ottiene il primo ID non utilizzato nella tabella?

In breve, ho bisogno di generare un id per un particolare record e mostrarlo sullo schermo di stampa.

E. g .:

 
ID Name 

1 abc 
2 def 
5 ghi 

Quindi, la cosa è che deve restituire ID=3 come il prossimo immediato che non viene ancora generato, e dopo questa generazione del id, io memorizzare questi dati torna alla tabella del database.

Non è un HW: sto facendo un progetto, e ho un requisito in cui ho bisogno di scrivere questa query, quindi ho bisogno di aiuto per raggiungere questo obiettivo.

Quindi indicatemi come effettuare questa query o come ottenere ciò.

Grazie.

io non sono in grado di aggiungere commenti ,, quindi questo è il motivo per cui scrivo i miei commenti qui .. Sto usando MySQL come database ..

miei passi sarebbe come questo: -

1) Recupera l'id dalla tabella del database che non viene utilizzata ..

2) Come loro sono no. degli utenti (progetto basato sul sito web), quindi non voglio che si verifichi concorrenza, quindi se un ID viene generato a un utente, allora dovrebbe bloccare il database, finché lo stesso utente riceve l'id e memorizza il record per quell'id .. Dopo di che, l'altro utente può recuperare l'ID che non esiste. (Requisito importante) ..

Come posso ottenere tutte queste cose in MySQL, Suppongo anche che la risposta di Quassnoi varrà, ma non è lavorando in MySQL .. quindi plz spiega il bit sulla query poiché è nuovo per me .. e questa query funzionerà in MySQL ..

+0

Che tipo di RDBMS stai utilizzando per il tuo progetto? – Quassnoi

+4

Fai attenzione alla concorrenza, qui. Se si dispone di più utenti, il divario di tempo tra l'esecuzione della query di Quassnoi e la memorizzazione dei risultati nel DB può comportare la duplicazione degli ID. Perché non lasciare che RDBMS gestisca le colonne ID? –

+2

Come scrive DDaviesBrackett, se non si tratta di compiti a casa, soffre di un serio problema del mondo reale: due processi possono eseguire la query e ottenere la loro risposta, quindi ognuno tenta di inserire un record duplicato. Se questo è solo per rispondere alla domanda: ci sono delle lacune? è diverso È quindi divertente che a qualcuno importi. – Yishai

risposta

6

Ho chiamato il vostro tavolo unused.

SELECT id 
FROM (
     SELECT 1 AS id 
     ) q1 
WHERE NOT EXISTS 
     (
     SELECT 1 
     FROM unused 
     WHERE id = 1 
     ) 
UNION ALL 
SELECT * 
FROM (
     SELECT id + 1 
     FROM unused t 
     WHERE NOT EXISTS 
       (
       SELECT 1 
       FROM unused ti 
       WHERE ti.id = t.id + 1 
       ) 
     ORDER BY 
       id 
     LIMIT 1 
     ) q2 
ORDER BY 
     id 
LIMIT 1 

Questa query è composta da due parti.

La prima parte:

SELECT * 
FROM (
     SELECT 1 AS id 
     ) q 
WHERE NOT EXISTS 
     (
     SELECT 1 
     FROM unused 
     WHERE id = 1 
     ) 

seleziona un 1 è che non c'è alcuna voce nella tabella con questo id.

La seconda parte:

SELECT * 
FROM (
     SELECT id + 1 
     FROM unused t 
     WHERE NOT EXISTS 
       (
       SELECT 1 
       FROM unused ti 
       WHERE ti.id = t.id + 1 
       ) 
     ORDER BY 
       id 
     LIMIT 1 
     ) q2 

seleziona un primo id nella tabella per la quale non c'è prossima id.

La query risultante seleziona il minimo di questi due valori.

+1

non troverà gli ID più piccoli del primo ID esistente. Vale a dire. se la tabella ha ID 3,4,6 troverà 5, ma non 1 e 2.Puoi unione con un'altra selezione che cerca ID maggiore di 0 e minore del primo id. –

+0

@Remus: bel punto, aggiungendo, grazie. – Quassnoi

+0

I miei passi sarebbero così: - 1) Recupera l'id dalla tabella del database che non viene utilizzata .. 2) Come loro sono no. degli utenti (progetto basato sul sito web), quindi non voglio che si verifichi concorrenza, quindi se un ID viene generato a un utente, allora dovrebbe bloccare il database, finché lo stesso utente riceve l'id e memorizza il record per quell'id .. Dopodiché, l'altro utente può recuperare l'ID che non esiste. (Requisito importante) .. Come posso ottenere tutte queste cose in MySQL – AGeek

5

Dipende da cosa intendi per "ID successivo" e come viene generato.

Se si sta utilizzando una sequenza o un'identità nel database per generare l'id, è possibile che il "prossimo id" non sia 3 o 4 ma 6 nel caso che si è presentato. Non hai modo di sapere se ci sono o meno valori con id di 3 o 4 che sono stati successivamente cancellati. Sequenze e identità non cercano necessariamente di recuperare le lacune; una volta che se ne sono andati non li riutilizzi.

Quindi la cosa giusta da fare è creare una colonna di sequenza o identità nel database che viene automaticamente incrementata quando si esegue INSERT, quindi SELEZIONA il valore generato.

+0

Questo succederà, ma dal momento che ci sono diversi utenti che accederanno al database, potrebbe esserci un momento in cui due utenti ricevono lo stesso ID, quindi come è possibile evitare questa concorrenza? diamo anche qualche esempio .. Grazie .. – AGeek

+3

Se si utilizza un campo auto_increment in mysql, non ci si deve preoccupare della concorrenza. Basta fare in modo che l'utente usi LAST_INSERT_ID() dopo aver ottenuto l'ID della riga appena inserita. –

0

è possibile avere una tabella di utilità? se quindi vorrei creare una tabella in questo modo:

CREATE TABLE number_helper (
    n INT NOT NULL 
    ,PRIMARY KEY(n) 
); 

riempirlo con tutti i numeri interi positivi a 32 bit (supponendo che l'id è necessario generare è un 32 bit numero intero positivo)

Quindi è possibile selezionare in questo modo :

SELECT MIN(h.n) as nextID 
FROM my_table t 
LEFT JOIN number_helper h ON h.n = t.ID 
WHERE t.ID IS NULL 

In realtà non è stato testato ma dovrebbe funzionare.

+0

ovviamente questo farà schifo sulle prestazioni, ma è l'unico modo relativamente facile (posso pensare al momento) di soddisfare le specifiche esposte nella domanda, al contrario di spiegare semplicemente le colonne di identità. – Kris

1
/* 
This is a query script I wrote to illustrate my method, and it was created to solve a Real World problem where we have multiple machines at multiple stores creating transfer transactions in their own databases, 
that are then synced to other databases on the store (this happens often, so getting the Nth free entry for the Nth machine should work) where the transferid is the PK and then those are synced daily to a MainFrame where the maximum size of the key (which is the TransactionID and StoreID) is limited. 
*/ 

--- table variable declarations 
/* list of used transaction ids (this is just for testing, it will be the view or table you are reading the transaction ids from when implemented)*/ 

DECLARE @SampleTransferIDSourceTable TABLE(TransferID INT)  

/* Here we insert the used transaction numbers*/ 

DECLARE @WorkTable TABLE (WorkTableID INT IDENTITY (1,1), TransferID INT) 

/*this is the same table as above with an extra column to help us identify the blocks of unused row numbers (modifying a table variable is not a good idea)*/ 

DECLARE @WorkTable2 TABLE (WorkTableID INT , TransferID INT, diff int) 

--- Machine ID declared 

DECLARE @MachineID INT 

-- MachineID set 

SET @MachineID = 5 

-- put in some rows with different sized blocks of missing rows. 
-- comment out the inserts after two to the bottom to see how it handles no gaps or make 
-- the @MachineID very large to do the same. 
-- comment out early rows to test how it handles starting gaps. 

INSERT @SampleTransferIDSourceTable (TransferID) VALUES (1) 
INSERT @SampleTransferIDSourceTable (TransferID) VALUES (2) 
INSERT @SampleTransferIDSourceTable (TransferID) VALUES (4) 
INSERT @SampleTransferIDSourceTable (TransferID) VALUES (5) 
INSERT @SampleTransferIDSourceTable (TransferID) VALUES (6) 
INSERT @SampleTransferIDSourceTable (TransferID) VALUES (9) 
INSERT @SampleTransferIDSourceTable (TransferID) VALUES (10) 
INSERT @SampleTransferIDSourceTable (TransferID) VALUES (20) 
INSERT @SampleTransferIDSourceTable (TransferID) VALUES (21) 
INSERT @SampleTransferIDSourceTable (TransferID) VALUES (24) 
INSERT @SampleTransferIDSourceTable (TransferID) VALUES (25) 
INSERT @SampleTransferIDSourceTable (TransferID) VALUES (30) 
INSERT @SampleTransferIDSourceTable (TransferID) VALUES (31) 
INSERT @SampleTransferIDSourceTable (TransferID) VALUES (33) 
INSERT @SampleTransferIDSourceTable (TransferID) VALUES (39) 
INSERT @SampleTransferIDSourceTable (TransferID) VALUES (40) 
INSERT @SampleTransferIDSourceTable (TransferID) VALUES (50) 

-- copy the transaction ids into a table with an identiy item. 
-- When implemented add where clause before the order by to limit to the local StoreID 
-- Zero row added so that it will find gaps before the lowest used row. 

INSERT @WorkTable (TransferID) 

SELECT 0 

INSERT @WorkTable (TransferID) 

SELECT TransferID FROM @SampleTransferIDSourceTable ORDER BY TransferID 

-- copy that table to the new table with the diff column 

INSERT @WorkTable2 

SELECT WorkTableID,TransferID,TransferID - WorkTableID 

    FROM @WorkTable    

--- gives us the (MachineID)th unused ID or the (MachineID)th id beyond the highest id used. 

IF EXISTS (

SELECT Top 1 

     GapStart.TransferID + @MachineID - (GapStart.diff + 1) 

    FROM @WorkTable2 GapStart 

INNER JOIN @WorkTable2 GapEnd 

    ON GapStart.WorkTableID = GapEnd.WorkTableID - 1 

    AND GapStart.diff < GapEnd.diff 

    AND gapEnd.diff >= (@MachineID - 1) 

ORDER BY GapStart.TransferID 

) 

SELECT Top 1 

     GapStart.TransferID + @MachineID - (GapStart.diff + 1) 

    FROM @WorkTable2 GapStart 

INNER JOIN @WorkTable2 GapEnd 

    ON GapStart.WorkTableID = GapEnd.WorkTableID - 1 

    AND GapStart.diff < GapEnd.diff 

    AND gapEnd.diff >= (@MachineID - 1) 

ORDER BY GapStart.TransferID 

ELSE 

SELECT MAX(TransferID) + @MachineID FROM @SampleTransferIDSourceTable 
1

Il modo corretto è quello di utilizzare una colonna di identità per la chiave primaria. Non cercare di guardare le righe già inserite e selezionare un valore inutilizzato. La colonna Id deve contenere un numero abbastanza grande che la tua applicazione non esaurirà mai nuovi valori (più alti) validi.

Nella descrizione, se si saltano i valori che si sta tentando di utilizzare in un secondo momento, probabilmente si sta dando un significato ai valori. Per favore riconsidera. Probabilmente dovresti usare questo campo solo come valore di ricerca (un riferimento) da un'altra tabella.

Lascia che il motore di database assegni il successivo valore più alto per il tuo ID. Se è in esecuzione più di un processo contemporaneamente, sarà necessario utilizzare la funzione LAST_INSERT_ID() per determinare l'ID generato dal database per la riga. Puoi utilizzare la funzione LAST_INSERT_ID() all'interno della stessa transazione prima del commit.

Il secondo migliore (ma non buono!) È utilizzare il valore massimo del campo indice più uno. Dovresti fare un lock da tavolo per gestire i problemi di concorrenza.

+0

Vero, e anche se la domanda che ha posto potrebbe non essere la domanda giusta per lui, è in realtà una domanda utile per avere una risposta per alcuni di noi (ad esempio allocare una risorsa inutilizzata da un pool di risorse limitato, rispetto all'allocazione di un numero univoco da una piscina non vincolata come nel caso delle chiavi primarie). – ijw

0

Dovrebbe funzionare in MySql.

SELECT TOP 100 
    T1.ID + 1 AS FREE_ID 
FROM TABLE1 T1 
LEFT JOIN TABLE2 T2 ON T2.ID = T1.ID + 1 
WHERE T2.ID IS NULL 
Problemi correlati