2009-05-25 20 views
6

Ich muss eine Abfrage schreiben, in der ich einen ID (eindeutigen Schlüssel) für einen bestimmten Datensatz zuordnen muss, der nicht verwendet wird/nicht generiert wird/nicht in der Datenbank existiert.Wie bekomme ich die erste ungenutzte ID in der Tabelle?

Kurz gesagt, ich muss einen id für einen bestimmten Datensatz erstellen und auf dem Bildschirm anzeigen.

E. g .:

 
ID Name 

1 abc 
2 def 
5 ghi 

Also, die Sache ist, dass es ID=3 als nächstes sofort zurückgeben soll, die noch nicht erzeugt wird, und nach dieser Generation des id, werde ich diese Daten speichern zurück zur Datenbanktabelle.

Es ist keine HW: Ich mache ein Projekt, und ich habe eine Anforderung, wo ich diese Abfrage schreiben muss, also brauche ich etwas Hilfe, um dies zu erreichen.

Also bitte führen Sie mich, wie diese Abfrage zu machen, oder wie dies zu erreichen.

Danke.

Ich bin nicht in der Lage Kommentare hinzufügen ,, so thats, warum ich meine Kommentare hier schreibe .. ich MySQL als Datenbank benutze ..

Meine Schritte so sein würde: -

1) Abrufen der ID aus der Datenbanktabelle, die nicht verwendet wird.

2) Wie sie sind nein. von Benutzern (website-basiertes Projekt), also möchte ich keine Nebenläufigkeit, also wenn eine ID für einen Benutzer generiert wird, sollte sie die Datenbank sperren, bis derselbe Benutzer die ID erhält und den Datensatz für diese ID speichert. Danach kann der andere Benutzer die ID abrufen, die nicht existiert. (Hauptanforderung) ..

Wie kann ich all diese Dinge in MySQL erreichen? Auch ich nehme Quassnoi's Antwort wird wert sein, aber es ist nicht wert Arbeiten in MySQL .. so erklären plz das Bit über die Abfrage, wie es neu für mich ist .. und wird diese Abfrage in MySQL funktionieren ..

+0

Welche RDBMS verwenden Sie für Ihr Projekt? – Quassnoi

+4

Vorsicht mit Gleichzeitigkeit, hier. Wenn Sie mehrere Benutzer haben, kann die Zeitlücke zwischen der Ausführung der Quassnoi-Abfrage und dem Speichern der Ergebnisse in der Datenbank zu doppelten IDs führen. Warum lassen Sie das RDBMS nicht einfach Ihre ID-Spalten verwalten? –

+2

Wie DDaviesBrackett schreibt, wenn dies keine Hausaufgaben sind, leidet es unter einem ernsten Problem der realen Welt: Zwei Prozesse können die Abfrage ausführen und ihre Antwort erhalten, und dann versucht jeder, einen doppelten Datensatz einzufügen. Wenn das nur die Frage beantworten soll: Gibt es Lücken? das ist anders. Es ist dann nur lustig, dass sich jemand darum kümmern würde. – Yishai

Antwort

6

Ich nannte Ihre Tabelle 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 

Diese Abfrage besteht aus zwei Teilen.

Der erste Teil:

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

eine 1 wählt gibt es keinen Eintrag mit diesem id in der Tabelle ist.

Der zweite Teil:

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 

wählt einen ersten id in der Tabelle für die es keine nächste id ist.

Die resultierende Abfrage wählt den kleinsten dieser beiden Werte aus.

+1

findet IDs nicht kleiner als die erste vorhandene ID. Ie. Wenn Tabelle hat IDs 3,4,6 findet 5, aber nicht 1 und 2.Sie können eine Verbindung mit einer anderen auswählen, die nach einer ID sucht, die größer als 0 und kleiner als die erste ID ist. –

+0

@Remus: Netter Punkt, Hinzufügen, danke. – Quassnoi

+0

Meine Schritte wären wie folgt: - 1) Abrufen der ID aus der Datenbanktabelle, die nicht verwendet wird. 2) Als ihre sind nein. von Benutzern (website-basiertes Projekt), so will ich keine Nebenläufigkeit passieren, wenn also eine ID für einen Benutzer erzeugt wird, sollte sie die Datenbank sperren, bis derselbe Benutzer die ID erhält und den Datensatz für diese ID speichert. Danach kann der andere Benutzer die ID abrufen, die nicht vorhanden ist. (Hauptanforderung) .. Wie kann ich alle diese Dinge in MySQL erreichen – AGeek

