coincoins

SQL.PL - Triggers et procédures



Ques aquo ?

Introduction
Comment créer des modifications en cascade en réponse à un événement (update, insert, delete) : procédures stockées

Dans SQL, il est possible de créer des traitements qui sont lancés selon des événements donnés ou des traitements indépendants, lancés à la demande.

Ces traitements permettent par exemple de :

  • exécuter un contrôle de cohérence de la base lors de la modification/insertion/suppression d'enregistrements,
  • faire un calcul de montant global dans une table de synthèse de données,
  • lancer automatiquement la sauvegarde de plusieurs tables,
  • etc.

Ces traitements sont faits avec :

  • Triggers (déclencheur), traitement événementiels, liés à une et une seule table par trigger
  • Procédures, traitement lancés à la demande, indépendantes des tables.
  • etc.

Un trigger est un objet associé à une table et est déclenché lorsqu'un événement intrevient sur celle-ci. Un trigger contient des ordres sql avancés qui vont alors être éxécutés.
Exemple d'usage : Recalculer le montant du CA d'un client lors de l'enregistrement d'une facture ou placer un indicateur de promotion si le montant de ce CA dépasse une certaine valeur, etc. ...

Une procédure est un objet qui contient des instructions SQL et est lancé à la demande. Elle est indépendante d'une table en particulier

Avec cela, on entre dans le monde du PL/SQL, process language/SQL.

 

Avantages/inconvénients.

Pour les triggers, il n'y a pas photo, c'est plutôt bien mais ...

  • Avantages :
    • Permet d'efferctuer des contrôles de cohérence (inclusion/exclusion) et des claculs dépendant de transactions
    • De n'autoriser certains trigger qu'à certains utilisateurs : gain de sécurité
  • Inconvénients :
    • On ne peut pas mettre plusieurs triggers sur la même transaction (/ex : before update)
    • Il n'est donc possible de mettre que six triggers par table : (2*3, before/after update/delete/insert into)
    • Les triggers compliqués sont à bannir à cause d'un maintenance nécessaire à chaque modification de structure.

 

Pour les procédures, idem, il y a du pour et du contre.
D'après le site dynamic-mess.com, on a :

  • Avantages :
    • Réduit les allers-retours entre le client et le serveur : donc gain de performance
    • De n'autoriser certaines procédures qu'à certains utilisateurs : améliore donc la sécurité
    • De s'assurer que certaines requêtes sensibles sont exécutées de la même manière quelque soit le language utilisé pour le développement du client
  • Inconvénients :
    • Ajoute de la charge au serveur SQL
    • Certains traitements sont plus courts et plus simples si développés avec un language adapté
    • La déclaration d'une procédure varie d'un SGBD à l'autre.

C'est pourquoi, les triggers et procédures sont parfois remplacés par des traitements effectués par l'application, malgré la rapidité de ces premiers et le gain de sécurité notable.

Trigger
Comment créer des modifications en cascade en réponse à un événement (update, insert, delete) : procédure stockée

Trigger (déclencheur) : traitement effectué sur une table lors de la mise à jour des données. Le traitement est effectué avant ou après une modification des données, il permet de faire des traitements de contrôle, des calculs avancés entre des tables, etc.

Le trigger est totalement lié à une table (et une seule).

Exemple 1 : calcul de synthèse

Mise a jour du montant des commande lors de l'ajout ou la modification d'une de leur ligne :

CREATE TRIGGER maj_montant_commande AFTER insert, update ON gc_contenir
FOR EACH ROW
BEGIN
    UPDATE gc_commande
		join gc_article ON gc_article.refArt = :NEW.refArt
        set totalHT = totalHT + :NEW.qte*prixHTArt
        where gc_commande.id= :NEW.cde_id
END ;

Remarquer :NEW qui symbolise un enregistrement temporaire contenant les valeurs à tester

Pour la forme inhabituelle de UPDATE, se référer au LMD de fou dans le SQL avancé

 

