MySQL仅在行已更改时才更新

时间:2011-06-09 16:43:24

标签: mysql sql database triggers

只有在数据真正改变的情况下才有可能使用“更新后”触发器。 我知道“新旧”。但是在使用它们时我只能比较列。 例如“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 |
+------+------+

重点是,有更新,但没有任何改变。 但无论如何都触发了触发器。恕我直言,它应该有一种方式。

我知道我可以使用

  

如果NOW.b<> OLD.b

这个例子。

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

加成

正如你在行上看到的那样

  

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

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

7 个答案:

答案 0 :(得分:67)

作为一种解决方法,您可以使用时间戳(旧的和新的)进行检查,但是当行没有更改时,更新。 (可能这是混淆的原因?因为那个也被称为'更新'但在没有发生变化时不执行) 一秒钟内的更改将不会执行触发器的那一部分,但在某些情况下可能会很好(例如,当您有一个拒绝快速更改的应用程序时。)

例如,而不是

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

答案 1 :(得分:15)

  

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

是的,但这是继续进行的方式。

作为旁注,在更新之前进行先发制人检查也是一种好习惯:

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

在您的示例中,这将使其更新(因此覆盖两行而不是三行。

答案 2 :(得分:12)

我无法评论,所以请注意,如果你的列支持NULL值,OLD.x&lt;&gt; 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))

答案 3 :(得分:9)

您可以使用NULL-safe equals operator <=>然后negating the result using 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 ;

(基于different answer of mine。)

答案 4 :(得分: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 ;

答案 5 :(得分:1)

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

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

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

答案 6 :(得分: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