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

Chiave composita univoca di SQL Server di due campi con incremento automatico del secondo campo

Da quando qualcuno ha postato una domanda simile, ho riflettuto su questo. Il primo problema è che i DB non forniscono sequenze "partizionabili" (che verrebbero riavviate/ricordate in base a chiavi diverse). Il secondo è che il SEQUENCE oggetti che sono forniti sono orientati all'accesso rapido e non possono essere ripristinati (ad esempio, farai ottenere spazi vuoti). Questo essenzialmente esclude l'uso di un'utilità incorporata... il che significa che dobbiamo lanciare il nostro.

La prima cosa di cui avremo bisogno è una tabella in cui memorizzare i nostri numeri di sequenza. Questo può essere abbastanza semplice:

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

In realtà la base la colonna dovrebbe essere un riferimento di chiave esterna a qualsiasi tabella/id definisca le attività/entità per cui stai emettendo le fatture. In questa tabella, desideri che le voci siano univoche per entità emessa.

Successivamente, vuoi un processo memorizzato che richiederà una chiave (base ) e sputa il numero successivo nella sequenza (invoiceNumber ). Il set di chiavi necessario varia (ad esempio, alcuni numeri di fattura devono contenere l'anno o la data completa di emissione), ma il modulo di base per questa situazione è il seguente:

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

Nota che:

  1. Devi devi eseguilo in una transazione serializzata
  2. La transazione deve essere lo stesso che sta inserendo nella tabella di destinazione (fattura).

Esatto, riceverai comunque il blocco per azienda quando emetti i numeri di fattura. Non puoi evitalo se i numeri di fattura devono essere sequenziali, senza spazi vuoti - fino a quando la riga non viene effettivamente impegnata, potrebbe essere annullata, il che significa che il numero di fattura non sarebbe stato emesso.

Ora, visto che non vuoi doverti ricordare di chiamare la procedura per la voce, avvolgila in un 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

(ovviamente, hai più colonne, comprese altre che dovrebbero essere compilate automaticamente - dovrai compilarle)
...che puoi quindi utilizzare semplicemente dicendo:

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

Allora cosa abbiamo fatto? Per lo più, tutto questo lavoro riguardava la riduzione del numero di righe bloccate da una transazione. Fino a questo INSERT è stato eseguito il commit, ci sono solo due righe bloccate:

  • La riga in Invoice_Sequence mantenendo il numero di sequenza
  • La riga in Invoice per la nuova fattura.

Tutte le altre righe per una particolare base sono gratuiti - possono essere aggiornati o interrogati a piacimento (l'eliminazione di informazioni da questo tipo di sistema tende a rendere nervosi i contabili). Probabilmente devi decidere cosa dovrebbe accadere quando le query normalmente includerebbero la fattura in sospeso...