Exemple 2 : vérification d'exclusion

Vérifier une contrainte d'exclusion avant insertion : un article produit ne peut pas être acheté

CREATE TRIGGER gc_Exclusion_acheter_approvisionner BEFORE INSERT ON gc_acheter
FOR EACH ROW
    DECLARE nb_articles NUMBER;
BEGIN
    Select count(*) INTO nb_articles from gc_produire
    Where no_article = :NEW.no_article
 
    IF nb_articles > 0 THEN -- afficher un message d'erreur
        Raise_application_error (-20002, 'Article N° : ', :NEW.no_article, ' est un article produit.');
    END IF ;
 
END  ;
ENABLE TRIGGER Exclusion_acheter_approvisionner ON gc_acheter ; -- Activer le trigger

Avant insertion dans la table gc_acheter, on compte le nombre d'articles produits avec le numéro de l'article à acheter. Si il y en a (>0) alors, on déclenche une erreur.

into permet de renommer le champ de sortie et de mettre sa valeur dans le champ déclaré.

 

Exemple 3 : vérification d'inclusion

Vérifier une contrainte d'inclusion avant insertion : une personne doit avoir un abonnement valide pour pouvoir faire un emprunt

Create TRIGGER Inclusion_ effectuer_souscrire before insert on bib_pret
for each row
	WHEN new.no_personne IS NOT NULL -- test préliminaire
	DECLARE nb_abonnements number;
BEGIN
	select count(*) into nb_abonnement from bib_personne
	where no_personne = :new.no_ personne
	  and dateDebut <= curdate()
	  and dateFin >= curdate()
 
	if nb_abonnement = 0 
	then
	   Raise_application_error (-20006,"Aucun abonnement souscrit");
	end if;
END ;

 

Voir aussi ce lien vers MySQL::Using Triggers

Top of page

Procédure

Si le trigger est totalement lié à une (et une seule) table, la procédure est totalement indépendante.

PROCEDURE : traitement composé d'un ensemble d'instructions effectuées sur une base de donnée.

Une procédure utilise des techniques comme un curseur et des lectures successives.

Voir des exemples plus loin.

Syntaxe de base selon IBM :

-- création ou modification : 
DELIMITER |
CREATE [OR REPLACE] PROCEDURE nom_procédure
  ([
     [IN | OUT | IN OUT] nom_paramètre {type_paramètre | ARRAY OF type_paramètre}, ...
  ])
  [DECLARE déclaration_variable;...[;]]
BEGIN
 instruction_corps_procédure;...[;]
END;
DELIMITER ;
 
-- execution : 
EXECUTE PROCEDURE nom_procédure(liste_des_valeurs_des_parametres);

 

Structure syntaxique

CREATE [OR REPLACE ] PROCEDURE procedure_name (parameter_list)     
BEGIN
      -- instructions de déclaration --
      -- instructions d'exécution --
