Sqlserver
 sql >> Datenbank >  >> RDS >> Sqlserver

Eindeutiger zusammengesetzter SQL Server-Schlüssel aus zwei Feldern mit automatischer Inkrementierung des zweiten Felds

Seitdem jemand eine ähnliche Frage gepostet hat, habe ich darüber nachgedacht. Das erste Problem besteht darin, dass DBs keine "partitionierbaren" Sequenzen bereitstellen (die basierend auf unterschiedlichen Schlüsseln neu gestartet/erinnert würden). Die zweite ist die SEQUENCE Objekte, die sind bereitgestellt werden, sind auf schnellen Zugriff ausgerichtet und können nicht zurückgesetzt werden (d. h. Sie werden Lücken bekommen). Dies schließt im Wesentlichen die Verwendung eines integrierten Dienstprogramms aus ... was bedeutet, dass wir unser eigenes erstellen müssen.

Das erste, was wir brauchen, ist eine Tabelle, um unsere Sequenznummern zu speichern. Das kann ziemlich einfach sein:

CREATE TABLE Invoice_Sequence (base CHAR(1) PRIMARY KEY CLUSTERED,
                               invoiceNumber INTEGER);

In Wirklichkeit die base Spalte sollte ein Fremdschlüsselverweis auf eine beliebige Tabelle/ID sein, die die Unternehmen/Entitäten definiert, für die Sie Rechnungen ausstellen. In dieser Tabelle sollen die Einträge pro ausgestellter Entität eindeutig sein.

Als Nächstes möchten Sie eine gespeicherte Prozedur, die einen Schlüssel akzeptiert (base ) und spucken die nächste Nummer in der Folge aus (invoiceNumber ). Der erforderliche Schlüsselsatz variiert (dh einige Rechnungsnummern müssen das Jahr oder das vollständige Ausstellungsdatum enthalten), aber die Grundform für diese Situation ist wie folgt:

CREATE PROCEDURE Next_Invoice_Number @baseKey CHAR(1), 
                                     @invoiceNumber INTEGER OUTPUT 
AS MERGE INTO Invoice_Sequence Stored
              USING (VALUES (@baseKey)) Incoming(base)
                 ON Incoming.base = Stored.base
   WHEN MATCHED THEN UPDATE SET Stored.invoiceNumber = Stored.invoiceNumber + 1
   WHEN NOT MATCHED BY TARGET THEN INSERT (base) VALUES(@baseKey)
   OUTPUT INSERTED.invoiceNumber ;;

Beachten Sie Folgendes:

  1. Sie müssen Führen Sie dies in einer serialisierten Transaktion aus
  2. Die Transaktion muss dieselbe sein, die in die Zieltabelle (Rechnung) eingefügt wird.

Das ist richtig, Sie werden immer noch pro Unternehmen gesperrt, wenn Sie Rechnungsnummern ausgeben. Sie können nicht Vermeiden Sie dies, wenn Rechnungsnummern lückenlos fortlaufend sein müssen - bis die Zeile tatsächlich festgeschrieben ist, könnte sie zurückgesetzt werden, was bedeutet, dass die Rechnungsnummer nicht ausgegeben worden wäre.

Da Sie nun nicht daran denken müssen, die Prozedur für den Eintrag aufzurufen, packen Sie ihn in einen Trigger:

CREATE TRIGGER Populate_Invoice_Number ON Invoice INSTEAD OF INSERT
AS 
  DECLARE @invoiceNumber INTEGER
  BEGIN
    EXEC Next_Invoice_Number Inserted.base, @invoiceNumber OUTPUT
    INSERT INTO Invoice (base, invoiceNumber) 
                VALUES (Inserted.base, @invoiceNumber)
  END

(Offensichtlich haben Sie mehr Spalten, einschließlich anderer, die automatisch ausgefüllt werden sollten - Sie müssen sie ausfüllen)
... die Sie dann verwenden können, indem Sie einfach sagen:

INSERT INTO Invoice (base) VALUES('A');

Was haben wir also getan? Meistens ging es bei all dieser Arbeit darum, die Anzahl der durch eine Transaktion gesperrten Zeilen zu verringern. Bis zu diesem INSERT festgeschrieben ist, sind nur zwei Zeilen gesperrt:

  • Die Zeile in Invoice_Sequence Beibehaltung der laufenden Nummer
  • Die Zeile in Invoice für die neue Rechnung.

Alle anderen Zeilen für eine bestimmte base sind kostenlos - sie können nach Belieben aktualisiert oder abgefragt werden (das Löschen von Informationen aus dieser Art von System macht Buchhalter nervös). Wahrscheinlich müssen Sie entscheiden, was passieren soll, wenn Abfragen normalerweise die ausstehende Rechnung beinhalten würden...