I. Introduction▲
Il est parfois nécessaire de fournir des compteurs sans trou pour certains documents (ex. : documents comptables) ou pour certains produits industriels (numéros de série). Ce document présente un moyen fiable de maintenir à jour un compteur lors de la suppression de documents ou l'annulation de ces derniers.
II. Les générateurs Interbase▲
Les générateurs Interbase sont utilisés en général afin de fabriquer des clefs primaires pour les tables. En effet, on peut être assuré de l'unicité du numéro généré même lors d'un accès concurrentiel, la génération du numéro se faisant hors transaction.
III. les générateurs continus▲
Il est nécessaire pour les développeurs de pouvoir générer des numéros uniques pour, par exemple, des numéros de facture. Ces numéros doivent êtres chronologiques et sans trou (nous parlerons d'une séquence pure).
Le problème majeur de l'utilisation des générateurs pour créer des numéros de documents est que si un document est annulé ou supprimé, le numéro généré ne sera jamais retrouvé, le générateur ne pouvant pas revenir en arrière sans modification des Metadata de la base de données. La séquence est donc interrompue. Il est bien évidemment trop dangereux de modifier la valeur du générateur sous peine de ne plus avoir une clef primaire valide.
IV. Une séquence pure▲
Pour obtenir une séquence pure, une solution est de créer une table dans laquelle le nouveau numéro sera disponible ainsi que les numéros recyclés.
Le problème est que l'accès a la table demande l'ouverture d'une transaction ce qui risque, lors d'un accès concurrent de fournir un numéro erroné. La solution présentée combine un générateur ainsi qu'une table de log afin de conserver les numéros a recyclé ainsi qu'un système de verrou sur les enregistrements dont nous souhaitons obtenir la valeur.
V. Présentation des tables▲
CREATE TABLE FACTURES(
ID INTEGER NOT NULL,
DATE_FAC DATE NOT NULL,
NUM_FAC INTEGER,/*Cette séquence devra être pure*/
CONSTRAINT PK_INV_HEADER PRIMARY KEY(ID));Cette table correspond à la liste des factures. L'ID est un générateur standard tandis que NUM_FAC doit être un séquenceur pur.
CREATE TABLE LOG_FACTURES(
LOG_ID INTEGER NOT NULL,
NUM_FACTURE INTEGER,/*Les numéros de facture a recycler*/
ID_FACTURE INTEGER,
STATUS VARCHAR(20),
MOTIF VARCHAR(50),
GEN_DATE DATE,
LOGDATE DATE,
CONSTRAINT PK_INV_LOG PRIMARY KEY(LOG_ID));La table de log. C'est dans cette table que nous allons rechercher les informations du compteur ainsi que les numéros à recycler. Nous verrons tout ça plus en détail lors de la description des triggers.
CREATE GENERATOR GEN_INV_HEADER_ID;
CREATE GENERATOR GEN_INV_LOG_ID;
CREATE GENERATOR GEN_INV_NUMBER;Les deux générateurs pour les clefs primaires et le numéro de document.
CREATE TRIGGER BI_INSERT_INV_HDR FOR FACTURES ACTIVE BEFORE INSERT
POSITION 8 AS BEGIN
IF (NEW.ID IS NULL) THEN
NEW.ID = GEN_ID(GEN_INV_HEADER_ID, 1);
IF (NEW.DATE_FAC IS NULL) THEN
NEW.DATE_FAC='NOW';
ENDCREATE TRIGGER BI_INSERT_INV_LOG FOR LOG_FACTURES ACTIVE BEFORE
INSERT POSITION 9 AS BEGIN
IF (NEW.LOG_ID IS NULL) THEN
NEW.LOG_ID = GEN_ID(GEN_INV_LOG_ID, 1);
ENDCes triggers permettent simplement de créer les clefs primaires. Le trigger suivant permet de récupérer le numéro de document correct. On commence par rechercher la liste des numéros à recycler dans la table de LOG et on prend le plus petit. Si un numéro est disponible, on le verrouille grâce au Update pour être sure de l'atomicité de l'opération. Cette étape est très importante ça c'est ce Update qui va permettre de garantir les accès concurrents a la table contenant les numéros. Une fois le trigger terminé, la transaction est libérée et les verrous avec. Si aucun numéro n'est disponible, on prend simplement la valeur du générateur.
CREATE TRIGGER BI_INSERT_INV_NUM FOR FACTURES ACTIVE BEFORE INSERT
POSITION 9 AS
DECLARE VARIABLE INV_NO INTEGER;
BEGIN
INV_NO = 0;
SELECT MIN(INV_NUMBER) FROM INV_LOG
WHERE STATUS='AVAILABLE'
INTO INV_NO;
IF (INV_NO > 0) THEN
UPDATE INV_LOG SET STATUS=STATUS /* locks the record */
WHERE INV_NUMBER= :INV_NO;
ELSE
BEGIN
INV_NO = GEN_ID(GEN_INV_NUMBER, 1);
INSERT INTO LOG_FACTURES(INV_NUMBER, GEN_DATE)
VALUES(:INV_NO, 'NOW');
END
NEW.INV_NUMBER = :INV_NO;
ENDUne fois l'insertion terminée, on peut mettre à jour la table de log pour confirmer l'état 'USED' du numéro. Ceci sera fait à travers le trigger AfterInsert de la table FACTURES.
CREATE TRIGGER AI_INSERT_INV_NUM FOR FACTURES ACTIVE AFTER INSERT
10 AS BEGIN
UPDATE LOG_FACTURES SET STATUS='USED',
ID_FACTURE = NEW.ID_FACTURE,
MOTIF ='',
LOGDATE = 'NOW'
WHERE NUM_FACTURE = NEW.NUM_FACTURE;
ENDNous allons ajouter deux procédures afin de supprimer ou d'annuler la création d'un nouveau document.
CREATE PROCEDURE SP_CANCEL_FACTURE (NUM_FACTURE INTEGER, MOTIF
VARCHAR(50)) AS BEGIN
UPDATE LOG_FACTURES
SET STATUS = 'CANCELLED',
MOTIF = :MOTIF
WHERE NUM_FACTURE = :NUM_FACTURE
ENDCREATE PROCEDURE SP_ANNULE_NUM_FACT(NUM_FACTURE INTEGER) AS BEGIN
UPDATE INV_LOG
SET NUM_FACTURE = NULL,
STATUS = 'AVAILABLE',
REASON = 'INVOICE ABORTED',
LOGDATE = 'NOW'
WHERE NUM_FACTURE = :NUM_FACTURE;
ENDEnfin, le trigger appelé lors de la suppression d'un document.
CREATE TRIGGER AD_DELETE_INV_HDR FOR FACTURES ACTIVE AFTER DELETE
POSITION 9 AS BEGIN
UPDATE LOG_FACTURES
SET NUM_FACTURE = NULL,
STATUS='AVAILABLE',
REASON='INVOICE DELETED',
LOGDATE='NOW'
WHERE NUM_FACTURE=OLD.NUM_FACTURE;
ENDVI. Conclusion▲
Grâce à ces quelques scripts SQL, il est possible de créer des générateurs continus paramétrables acceptant des accès concurrentiels très facilement. Ces exemples sont en effet adaptables à quasiment tous les projets.
Merci à http://www.ibobjects.com, à yobenzen pour ses conseils, à bouyao pour la correction et à toute l'équipe developpez.com.



