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

Construire dynamiquement du code MySQL pour créer un déclencheur

N'ayant reçu aucune solution définitive pour cette question, j'ai procédé à la mise au point d'une option de preuve de concept (puisque MySQL nativement ne vous permettrait pas d'exécuter du code SQL qui crée un déclencheur, à l'aide d'instructions préparées). N'hésitez pas à faire des commentaires positifs.

DELIMITER //
DROP PROCEDURE IF EXISTS createAuditTable//
CREATE PROCEDURE createAuditTable(tblname CHAR(30), sufftxt CHAR(10), pri CHAR(20), filename CHAR(255) )
BEGIN
    SELECT DATABASE() INTO @dbname;
    SET @srctbl = CONCAT(@dbname, ".", tblname);
    SET @destdb = CONCAT(@dbname, "_", sufftxt);
    SET @desttbl = CONCAT(@destdb, ".", tblname);

    SET @str1 = CONCAT( "CREATE DATABASE IF NOT EXISTS ", @destdb);
    PREPARE stmt1 FROM @str1;
    EXECUTE stmt1;
    DEALLOCATE PREPARE stmt1;

    SET @str2 = "SET FOREIGN_KEY_CHECKS=0";
    PREPARE stmt2 FROM @str2;
    EXECUTE stmt2;
    DEALLOCATE PREPARE stmt2;

    SELECT COUNT(*) FROM information_schema.tables WHERE table_name = tblname AND table_schema = @destdb INTO @tblcount;
    IF (@tblcount = 0) THEN 
        SET @str3 = CONCAT("CREATE TABLE ", @desttbl, " LIKE ", @srctbl);
        PREPARE stmt3 FROM @str3;
        EXECUTE stmt3;
        DEALLOCATE PREPARE stmt3;
    END IF;

    SELECT COUNT(*) FROM information_schema.columns WHERE table_name = tblname AND table_schema = @destdb AND column_key = 'PRI' INTO @keycount;

    IF (@keycount <> 0) THEN 
        SET @str4 = CONCAT("ALTER TABLE ", @desttbl, " DROP PRIMARY KEY, ADD INDEX ", pri, " (", pri, ")" );
        PREPARE stmt4 FROM @str4;
        EXECUTE stmt4;
        DEALLOCATE PREPARE stmt4;
    END IF;

SELECT CONCAT( "DELIMITER $$
DROP TRIGGER IF EXISTS ", tblname, "_history_BU$$
CREATE TRIGGER ", tblname, "_history_BU
BEFORE UPDATE ON ", tblname, "
FOR EACH ROW
BEGIN
    INSERT INTO ", @desttbl, " (",
(SELECT GROUP_CONCAT(column_name) FROM information_schema.columns WHERE table_schema = @dbname AND table_name = tblname), ") ",
    "
    VALUES(", 
(SELECT GROUP_CONCAT('OLD.', column_name) FROM information_schema.columns WHERE table_schema = @dbname AND table_name = tblname),
 ");
END$$
DELIMITER ;"
 ) AS qstr FROM DUAL INTO @triggertxt;

SET @savestr = CONCAT('SELECT ', '"', @triggertxt, '"', " INTO DUMPFILE ", '"', filename, '"');
PREPARE stmt5 FROM @savestr;
EXECUTE stmt5;
DEALLOCATE PREPARE stmt5;


END//
DELIMITER ;  

POUR UTILISER, appelez la Procédure :

CALL createAuditTable('name_of_table', 'history', 'pri_key_fld', 'path/to/file.sql');

Une nouvelle base de données est créée en utilisant le nom de votre base de données de travail actuelle, avec un suffixe "_history" ajouté. La table "nom_de_table" est créée dans cette nouvelle BD, identique à la table d'origine. Le champ "pri_key_fld" (qui doit être la clé primaire/unique de la table "nom_de_table") est converti en une clé "INDEX" ordinaire. Le but est d'éviter des violations uniques lors de la journalisation d'audit de plusieurs lignes à l'avenir.

ALORS Lancez le fichier créé par la procédure :SOURCE 'path/to/file.sql'; (ou toute syntaxe alternative pour exécuter SQL à partir de ce fichier)

Quelques mises en garde :pour le moment, vous ne pouvez fournir qu'un seul champ pour "pri_key_fld". Idéalement, nous voudrions fournir un "tableau" contenant tous les champs uniques de cette table. Actuellement, si vous avez plusieurs champs uniques, les violations uniques vous empêcheront d'enregistrer plus d'une ligne. Et ce n'est pas gentil !

Encore une fois, il est évidemment très maladroit et non performant de passer par le processus de création d'un fichier sur disque, uniquement pour lire SQL à partir du même fichier dans la commande suivante. Une alternative que l'on peut explorer pour améliorer est la suivante :exécutez le CALL createAuditTable partie de la ligne de commande, récupérez la sortie sous forme de texte, puis exécutez la même chose que SQL directement sur la ligne de commande. J'ai essayé cela sur Windows PowerShell; mais la sortie était truffée de chaînes littérales "\r\n" (représentant des sauts de ligne). Je n'ai pas eu le temps de nettoyer immédiatement cette ficelle, elle est donc au réfrigérateur maintenant !

Enfin, ô ninjas de MySQL, soyez gentils. Je ne suis pas un pro, vraiment. Il s'agit simplement d'une tentative de faire pousser votre propre épicerie pour résoudre un problème pratique.

Merci.