3

Due to reputation constraints, this is a follow-up question to this answer to a prior question. I'd like to know if there's a more efficient way to test each field for changes while handling NULL values.

CREATE TABLE foo (
  a INT NULL DEFAULT NULL, 
  b INT NULL DEFAULT NULL,
  c INT NULL DEFAULT NULL
);

CREATE TABLE bar (
  a INT NULL DEFAULT NULL,
  b INT NULL DEFAULT NULL,
  c INT NULL DEFAULT NULL
);

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

I want a trigger whose actions occur after update only if a value is changed by the update. So this UPDATE won't result in an INSERT:

UPDATE foo SET a = 1 WHERE b = 2;

But this UPDATE will result in an INSERT:

UPDATE foo SET a = 2 WHERE b = 2;

I have written this trigger to handle that, however I'm hoping the IF statement can be simplified.

DELIMITER ///
CREATE TRIGGER t_after_update_foo
AFTER UPDATE ON foo
FOR EACH ROW 
  BEGIN
    IF
      ((OLD.a <> NEW.a OR OLD.a IS NULL OR NEW.a IS NULL) AND (NEW.a IS NOT NULL OR OLD.a IS NOT NULL))
      OR
      ((OLD.b <> NEW.b OR OLD.b IS NULL OR NEW.b IS NULL) AND (NEW.b IS NOT NULL OR OLD.b IS NOT NULL))
      OR
      ((OLD.c <> NEW.c OR OLD.c IS NULL OR NEW.c IS NULL) AND (NEW.c IS NOT NULL OR OLD.c IS NOT NULL))
    THEN
      INSERT INTO bar (a, b, c) VALUES (NEW.a, NEW.b, NEW.c);
    END IF;
  END;
///
DELIMITER ;

Can the IF statement be simplified? Or is there an easier overall solution?

Community
  • 1
  • 1
Jobu
  • 590
  • 7
  • 18

2 Answers2

7

You can do this by comparing using the NULL-safe equals operator <=> and then negating the result using NOT.

So,

((OLD.a <> NEW.a OR OLD.a IS NULL OR NEW.a IS NULL) AND (NEW.a IS NOT NULL OR OLD.a IS NOT NULL))

would become

!(OLD.a <=> NEW.a)

To check if any of multiple columns have changed, you could do

!(OLD.a <=> NEW.a AND OLD.b <=> NEW.b)

A slightly shorter alternative if you have many colums to compare, courtesy of @dlauzon:

!((OLD.a, OLD.b, OLD.c, ...) <=> (NEW.a, NEW.b, NEW.c, ...))
user2428118
  • 7,935
  • 4
  • 45
  • 72
  • 1
    When there are many columns to check, a slightly shorter version could be: `!((OLD.a, OLD.b, OLD.c, ...) <=> (NEW.a, NEW.b, NEW.c, ...))` – dlauzon Oct 09 '19 at 20:01
5

You could use coalesce(), which returns the first of its arguments that is not null.

if coalesce(old.a,'') <> coalesce(new.a,'') or
   coalesce(old.b,'') <> coalesce(new.b,'') or
   coalesce(old.c,'') <> coalesce(new.c,'')
   then
     insert ...;
   end if;

It can be tricky to choose the second argument. The above example works for the common case when a, b and c are strings and when an empty string value is equivalent to a null value.

Andomar
  • 232,371
  • 49
  • 380
  • 404