CORRECTION
CREATE TRIGGER E_CLI_INS
ON T_CLIENT
FOR INSERT, UPDATE
AS
-- requête de contrôle avec table d'insertion
SELECT CAST(REPLACE(CLI_TEL, '.', '') as DECIMAL(20))
FROM INSERTED
-- rollback en cas d'erreur
IF @@Error <> 0
ROLLBACK TRANSACTION
La première tentative de modification :
UPDATE T_CLIENT
SET CLI_TEL = '01 02 03 04 05'
WHERE CLI_ID = 1
Serveur : Msg 8114, Niveau 16, État 5, Procédure E_CLI_INS, Ligne 6
Erreur de conversion du type de données varchar en numeric.
provoque une erreur et l'insertion n'a pas lieu.
Tandis que la seconde va bien produire ses effets :
UPDATE T_CLIENT
SET CLI_TEL = '91.92.93.94.95'
WHERE CLI_ID = 1
Le seul inconvénient est que cette façon de procéder rejette toutes les lignes insérées ou mise à jour sans accepter celles qui peuvent être correctement formatées.
D'autre part on exécute cette procédure jusqu'au bout, même si la colonne CLI_TEL ne subie aucune modification. Néanmoins ce cas peut être résolu par un traitement spécifique utilisant la fonction UPDATE :
CREATE TRIGGER E_CLI_INS
ON CLIENT
FOR INSERT, UPDATE
AS
-- inutile si pas d'update de la colonne visée
IF NOT UPDATE(CLI_TEL)
RETURN
-- requête de contrôle avec table d'insertion
SELECT CAST(REPLACE(CLI_TEL, '.', '') as DECIMAL(20))
FROM INSERTED
-- rollback en cas d'erreur
IF @@Error <> 0
ROLLBACK TRANSACTION
2- - L'exercice consiste maintenant à corriger à la volée des saisie incorrectes. Tous les caractères de séparation tel que le tiret ou l'espace d'un numéro de téléphone devra être convertis en point.
CORRECTION
CREATE TRIGGER E_CLI_INS
ON CLIENT
FOR UPDATE
AS
-- inutile si pas d'update de la colonne visée
IF NOT UPDATE(CLI_TEL)
RETURN
-- requête de correction avec table d'insertion
UPDATE client
SET cli_tel =
REPLACE(REPLACE(I.CLI_TEL, ' ', '.'), '-', '.')
FROM T_CLIENT C
INNER JOIN INSERTED I
ON C.CLI_ID = I.CLI_ID
-- rollback en cas d'erreur
IF @@Error <> 0
ROLLBACK TRANSACTION
Ainsi l'ordre :
UPDATE T_CLIENT
SET CLI_TEL = '88 77-66 55.44'
WHERE CLI_ID = 1
donne pour résultat :
cli_id cli_nom Cli_tel
----------- -------------------------------- --------------------
1 DUPONT 88.77.66.55.44
et la saisie du numéro de téléphone a été corrigé à la volée et se trouve désormais au format voulu !
Attention : le danger réside dans l'exécution récursive de tels triggers. Comme l'on remet à jour la table à l'intérieur même du trigger, celui-ci est à nouveau déclenché. Le phénomène, s'il n'était pas limité, pourrait provoquer une famine du processus. Il faut donc veiller à le limiter. Dans ce sens SQL Server propose deux garde fous : le premier, intrinsèque au serveur est de ne jamais dépasser 16 niveaux de récursion. Le second est de proposer une limite plus restrictive à l'aide de la procédure sp_configure, qui permet de modifier la variable nested triggers afin d'étendre les limites d'appel de triggers imbriqués.
De plus pour connaître le niveau d'imbrication du trigger à l'intérieur de ce dernier il suffit de lancer la fonction TRIGGER_NESTLEVEL() qui renvoie une variable de niveau.
Conseil : il est préférable de ne pas utiliser de triggers imbriqués et donc de laisser le paramètre nested triggers de la configuration à 1.
3-Bien entendu ou pourrait être beaucoup plus fin dans ce genre de contrôle et analyser puis remplacer, caractères par caractères.
A titre d’ exemple, nous allons réaliser un tel trigger :
CREATE TRIGGER E_CLI_INS
ON CLIENT
FOR UPDATE
AS
-- inutile si pas d'update de la colonne visée
IF NOT UPDATE(CLI_TEL)
RETURN
-- ouverture d'un curseur sur la table INSERTED
-- pour les téléphones renseignés
DECLARE CurIns CURSOR
FOR
SELECT CLI_ID, CLI_TEL
FROM INSERTED
WHERE CLI_TEL IS NOT NULL
IF @@error <> 0 GOTO LBL_ERROR
-- variable de travail
DECLARE @IdCli int, @TelAvant VARCHAR(20), @TelApres VARCHAR(20),
@car CHAR(1), @i int, @j int
-- ouverture du curseur
OPEN CurIns
IF @@error <> 0 GOTO LBL_ERROR
-- lecture première ligne
FETCH CurIns INTO @IdCli, @TelAvant
-- boucle de lecture
WHILE @@Fetch_Status = 0
BEGIN
-- si vide reboucle immédiatement
IF @TelAvant = ''
BEGIN
FETCH CurIns INTO @IdCli, @TelAvant
CONTINUE
END
-- scrutation de la valeur du téléphone
SET @i = 1
SET @j = 0
SET @TelApres = ''
-- boucle de nettoyage sur tous les caractères
WHILE @i <= LEN(@TelAvant)
BEGIN
-- reprise du caractère d'ordre i
SET @car = SUBSTRING(@TelAvant,@i,1)
-- on ne traite que les caractères de 0 à 9
IF @car = '0' or @car = '1' or @Car = '2' or @Car = '3'
or @car = '4' or @car = '5' or @Car = '6' or @Car = '7'
or @car = '8' or @car = '9'
BEGIN
SET @TelApres = @TelApres + @Car
SET @j = @j + 1
END
SET @i =@i + 1
END
-- si vide reboucle immédiatement
IF @TelApres = ''
BEGIN
FETCH CurIns INTO @IdCli, @TelAvant
CONTINUE
END
-- découpage par tranche de 2 nombres
SET @TelAvant = @TelApres
SET @i = 1
SET @TelApres = ''
-- boucle de découpage
WHILE @i <= LEN(@TelAvant)
BEGIN
SET @car = SUBSTRING(@TelAvant,@i,1)
SET @TelApres = @TelApres + @Car
IF @i % 2 = 0
SET @TelApres = @TelApres + '-'
SET @i =@i + 1
END
-- petit effet de bord si @TelApres se termine par un nombre pair,
-- alors tiret en trop !
IF @j % 2 = 0 -- au pasage % est la fonction MODULO dans SQL Server
SET @TelApres = SUBSTRING(@TelApres, 1, LEN(@TelApres)-1)
-- mise à jour si différence
IF @TelAvant <> @TelApres
UPDATE CLIENT
SET CLI_TEL = @TelApres
WHERE CLI_ID = @IdCli
IF @@error <> 0 GOTO LBL_ERROR
FETCH CurIns INTO @IdCli, @TelAvant
END
-- fermeture du curseur et désallocation de l'espace mémoire
CLOSE CurIns
DEALLOCATE CurIns
RETURN
-- rollback en cas d'erreur
LBL_ERROR:
ROLLBACK TRANSACTION
4 - il s'agit maintenant de supprimer en cascade dans différentes tables. Si un client (table T_CLIENT) est supprimé on doit lui retirer les factures (table T_FACTURE) qui le concerne :
CORRECTION
CREATE TRIGGER E_DEL_CLI ON T_CLIENT
FOR DELETE
AS
DELETE FROM T_FACTURE
FROM T_FACTURE F
INNER JOIN DELETED D
ON F.CLI_ID = D.CLI_ID
IF @@ERROR <> 0
ROLLBACK TRANSACTION
Bien entendu si vous avez placé de nouveau un trigger permettant de faire de la suppression dans les lignes de facture, alors il sera déclenché et supprimera les occurrences désirées. C'est ce que l'on appelle un déclenchement de triggers en cascade.
5- - la gestion d'un lien d'héritage suppose souvent une exclusion mutuelle entre les fils nous allons voir comment gérer ce cas de figure. Partons d'une table T_VEHICULE dont la spécialisation provoque deux tables : T_AVION et T_BATEAU. Un véhicule peut être un avion ou bien un bateau mais pas les deux. Une valeur de clef présente dans T_VEHICULE peut donc se retrouver soit dans T_BATEAU soit dans T_AVION mais on doit éviter qu'elle se retrouve dans les deux tables.
CORRECTION
CREATE TRIGGER E_AVI_INS ON T_AVION
FOR INSERT
AS
DECLARE @rowInUse int, @rows int
-- on regarde si les clefs existent bien dans la table T_VEHICULE
SELECT @RowInUse = COUNT(*)
FROM INSERTED
SELECT @Rows = COUNT(*)
FROM T_VEHICULE V
JOIN INSERTED I
ON V.VHC_ID = I.VHC_ID
IF @RowInUse <> @Rows
BEGIN
ROLLBACK
RAISERROR ('Identifiant de l''héritant inexistant',16,1)
RETURN
END
-- on regarde si les clefs n'existent pas dans la table T_BATEAU
SELECT @Rows = COUNT(*)
FROM T_BATEAU B
JOIN INSERTED I
ON B.VHC_ID = I.VHC_ID
IF @Rows <> 0
BEGIN
ROLLBACK
RAISERROR ('Fils pré existant dans l''entité soeur BATEAU',16,1)
END
Jeu de test :
CREATE TABLE T_VEHICULE
(VHC_ID INT)
CREATE TABLE T_AVION
(VHC_ID INT,
AVI_MARQUE VARCHAR(16),
AVI_MODELE VARCHAR(16))
CREATE TABLE T_BATEAU
(VHC_ID INT,
BTO_NOM VARCHAR(16),
BTO_PORT VARCHAR(16))
INSERT INTO T_VEHICULE VALUES (1)
INSERT INTO T_VEHICULE VALUES (2)
INSERT INTO T_VEHICULE VALUES (3)
INSERT INTO T_BATEAU VALUES (2, 'Penduick', 'Lorient')
INSERT INTO T_BATEAU VALUES (3, 'Titanic', 'Liverpool')
INSERT INTO T_AVION VALUES (1, 'Boeing', '747')
INSERT INTO T_AVION VALUES (3, 'Tupolev', '144')
INSERT INTO T_AVION VALUES (5, 'Airbus', 'A320')
Les deux dernières insertions doivent être rejetées : l'id 3 existant dans l'entité frère T_BATEAU et l'id 5 n'existant pas dans l'entité mère.
Mais cet exemple est incomplet car il faudrait créer ce même type de trigger dans la table T_BATEAU pour vérifier la présence de la clef dans la table père et vérifier son absence dans la table sœur. De même qu'il serait souhaitable de gérer une suppression en cascade pour le père et éventuellement une modification de la valeur de la clef en cascade ! Bref, à vous de jouer...
6- - voici maintenant une association d'un genre particulier. L'association 0:0 ! Comment gérer une telle relation ? Comme à mon habitude un exemple concret est plus compréhensible : nous voici avec un texte à indexer mot pour mot, et pour cela nous devons classer chaque mot rencontré dans le texte dans une table T_MOT (MOT_MOT, MOT_REF, MOT_PAGE, MOT_LIGNE, MOT_OFFSET) avec la référence du texte, la page, la ligne et l'offset en nombre de caractère. Mais il serait absurde d'indexer tous les mots. C'est pourquoi une table T_MOT_NOIR(MNR_MOT) de mot indésirables (les mots "noirs") est créée, et l'on souhaite qu'aucun des mots indexé pour le texte ne soit un mot noir, ni qu'aucun mot noir ne se trouve dans les mots indexé. C'est donc bien une relation d'exclusion totale, telle que l'intersection des colonnes MOT_MOT de T_MOT et MNR_MOT de T_MOT_NOIR produise un ensemble vide, ou plus simplement que :
NOT EXISTS(SELECT *
FROM T_MOT MOT
JOIN T_MOT_NOIR MNR
ON MOT.MOT_MOT = MNR.MNR_MOT)
Soit toujours évaluée à vrai !
Un tel trigger n'est pas difficile à écrire :
CORRECTION
CREATE TRIGGER E_INS_MOT ON T_MOT
FOR INSERT
AS
IF EXISTS(SELECT *
FROM INSERTED I
JOIN T_MOT_NOIR M
ON I.MOT_MOT = M.MNR_MOT)
BEGIN
ROLLBACK
RAISERROR ('Insertion d''un mot noir impossible',16,1)
RETURN
END
Il faudrait d'ailleurs penser à écrire son réciproque dans la table T_MOT_NOIR empêchant ainsi l'insertion d'un mot noir pré existant dans la table T_MOT.
On peut bien entendu tester un tel trigger avec le jeu d'essai suivant :
CREATE TABLE T_MOT
(MOT_MOT CHAR(32),
MOT_REF CHAR(
,
MOT_PAGE INT,
MOT_LIGNE INT,
MOT_OFFSET INT)
CREATE TABLE T_MOT_NOIR
(MNR_MOT CHAR(32))
INSERT INTO T_MOT_NOIR VALUES ('LE')
INSERT INTO T_MOT_NOIR VALUES ('LA')
INSERT INTO T_MOT_NOIR VALUES ('LES')
INSERT INTO T_MOT_NOIR VALUES ('UN')
INSERT INTO T_MOT_NOIR VALUES ('UNE')
INSERT INTO T_MOT_NOIR VALUES ('DES')
INSERT INTO T_MOT_NOIR VALUES ('DE')
INSERT INTO T_MOT VALUES('LA', 'BIBLE', 147, 23, 14)
INSERT INTO T_MOT VALUES('VALLÉE', 'BIBLE', 147, 23, 14)
INSERT INTO T_MOT VALUES('DE', 'BIBLE', 147, 23, 14)
INSERT INTO T_MOT VALUES('LA', 'BIBLE', 147, 23, 14)
INSERT INTO T_MOT VALUES('MORT', 'BIBLE', 147, 23, 14)
En conclusion nous pouvons dire que les triggers de la version 7 de SQL Server sont assez limités en ne permettent pas de gérer très finement les données. Ils ne fournissent pas un mécanisme pratique et simple lorsque l'on veut par exemple manipuler ligne à ligne et colonne par colonne la vailidité des données et les rectifier à la volée avant l'insertion définitive. Il semble que la version 2000 de SQL Server respecte plus la norme SQL 2 sur ce point.
7-- dans une relation d'héritage, comment insérer dans une table fille alors que l'insertion dans la table mère est un pré requis ?
Par exemple, nous avons une table des personnes, une table des clients et une table des employés. Ces tables sont construites de la sorte :
CREATE TABLE T_PERSONNE_PRS(PRS_ID INT IDENTITY NOT NULL PRIMARY KEY, PRS_NOM CHAR(32) NOT NULL, PRS_PRENOM VARCHAR(16))
CREATE TABLE T_EMPLOYE_EMP(PRS_ID INT NOT NULL PRIMARY KEY REFERENCES T_PERSONNE_PRS (PRS_ID), EMP_MATRICULE VARCHAR(
)
On ne peut donc pas insérer directement dans T_EMPLOYE_EMP, sauf à utiliser une vue et un trigger INSTEAD OF...
CORRECTION
Creation de la vue V_EMPLOYEE_EMP :
CREATE VIEW V_EMPLOYEE_EMP
AS
SELECT P.PRS_ID, P.PRS_NOM, P.PRS_PRENOM, E.EMP_MATRICULE
FROM T_PERSONNE_PRS P
INNER JOIN T_EMPLOYE_EMP E
ON P.PRS_ID = E.PRS_ID
Dès lors on peut créer un trigger d'insertion dans cette vue qui va décomposer les éléments à insérer et injecter les données dans les deux tables :
CREATE TRIGGER TRG_INS_EMPLOYE
ON V_EMPLOYEE_EMP
INSTEAD OF INSERT
AS
BEGIN
INSERT INTO T_PERSONNE_PRS (PRS_NOM, PRS_PRENOM)
SELECT PRS_NOM, PRS_PRENOM
FROM INSERTED
INSERT INTO T_EMPLOYE_EMP (PRS_ID, EMP_MATRICULE)
SELECT @@IDENTITY, EMP_MATRICULE
FROM INSERTED
END
Utilisation :
INSERT INTO V_EMPLOYEE_EMP VALUES (1, 'DUPONT', 'Maurice', 'XF5090AZ')
SELECT * FROM T_PERSONNE_PRS
PRS_ID PRS_NOM PRS_PRENOM ----------- -------------------------------- ----------------1 DUPONT Maurice
SELECT * FROM T_EMPLOYE_EMP
PRS_ID EMP_MATRICULE----------- -------------1 XF5090AZ
NOTA : voici le trigger de contrôle d'intégrité des bornes des arborescence exprimées sous forme intervallaire
CREATE TRIGGER E_DEV_UNIQUE_BORNE ON T_DEVELOPPEMENT_DEV
FOR INSERT, UPDATE, DELETE
AS
-- vérification de l'unicité de l'ensemble des bornes (bornes gauches et bornes droite)
IF EXISTS (SELECT COUNT(*), BORNE
FROM (SELECT DEV_BORNE_DROITE AS BORNE
FROM T_DEVELOPPEMENT_DEV
UNION ALL
SELECT DEV_BORNE_GAUCHE AS BORNE
FROM T_DEVELOPPEMENT_DEV) T
GROUP BY BORNE
HAVING COUNT(*) <> 1)
ROLLBACK
-- vérification de la borne maximale comme étant deux fois le nombre de lignes de la table
IF (SELECT MAX(BORNE)
FROM (SELECT DEV_BORNE_DROITE AS BORNE
FROM T_DEVELOPPEMENT_DEV
UNION ALL
SELECT DEV_BORNE_GAUCHE AS BORNE
FROM T_DEVELOPPEMENT_DEV) T) <> (SELECT COUNT(*) * 2
FROM T_DEVELOPPEMENT_DEV)
BEGIN
ROLLBACK
RAISERROR ('Une borne dépasse la valeur maximale attendue', 16, 1)
END
-- vérification de la borne minimale comme étant égale à un
IF (SELECT MIN(BORNE)
FROM (SELECT DEV_BORNE_DROITE AS BORNE
FROM T_DEVELOPPEMENT_DEV
UNION ALL
SELECT DEV_BORNE_GAUCHE AS BORNE
FROM T_DEVELOPPEMENT_DEV) T) <> 1
BEGIN
ROLLBACK
RAISERROR ('Une borne dépasse la valeur minimale attendue', 16, 1)
END
Dim 18 Jan - 13:35 par MariellePar
» Comment sauvegarder et partager nos fichiers?
Jeu 28 Aoû - 12:13 par MariellePar
» Mise en place d'un proxy
Sam 6 Juil - 9:24 par samfp
» Kerberos Apache, SSH ou Squid impossible - Serveurs Linux
Lun 16 Juil - 13:52 par isador999
» mot de passe !!!
Sam 23 Juin - 0:37 par mr-class
» Comparer (points forts et points faibles) les plateformes
Mar 19 Juin - 11:08 par nana85
» HSRP et VRRP
Ven 25 Mai - 15:53 par azertyuiop
» J'ai besoin de votre aide svp
Ven 25 Mai - 15:46 par azertyuiop
» client xp
Mer 25 Avr - 10:58 par wissalnouran