web-dev-qa-db-fra.com

MySQL Trigger après la mise à jour uniquement si la ligne a été modifiée

Existe-t-il une possibilité d'utiliser un déclencheur "après mise à jour" uniquement dans le cas où les données ont été VRAIMENT modifiées. Je connais "NEW and OLD". Mais lorsque je les utilise, je ne peux comparer que des colonnes. Par exemple "NEW.count <> OLD.count".

Mais je veux quelque chose comme: run trigger if "NEW <> OLD"

Un exemple:

create table foo (a INT, b INT);
create table bar (a INT, b INT);

INSERT INTO foo VALUES(1,1);
INSERT INTO foo VALUES(2,2);
INSERT INTO foo VALUES(3,3);

CREATE TRIGGER ins_sum
    AFTER UPDATE ON foo
    FOR EACH ROW
    INSERT INTO bar VALUES(NEW.a, NEW.b);

UPDATE foo SET b = 3 WHERE a=3;
Query OK, 0 rows affected (0.00 sec)
Rows matched: 1  Changed: 0  Warnings: 0


select * from bar;
+------+------+
| a    | b    |
+------+------+
|    3 |    3 |
+------+------+

Le fait est qu'il y a eu une mise à jour, mais rien n'a changé. Mais la gâchette fonctionnait quand même. IMHO il devrait y avoir un moyen que ce ne soit pas.

Je sais que j'aurais pu utiliser

IF NOW.b <> OLD.b

pour cet exemple.

MAIS imaginez une grande table avec des colonnes changeantes. Vous devez comparer chaque colonne et si la base de données change, vous devez ajuster le déclencheur. ET il n'est pas "agréable" de comparer chaque colonne de la ligne codée en dur :)

Ajout

Comme vous pouvez le voir sur la ligne

Lignes correspondantes: 1 Changement: 0 Avertissements: 0

MySQL sait que la ligne n'a pas changé. Mais il ne partage pas cette connaissance avec la gâchette. Un déclencheur du type "AFTER REAL UPDATE" ou quelque chose comme cela serait cool.

62
jens

En guise de solution de contournement, vous pouvez utiliser l'horodatage (ancien et nouveau) pour vérifier si celui-ci est pas mis à jour lorsqu'il n'y a aucune modification dans la ligne. (Peut-être que c'est la source de confusion? Parce que celui-ci est aussi appelé 'on update' mais n'est pas exécuté si aucun changement ne survient) Les changements en une seconde n'exécuteront alors pas cette partie du déclencheur, mais dans certains cas cela pourrait être correct (comme quand vous avez une application qui rejette quand même les changements rapides.)

Par exemple, plutôt que

IF NEW.a <> OLD.a or NEW.b <> OLD.b /* etc, all the way to NEW.z <> OLD.z */ 
THEN  
  INSERT INTO bar (a, b) VALUES(NEW.a, NEW.b) ;
END IF

vous pourriez utiliser

IF NEW.ts <> OLD.ts 
THEN  
  INSERT INTO bar (a, b) VALUES(NEW.a, NEW.b) ;
END IF

Ensuite, vous ne devez pas modifier votre déclencheur à chaque fois que vous mettez à jour le schéma (le problème que vous avez mentionné dans la question.)

EDIT: Exemple complet ajouté

create table foo (a INT, b INT, ts TIMESTAMP);
create table bar (a INT, b INT);

INSERT INTO foo (a,b) VALUES(1,1);
INSERT INTO foo (a,b) VALUES(2,2);
INSERT INTO foo (a,b) VALUES(3,3);

DELIMITER ///

CREATE TRIGGER ins_sum AFTER UPDATE ON foo
    FOR EACH ROW
    BEGIN
        IF NEW.ts <> OLD.ts THEN  
            INSERT INTO bar (a, b) VALUES(NEW.a, NEW.b);
        END IF;
    END;
///

DELIMITER ;

select * from foo;
+------+------+---------------------+
| a    | b    | ts                  |
+------+------+---------------------+
|    1 |    1 | 2011-06-14 09:29:46 |
|    2 |    2 | 2011-06-14 09:29:46 |
|    3 |    3 | 2011-06-14 09:29:46 |
+------+------+---------------------+
3 rows in set (0.00 sec)

-- UPDATE without change
UPDATE foo SET b = 3 WHERE a = 3;
Query OK, 0 rows affected (0.00 sec)
Rows matched: 1  Changed: 0  Warnings: 0

-- the timestamo didnt change
select * from foo WHERE a = 3;
+------+------+---------------------+
| a    | b    | ts                  |
+------+------+---------------------+
|    3 |    3 | 2011-06-14 09:29:46 |
+------+------+---------------------+
1 rows in set (0.00 sec)

-- the trigger didn't run
select * from bar;
Empty set (0.00 sec)

-- UPDATE with change
UPDATE foo SET b = 4 WHERE a=3;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