EXCEPTION
      -- [traitement des exceptions --
END;

 

Explications :

DELIMITER : Cette clause redéfini le délimiteur de fin de requête pour que le point virgule (;), utilisé par défaut et utilisé pour la fin de ligne dans la procédure, ne risque pas terminer l'écriture de la procédure avant la fin de la rédaction de celle-ci.

IN, OUT ou {parameter_list} : déclarer les paramètres d'entrée/sortie de la procédure.

le reste de la structure ressemble au trigger (mais on n'y fait pas la même chose).

Exemple 1, simple

on est dans mysql en ligne de commande !

One Code

mysql> delimiter // --changer le délimiteur
 
mysql> CREATE PROCEDURE nbVilles (IN pays CHAR(3), OUT villes INT)
       BEGIN
         SELECT COUNT(*) INTO villes FROM gc_client
         WHERE paysCli = pays;
       END//
Query OK, 0 rows affected (0.01 sec)
 
mysql> delimiter ;  -remettre le délimiteur standard 

One Call

mysql> CALL nbVilles('FRA', @villes); -- appel de la procédure
Query OK, 1 row affected (0.00 sec)

@villes est la manière de déclarer une variable utilisateur. Les variables déclarées en interne à une procédure sont innaccessibles de l'extérieur de celle-ci. on appelle ça la porté des variables (cf algo).

One Result

mysql> SELECT @villes;
+---------+
| @villes |
+---------+
|       3 |
+---------+
1 row in set (0.00 sec)

Comme d'hab, SELECT sert à afficher quelque chose : une constante, une variable, une requête, etc.

Exemple 2, plus complet

Les étapes sont les suivantes :

  1. Déclaration du curseur : declare nom_du_curseur cursor for requête_sql ;
  2. Ouverture du curseur : open nom_du_curseur ;
  3. Lorsque le curseur est ouvert, les lignes peuvent être extraites du curseur une par une ou dans un bloc pour effectuer la manipulation des données(FETCH).
    • Utiliser loop/end loop ou while/end while pour parcourir les lignes
  4. Fermer le curseur pour libérer la mémoire allouée : closepuis deallocate.

Traitement stocké à lancer à intervalle régulier : mise à jour mensuelle d'une classe de rotation (A, B ou C) des articles

Les articles sont classés en fonction de la vitesse de rotation (renouvellement) des stocks au moyen de l'analyse dite ABC (voir la méthode sur le net).

Create PROCEDURE miseAjourMensuelle()
BEGIN
    DECLARE mois INT;
    DECLARE reference INT;
    DECLARE total INT;
    DECLARE classe varchar(1);
 
    DECLARE cur_mAJour CURSOR FOR   -- déclaration du curseur
        Select L.art_id, month(C.cdeDate), sum(L.qte)
        from gc_contenir L 
            join gc_commande C on L.numCde = C.numCde
        group by L.art_id, month(C.cdeDate) ;
 
    OPEN cur_mAJour;                -- ouverture du curseur
 
    getDonnees: LOOP                -- définir la boucle
        -- récupérer les données ; mappage des champs de la requête 
        FETCH cur_mAJour INTO ref_Art, mois, total;
 
        IF total > 100 THEN     SET classe = "A";  -- voir if ci-dessous
         ELSEIF total > 10 THEN SET classe = "B";
         ELSE                   SET classe = "C";
        END IF;
 
        update gc_classementArticle A -- màj de la table classement mensuel
        set A.classement = classe
        where A.mois=mois and A.refArt=ref_Art;
    END LOOP;
    CLOSE cur_mAJour;
 END;

Glps !

 

  • DECLARE <nom> <type> Déclare une variable interne et son type.
  • DECLARE <nom> CURSOR FOR <requête> Déclare une variable de type curseur sur une requête.
    Elle attend la lecture de chaque ligne avec FETCH plus bas. en fait, c'est une
  • OPEN Ouvre le curseur (exécute la requête mais sans renvoyer les lignes)
  • LOOP On commence la boucle. En fin de boucle, on revient ici jusqu'a ce qu'on n'ait plus de ligne à lire avec FETCH.
    • FETCH Permet de lire la ligne suivante en commençant par la première.
      Ici, on lit le curseur déclaré plus haut et on assigne les valeurs des colonnes récupérées aux variable données après INTO.
    • IF <test> THEN <instr> ELSIF <test> THEN <instr> ELSE <instr> END IF ;
      Cette syntaxe est assez claire et connue depuis le PHP, Powershell, etc. C'est la même ici.
    • La suite est classique : une requête de mise à jour
  • Close Fermer le curseur

Top of page

Détail des boucles pour les procédures

WHILE et LOOP permettent de faire une boucle.

  • While est conditionné par une condition de continuation (voir cours algo comme powershell, par exemple)
  • Loop parcours une liste d'enregistrements chargés par un curseur ou un select

Exemples

while

CREATE PROCEDURE boucleBidon()
BEGIN
  DECLARE cnt INT DEFAULT 5; -- initialiser un compteur
 
  WHILE cnt > 0 DO  -- tant que cnt est > à 0
    select curtime(); -- afficher l'heure
    SET cnt = cnt - 1;  -- dévrémenter le compteur cnt
  END WHILE;
END;

loop

CREATE PROCEDURE iterationBibon(cnt INT)
BEGIN
  label1: LOOP        -- déclarer la boucle
 
    SET cnt = cnt + 1; -- incrémenter le compteur
    IF cnt >= 10 THEN   -- si le compteur est 10 ou plus alors
      LEAVE label1;      -- sortir du loop
    END IF;
 
    ITERATE label1;     -- boucler
  END LOOP label1;    
  SET @x = cnt;
END;

Attention, cette boucle est faite pour montrer ITERATE et LEAVE.
@x est la manière de remplir une variable globale (extérieure à la procédure). Les autres variables (cnt) sont innaccessibles de l'extérieur de la procédure.

Exemple en MS SQL Server

Cette procédure parcourt les enregistrements d'un curseur.
Noter la forme du test avec l'usage de la variable système @@fetch_status qui récupère l'état de la dernière lecture (0=ok, <0=erreur ou fin)

-- ATTENTION : langage = MS SQL Server (à cause des @)
DECLARE @pNom varchar(100);                             -- déclaration de variables
DECLARE cursor_product CURSOR  FOR select ... ; -- déclaration du curseur
OPEN myCursor;                                                     -- ouverture du curseur
FETCH next from myCursor into @pNom ;             -- lire et mapper le 1er enreg
 
WHILE @@FETCH_STATUS = 0  -- TQ le curseur est ok (il y a 2 @ !)
    BEGIN
        PRINT @product_name          -- traitement
        FETCH next from myCursor ... -- lire et mapper l'enreg suivant
    END;                                       -- fin TQ
 
CLOSE myCursor ;                     -- fermer le curseur
DEALLOCATE myCursor ;                -- libérer, délivrer la mémoire

Même exemple pour Oracle

Voir aussi plus haut

Rappel : CURSOR : curseur de base de données. Permet de parcourir une table et d'effectuer des traitments sur les enregistrements. Utilisé dans les triggers et procédures.
Le loop s'arrête automatiquement quand FETCH arrive au bout de la requête

-- syntaxe Oracle ou mysql
DECLARE      -- déclaration des variables
    prodNom  Varchar(100), 
    prodPrix Decimal
DECLARE myCursor CURSOR    -- déclaration du curseur pour une requête classique qui sera parcourue
    FOR select pNom, pPrix from production.products; -- requête dans la DB production
 
BEGIN
      OPEN myCursor;      -- ouverture du curseur
 
      myLoop LOOP;        -- boucle sur le curseur
          FETCH NEXT FROM myCursor INTO prodNom, prodPrix  ;  -- lecture et mapping des champs
          PRINT prodNom + CAST(prodPrix AS varchar); -- affichage (PRINT à voir, n'existe pas en mysql)
      END LOOP;
 
      CLOSE myCursor ;    -- fermer le curseur
END;
DEALLOCATE myCursor ;

Exemple avec FOR

Exemple avec FOR sans devoir ouvrir manuellement le curseur : le curseur est lu et fermé automatiquement mais il n'y a pas de mappage des variables

-- syntaxe Oracle
DECLARE myCursor CURSOR  -- déclaration du curseur pour une requête classique qui sera parcourue
    FOR select pNom, pPrix FROM   production.products;
 
BEGIN
      FOR currentRecord IN myCursor LOOP;   -- boucle sur le curseur
          PRINT currentRecord.pNom + CAST(currentRecord.pPrix AS varchar); -- PRINT ??? (à voir)
      END LOOP;
END;

C'est plus court. C'est mieux ?

Top of page

Conclusion

Ouh là, on cherche à savoir comment pirater une base de données, ou y mettre son propre accès réservé ?

Pas de panique, les admins ne vous pas attendus pour sécuriser leur base ...

Top of page