仅当行已更改时,MySQL才会在更新后触发


69

仅在真正更改数据的情况下,才有可能使用“更新后”触发器。我知道“新旧”。但是使用它们时,我只能比较列。例如“ NEW.count <> OLD.count”。

但我想要类似的东西:如果“ NEW <> OLD”,则运行触发器

一个例子:

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 |
+------+------+

关键是,有一个更新,但是什么都没有改变。但是无论如何,触发器都在运行。恕我直言,应该有一个没有的方法。

我知道我可以使用

如果现在b <> OLD.b

对于这个例子。

但是,想象一下一个有变化的列的大桌子。您必须比较每列,如果数据库发生更改,则必须调整触发器。并且比较硬编码的行的每一列都不是“感觉”好:)

加成

如您所见

匹配的行:1已更改:0警告:0

MySQL知道这条线没有改变。但是它不会与触发器共享这些知识。像“ AFTER REAL UPDATE”之类的触发器或类似的东西会很酷。


如果列发生更改,则无论如何都需要调整触发器,因为它会插入INSERT INTO bar VALUES(NEW.a, NEW.b);。有什么解决方案可以避免吗?会INSERT INTO bar VALUES(SELECT * FROM foo WHERE…);工作吗?
zxcat

@ juwens,这个问题您应得10分以上。令人难以置信的是,他们如何如此反常地建立行为!
Ifedi Okonkwo

@zcat,不,您不必在每次更改表时都调整触发器。您可以做到INSERT INTO bar, SELECT * FROM foo WHERE foo.id = OLD.id,而且还可以。
Ifedi Okonkwo

Answers:


72

作为一种解决方法,您可以使用时间戳(旧的和新的)来检查,如果该行没有更改,则不会更新该时间戳。(可能是造成混乱的根源?因为那个也被称为“更新时”,但是在没有任何变化的情况下不会执行)一秒钟之内的变化将不会执行触发器的那部分,但是在某些情况下可能会很好(例如,当您的应用程序仍然拒绝快速更改时。)

例如,而不是

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

你可以用

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

这样一来,您不必在每次更新方案时都更改触发器(问题中提到的问题)。

编辑:添加了完整的示例

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)

由于mysql在处理时间戳上的行为,因此可以正常工作。仅当更新中发生更改时,时间戳才会更新。

文档在这里: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 |
+-------+-----------+------+-----+-------------------+-----------------------------+

我不知道这怎么工作。您能否详细解释一下这是什么意思。
juwens 2011年

@derkommissar:我添加了一个示例
Inca

有任何参考链接可用

1
如果更新频率低于1秒钟,则无法使用。时间戳将更改(但更改为相同的值)。您必须使用一个足够准确的时间戳(6)来跟踪所有更新
Rufo爵士,2016年

1
根据我的回答,别忘了使用<=>(可感知空值的运算符)来跟踪从null到back到null的变化
蜡笼

15

但是,想象一下一个有变化的列的大桌子。您必须比较每列,如果数据库发生更改,则必须调整触发器。而且比较硬编码的每一行并不“感觉”好:)

是的,但这就是进行的方式。

附带说明一下,在更新之前先检查一下也是一种好习惯:

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

在您的示例中,这将使它更新(从而覆盖行而不是三行。


@Denis,这不是必需的,MySQL会检查该值是否真正更改,并且仅在UPDATE需要时才启动(update + trigger)。您的支票只会使事情变慢。
约翰

5
@Johan:这是必需的,而MySQL不会这样做。如果这样做,它就不会尊重SQL标准-并且OP不会一开始就问他的问题。
Denis de Bernardy 2011年

@Denis,是的,我在5.0和5.5中用自己的测试触发器进行了检查,它确实做到了。真烦人
约翰

这并不烦人,这是正常现象,实际上是可取的:有时,不管是否发生更改,触发更新并触发它都是有用的。间接的结果是,如果没有实际发生更改,则由开发人员(或他的ORM,即使我知道后者永远做得最好)决定是否更新。
Denis de Bernardy 2011年

1
@Yohan:您可能会发现此讨论很有趣。
Denis de Bernardy 2011年

14

我不能评论,所以请注意,如果您的列支持NULL值,则OLD.x <> NEW.x不够,因为

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

返回0等于

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

因此它将不会跟踪更改FROM和TO NULL

在这种情况下正确的方法是

((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))

1
感谢您的宝贵答复/提示!这使时间戳记解决方案更具吸引力,而比较解决方案实际上不可用。
juwens 2014年

2
或者,您可以使用COALESCE()which返回其第一个参数not NULL。因此,您可以将其写为IF COALESCE(OLD.X,'') <> COALESCE(NEW.X,'')
Lyman Zerga,2014年

17
或者干脆使用mysql空感知比较运算符<=>
djmj 2014年

10

您可以通过使用NULL安全的equals运算符<=>比较每个字段,然后使用来对结果求反NOT来做到这一点。

完整的触发器将变为:

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 ;

(基于不同答案。)


2

在这里,如果有任何行影响新插入,那么它将在数据库中的不同表上更新。

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 ;

1

使用以下查询来查看哪些行已更改:

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

该查询的结果应包含与旧记录不同的所有新记录。


遗憾的是,这无法回答问题。更新不会删除行,并且如果更新未更改任何数据,OP会尝试停止触发触发器(将值1的列更新为值1不会更改数据,但是更新操作仍在运行,因此触发器也是如此。)
Takarii,2016年

据我了解,UPDATE查询会将OLD值放入已删除的记录集中,将NEW值放入插入的记录集中。虽然这不会阻止触发器运行,但是可以用来阻止触发器执行任何操作,通常这是足够的。
霍桑

也许值得用扩展的代码来说明您的答案。但是,请记住,这个问题也已有5年历史了。
塔卡里

这是Microsoft的报价。更新事务类似于删除操作,后跟插入操作。首先将旧行复制到已删除的表中,然后将新行复制到触发器表和插入的表中。”
Hawthorne

示例代码:CREATE TRIGGER ins_sum在foo上更新后INSERT INTO bar VALUES(从插入中选择*,从已删除中选择*);
霍桑

0
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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.