Sqlserver
 sql >> Base de données >  >> RDS >> Sqlserver

Clé composite unique SQL Server de deux champs avec incrémentation automatique du deuxième champ

Depuis que quelqu'un a posté une question similaire, j'y réfléchis. Le premier problème est que les bases de données ne fournissent pas de séquences "partitionnables" (qui redémarreraient/se souviendraient en fonction de différentes clés). La seconde est que la SEQUENCE objets qui sont fournis sont conçus pour un accès rapide et ne peuvent pas être annulés (c'est-à-dire que vous allez obtenir des lacunes). Cela exclut essentiellement l'utilisation d'un utilitaire intégré... ce qui signifie que nous devons lancer le nôtre.

La première chose dont nous aurons besoin est une table pour stocker nos numéros de séquence. Cela peut être assez simple :

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

En réalité la base La colonne doit être une référence de clé étrangère à n'importe quelle table/id définissant les entreprises/entités pour lesquelles vous émettez des factures. Dans ce tableau, vous souhaitez que les entrées soient uniques par entité émise.

Ensuite, vous voulez un proc stocké qui prendra une clé (base ) et cracher le numéro suivant dans la séquence (invoiceNumber ). Le jeu de clés nécessaires variera (par exemple, certains numéros de facture doivent contenir l'année ou la date d'émission complète), mais le formulaire de base pour cette situation est le suivant :

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

Notez que :

  1. Vous devez exécuter ceci dans une transaction sérialisée
  2. La transaction doit être le même que celui qui est inséré dans la table de destination (facture).

C'est vrai, vous obtiendrez toujours un blocage par entreprise lors de l'émission de numéros de facture. Vous ne pouvez pas évitez cela si les numéros de facture doivent être séquentiels, sans espace - jusqu'à ce que la ligne soit réellement validée, elle pourrait être annulée, ce qui signifie que le numéro de facture n'aurait pas été émis.

Maintenant, puisque vous ne voulez pas avoir à vous rappeler d'appeler la procédure pour l'entrée, encapsulez-la dans un déclencheur :

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

(évidemment, vous avez plus de colonnes, y compris d'autres qui devraient être remplies automatiquement - vous devrez les remplir)
...que vous pouvez ensuite utiliser en disant simplement :

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

Alors, qu'avons-nous fait? La plupart du temps, tout ce travail consistait à réduire le nombre de lignes verrouillées par une transaction. Jusqu'à ce INSERT est validé, il n'y a que deux lignes verrouillées :

  • La ligne dans Invoice_Sequence maintenir le numéro de séquence
  • La ligne dans Invoice pour la nouvelle facture.

Toutes les autres lignes pour une base particulière sont gratuits - ils peuvent être mis à jour ou interrogés à volonté (la suppression d'informations de ce type de système a tendance à rendre les comptables nerveux). Vous devez probablement décider ce qui devrait se passer lorsque les requêtes incluraient normalement la facture en attente...