sql >> Databasteknik >  >> RDS >> Sqlserver

SQL Server Unik sammansatt nyckel av två fält med andra fält automatisk ökning

Ända sedan någon postade en liknande fråga har jag funderat på detta. Det första problemet är att DB:er inte tillhandahåller "partitionerbara" sekvenser (som skulle starta om/komma ihåg baserat på olika nycklar). Den andra är att SEQUENCE objekt som är tillhandahålls är inriktade på snabb åtkomst och kan inte återställas (dvs. du kommer). få luckor). Detta utesluter i huvudsak att använda ett inbyggt verktyg... vilket betyder att vi måste rulla vårt eget.

Det första vi kommer att behöva är en tabell för att lagra våra sekvensnummer. Detta kan vara ganska enkelt:

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

I verkligheten base kolumnen bör vara en främmande nyckelreferens till vilken tabell/id som än definierar företagen/enheterna som du utfärdar fakturor för. I den här tabellen vill du att poster ska vara unika per utfärdad enhet.

Därefter vill du ha en lagrad proc som tar en nyckel (base ) och spotta ut nästa nummer i sekvensen (invoiceNumber ). Uppsättningen nycklar som krävs kommer att variera (dvs vissa fakturanummer måste innehålla året eller fullständigt utfärdandedatum), men grundformuläret för denna situation är som följer:

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

Observera att:

  1. Du måste kör detta i en serialiserad transaktion
  2. Transaktionen måste vara samma som infogas i destinationstabellen (faktura).

Det stämmer, du kommer fortfarande att få blockering per företag när du utfärdar fakturanummer. Du kan inte undvik detta om fakturanumren måste vara sekventiella, utan luckor - tills raden faktiskt har begåtts kan den återställas, vilket innebär att fakturanumret inte skulle ha utfärdats.

Nu, eftersom du inte vill behöva komma ihåg att anropa proceduren för posten, slå in den 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

(uppenbarligen har du fler kolumner, inklusive andra som ska fyllas i automatiskt - du måste fylla i dem)
...som du sedan kan använda genom att helt enkelt säga:

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

Så vad har vi gjort? För det mesta handlade allt detta arbete om att minska antalet rader som låstes av en transaktion. Tills denna INSERT är begått, finns det bara två rader låsta:

  • Raden i Invoice_Sequence bibehålla sekvensnumret
  • Raden i Invoice för den nya fakturan.

Alla andra rader för en viss base är gratis - de kan uppdateras eller frågas efter behag (att ta bort information från denna typ av system tenderar att göra revisorer nervösa). Du måste antagligen bestämma vad som ska hända när frågor normalt skulle innehålla den väntande fakturan...



  1. Hitta det maximala antalet år i följd för varje ID i en tabell (Oracle SQL)

  2. Castar NULL-typ vid uppdatering av flera rader

  3. Varför behöver vi meddelandeförmedlare som RabbitMQ över en databas som PostgreSQL?

  4. Uppgradera rader endast vid appuppgradering