sql >> Databasteknik >  >> RDS >> Mysql

Dynamiskt konstruera MySQL-kod för att skapa en trigger

Efter att inte ha fått någon definitiv lösning på den här frågan har jag gått vidare med att plocka fram ett proof of concept-alternativ (eftersom MySQL naturligt inte skulle låta dig köra SQL-kod som skapar en trigger, med hjälp av Prepared Statements). Kom gärna med positiv input.

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 ;  

FÖR ATT ANVÄNDA, ring Proceduren:

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

En ny databas skapas med namnet på din nuvarande fungerande DB, med suffixet "_history" tillagt. Tabellen "name_of_table" skapas i denna nya DB, identisk med den ursprungliga tabellen. Fältet "pri_key_fld" (som ska vara den primära/unika nyckeln i tabellen "name_of_table") konverteras till en vanlig "INDEX"-nyckel. Syftet med detta är att förhindra unika överträdelser under granskningsloggning av flera rader i framtiden.

Kör DÅ filen som skapats med proceduren:SOURCE 'path/to/file.sql'; (eller någon alternativ syntax för att köra SQL från den filen)

Ett par varningar:Just nu kan du bara ange ett fält för "pri_key_fld". Helst skulle vi vilja tillhandahålla en "array" som innehåller alla unika fält i den tabellen. För närvarande, om du har mer än ett unikt fält, kommer unika överträdelser att hindra dig från att logga mer än en rad. Och det är inte trevligt!

Återigen, det är uppenbarligen väldigt klumpigt och icke-presterande att gå igenom processen att skapa en fil på disk, bara för att läsa SQL från samma fil i nästa kommando. Ett alternativ man kan utforska för att förbättra är detta:Kör CALL createAuditTable del från kommandoraden, fånga utdata som text och kör sedan samma som SQL där på kommandoraden. Jag försökte det på Windows PowerShell; men utgången var full av bokstavliga "\r\n"-strängar (som representerar radbrytningar). Jag hade inte tid att omedelbart arbeta med att rensa ut det här snöret, så det ligger i kylen nu!

Till sist, O ni MySQL-ninjor, var snälla. Jag är inget proffs, verkligen. Det här är bara ett försök att odla din egen matbutik för att lösa ett praktiskt problem.

Tack.



  1. mysql_real_escape_string() tar bort strängen helt

  2. Att utföra en sammanfogning över flera heterogena databaser, t.ex. PostgreSQL och MySQL

  3. MySQL- GROUP och COUNT efter datum

  4. Hur genererar jag en unik, slumpmässig sträng för en av mina MySql-tabellkolumner?