5

Hängt davon ab, was Sie mit "nächste ID" meinen und wie es generiert wird.

Wenn Sie eine Sequenz oder Identität in der Datenbank verwenden, um die ID zu generieren, ist es möglich, dass die "nächste ID" nicht 3 oder 4 ist, sondern 6 in dem von Ihnen angegebenen Fall. Sie haben keine Möglichkeit zu wissen, ob es Werte mit der ID 3 oder 4 gab, die anschließend gelöscht wurden. Sequenzen und Identitäten versuchen nicht unbedingt, Lücken wiederzugewinnen; Sobald sie weg sind, benutzt du sie nicht wieder.

Sie müssen also in Ihrer Datenbank eine Sequenz- oder Identitätsspalte erstellen, die bei einer INSERT-Operation automatisch inkrementiert wird, und dann SELECT den generierten Wert.

+0

Dies wird geschehen, aber da es verschiedene Benutzer gibt, die auf die Datenbank zugreifen werden, könnte es eine Zeit geben, wenn zwei Benutzer die gleiche ID erhalten. Wie ist es möglich, diese Nebenläufigkeit zu vermeiden, plz Geben Sie auch ein Beispiel dafür .. Thanx .. – AGeek

+3

Wenn Sie ein Auto_increment-Feld in MySQL verwenden, müssen Sie sich keine Gedanken über Nebenläufigkeit machen. Machen Sie einfach einen Benutzer, den Sie LAST_INSERT_ID() verwenden, um die ID der Zeile zu erhalten, die Sie gerade eingefügt haben. –

0

ist es erlaubt, einen Dienstprogrammtisch zu haben? wenn ja, würde ich eine Tabelle erstellen, etwa so:

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

Füllen Sie es mit allen positiven 32-Bit-Integer (vorausgesetzt, die id Sie müssen erzeugen, ist eine positive 32-Bit-Integer)

Dann können Sie wie so wählen :

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 

Haben nicht tatsächlich getestet, aber es sollte funktionieren.

+0

Offensichtlich wird dies leistungsmäßig lächerlich sein, aber es ist der einzige relativ einfache Weg (ich kann im Moment daran denken), die in der Frage dargelegten Spezifikationen zu erfüllen, im Gegensatz zu Identitätsspalten nur zu erklären. – 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

Der korrekte Weg ist die Verwendung einer Identitätsspalte für den Primärschlüssel. Versuchen Sie nicht, die bereits eingefügten Zeilen zu betrachten, und wählen Sie einen nicht verwendeten Wert aus. Die Id-Spalte sollte eine Zahl enthalten, die groß genug ist, damit Ihre Anwendung niemals gültige neue (höhere) Werte auslässt.

Wenn Sie in Ihrer Beschreibung Werte überspringen, die Sie später verwenden möchten, geben Sie den Werten wahrscheinlich eine Bedeutung. Bitte ueberlege es Dir nochmal. Sie sollten dieses Feld wahrscheinlich nur als Nachschlagewert (eine Referenz) aus einer anderen Tabelle verwenden.

Lassen Sie das Datenbankmodul den nächst höheren Wert für Ihre ID zuweisen. Wenn mehrere Prozesse gleichzeitig ausgeführt werden, müssen Sie die LAST_INSERT_ID() - Funktion verwenden, um die ID zu ermitteln, die die Datenbank für Ihre Zeile generiert hat. Sie können die LAST_INSERT_ID() -Funktion innerhalb der gleichen Transaktion verwenden, bevor Sie das Commit durchführen.

Zweitbeste (aber nicht gut!) Ist, den maximalen Wert des Indexfeldes plus eins zu verwenden. Sie müssen eine Tabellensperre einrichten, um die Probleme mit der Nebenläufigkeit zu verwalten.

+0

Richtig, und obwohl die Frage, die er gestellt hat, vielleicht nicht die richtige Frage für ihn ist, ist es eine nützliche Frage, um eine Antwort für einige von uns zu haben (dh eine unbenutzte Ressource aus einem begrenzten Ressourcenpool zuzuteilen, statt eine eindeutige Nummer zuzuweisen ein unbeschränkter Pool wie bei Primärschlüsseln). – ijw

0

Sollte unter MySql arbeiten.

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 
Verwandte Themen