-- the timestamp changed
select * from foo;
+------+------+---------------------+
| a    | b    | ts                  |
+------+------+---------------------+
|    1 |    1 | 2011-06-14 09:29:46 |
|    2 |    2 | 2011-06-14 09:29:46 |
|    3 |    4 | 2011-06-14 09:34:59 |
+------+------+---------------------+
3 rows in set (0.00 sec)

-- and the trigger ran
select * from bar;
+------+------+---------------------+
| a    | b    | ts                  |
+------+------+---------------------+
|    3 |    4 | 2011-06-14 09:34:59 |
+------+------+---------------------+
1 row in set (0.00 sec)

Cela fonctionne à cause du comportement de mysql sur la gestion des horodatages. L'horodatage n'est mis à jour que si une modification est survenue dans les mises à jour.

La documentation est ici:
https://dev.mysql.com/doc/refman/5.7/en/timestamp-initialization.html

desc foo;
+-------+-----------+------+-----+-------------------+-----------------------------+
| Field | Type      | Null | Key | Default           | Extra                       |
+-------+-----------+------+-----+-------------------+-----------------------------+
| a     | int(11)   | YES  |     | NULL              |                             |
| b     | int(11)   | YES  |     | NULL              |                             |
| ts    | timestamp | NO   |     | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
+-------+-----------+------+-----+-------------------+-----------------------------+
70
Inca

MAIS imaginez une grande table avec des colonnes changeantes. Vous devez comparer chaque colonne et si la base de données change, vous devez ajuster le déclencheur. ET il n'est pas "agréable" de comparer chaque ligne codée en dur :)

Oui, mais c'est la façon de procéder.

En passant, il est également judicieux de vérifier avant la mise à jour:

UPDATE foo SET b = 3 WHERE a=3 and b <> 3;

Dans votre exemple, cela le ferait mettre à jour (et donc écraser) deux lignes au lieu de trois.

15

Attention, si votre colonne prend en charge les valeurs NULL, OLD.x <> NEW.x ne suffit pas, car

SELECT IF(1<>NULL,1,0)

renvoie 0 comme identique à

NULL<>NULL 1<>NULL 0<>NULL 'AAA'<>NULL

Donc, il ne suivra pas les changements FROM et TO NULL

La manière correcte dans ce scénario est

((OLD.x IS NULL AND NEW.x IS NOT NULL) OR (OLD.x IS NOT NULL AND NEW.x IS NULL) OR (OLD.x<>NEW.x))
12
Wax Cage

Vous pouvez le faire en comparant chaque champ à l'aide de l'opérateur opérateur égal à NULL-safe <=> et ensuite nier le résultat avec NOT .

Le déclencheur complet deviendrait:

DROP TRIGGER IF EXISTS `my_trigger_name`;

DELIMITER $$

CREATE TRIGGER `my_trigger_name` AFTER UPDATE ON `my_table_name` FOR EACH ROW 
    BEGIN
        /*Add any fields you want to compare here*/
        IF !(OLD.a <=> NEW.a AND OLD.b <=> NEW.b) THEN
            INSERT INTO `my_other_table` (
                `a`,
                 `b`
            ) VALUES (
                NEW.`a`,
                NEW.`b`
            );
        END IF;
    END;$$

DELIMITER ;

(Basé sur un réponse différente de la mienne .)

9
user2428118

Ici, s'il y a une ligne affectant la nouvelle insertion, elle sera mise à jour sur une autre table de la base de données.

DELIMITER $$

CREATE TRIGGER "give trigger name" AFTER INSERT ON "table name" 
FOR EACH ROW
BEGIN
    INSERT INTO "give table name you want to add the new insertion on previously given table" (id,name,age) VALUES (10,"sumith",24);
END;
$$
DELIMITER ;
2
sumith madhushan

Utilisez la requête suivante pour voir quelles lignes ont été modifiées:

(select * from inserted) except (select * from deleted)

Les résultats de cette requête doivent contenir tous les nouveaux enregistrements différents des anciens.

1
Hawthorne
MYSQL TRIGGER BEFORE UPDATE IF OLD.a<>NEW.b

USE `pdvsa_ent_aycg`;

DELIMITER $$

CREATE TRIGGER `cisterna_BUPD` BEFORE UPDATE ON `cisterna` FOR EACH ROW

BEGIN

IF OLD.id_cisterna_estado<>NEW.id_cisterna_estado OR OLD.observacion_cisterna_estado<>NEW.observacion_cisterna_estado OR OLD.fecha_cisterna_estado<>NEW.fecha_cisterna_estado

    THEN 

        INSERT INTO cisterna_estado_modificaciones(nro_cisterna_estado, id_cisterna_estado, observacion_cisterna_estado, fecha_cisterna_estado) values (NULL, OLD.id_cisterna_estado, OLD.observacion_cisterna_estado, OLD.fecha_cisterna_estado); 

    END IF;

END
0
Herwin Rey