sql >> Database teknologi >  >> RDS >> Sqlserver

SQL Server Unik sammensat nøgle af to felter med andet felt automatisk stigning

Lige siden nogen postede et lignende spørgsmål, har jeg overvejet dette. Det første problem er, at DB'er ikke giver "partitionerbare" sekvenser (som ville genstarte/huske baseret på forskellige nøgler). Den anden er, at SEQUENCE objekter, der er forudsat er rettet mod hurtig adgang og kan ikke rulles tilbage (dvs. du vil). få huller). Dette udelukker i bund og grund at bruge et indbygget hjælpeprogram... hvilket betyder, at vi skal rulle vores eget.

Den første ting, vi skal bruge, er en tabel til at gemme vores sekvensnumre. Dette kan være ret simpelt:

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

I virkeligheden er base kolonnen skal være en fremmednøglereference til den tabel/id, der definerer de(n) virksomhed(er)/enheder, du udsteder fakturaer for. I denne tabel ønsker du, at poster skal være unikke pr. udstedt enhed.

Dernæst vil du have en lagret proc, der tager en nøgle (base ) og spyt det næste tal ud i rækkefølgen (invoiceNumber ). Det nødvendige sæt nøgler vil variere (dvs. nogle fakturanumre skal indeholde året eller den fulde udstedelsesdato), men basisformularen for denne situation er som følger:

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

Bemærk at:

  1. Du skal køre dette i en serialiseret transaktion
  2. Transaktionen skal være den samme, som indsætter i destinationstabellen (faktura).

Det er rigtigt, du vil stadig få blokering pr. virksomhed, når du udsteder fakturanumre. Du kan ikke undgå dette, hvis fakturanumre skal være sekventielle uden huller - indtil rækken faktisk er begået, kan den blive rullet tilbage, hvilket betyder, at fakturanummeret ikke ville være blevet udstedt.

Nu, da du ikke ønsker at skulle huske at kalde proceduren for indtastningen, skal du pakke det ind i en 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

(du har selvfølgelig flere kolonner, inklusive andre, der skal udfyldes automatisk - du skal udfylde dem)
...som du så kan bruge ved blot at sige:

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

Så hvad har vi gjort? For det meste handlede alt dette arbejde om at formindske antallet af rækker låst af en transaktion. Indtil denne INSERT er begået, er der kun to rækker låst:

  • Rækken i Invoice_Sequence bevare sekvensnummeret
  • Rækken i Invoice for den nye faktura.

Alle andre rækker for en bestemt base er gratis - de kan opdateres eller forespørges efter behag (sletning af oplysninger fra denne form for system har en tendens til at gøre revisorer nervøse). Du skal sandsynligvis beslutte, hvad der skal ske, når forespørgsler normalt vil omfatte den afventende faktura...



  1. Brug af en betinget UPDATE-sætning i SQL

  2. Tilføj 2 måneder til det nuværende tidsstempel

  3. Introduktion til SQL Server

  4. CRUD-drift med ASP.NET Core MVC, Entity Framework Core og SQL Server