有没有办法在MySQL触发器中动态引用NEW/OLD值?

Is there a way to reference NEW/OLD value in MySQL trigger dynamically?

提问人:mario199 提问时间:11/8/2023 更新时间:11/8/2023 访问量:35

问:

我正在尝试在数据库触发器中实现行的版本控制。我有 2 张桌子:

  1. 地址(ID、城市、街道、国家/地区)
  2. address_versions (同上, object_changes)

每次更改地址表中的行时address_versions我想在表中创建一行。

我创建了一个以下触发器:

CREATE TRIGGER `addresses_after_create` AFTER INSERT ON `addresses` FOR EACH ROW
    this_trigger:BEGIN
    DECLARE object_changes_json JSON;

    -- Prepare object_changes JSON for address_versions row creation
    SET object_changes_json = JSON_OBJECT();

    IF COALESCE(OLD.city, '') <> COALESCE(NEW.city, '') THEN
        SET object_changes_json = JSON_SET(object_changes_json, '$.city', JSON_ARRAY(OLD.city, NEW.city));
    END IF;

    IF COALESCE(OLD.street, '') <> COALESCE(NEW.street, '') THEN
        SET object_changes_json = JSON_SET(object_changes_json, '$.street', JSON_ARRAY(OLD.street, NEW.street));
    END IF;

    IF COALESCE(OLD.country, '') <> COALESCE(NEW.country, '') THEN
        SET object_changes_json = JSON_SET(object_changes_json, '$.country', JSON_ARRAY(OLD.country, NEW.country));
    END IF;

    INSERT INTO address_versions (object_changes)
    VALUES (object_changes_json);

因此,每次更改地址表中的行时,都会使用更改值的 JSON 创建address_versions行。

但是上述解决方案的问题在于,每次向表添加/删除列时,我都需要更新触发器。

有没有办法更动态地做到这一点?

我尝试创建一个带有列名的单独表并遍历它,但我无法动态访问 NEW/OLD 值

...
    read_loop: LOOP
        FETCH cursor_column_name INTO column_name_string;
        IF done THEN
            LEAVE read_loop;
        END IF;
        IF COALESCE(OLD.column_name_string, '') <> COALESCE(NEW.column_name_string, '') THEN
            SET object_changes_json = JSON_SET(object_changes_json, '$.additional_info', JSON_ARRAY(OLD.column_name_string, NEW.column_name_string));
        END IF;
    END LOOP;
...

但它失败了:

=> ERROR 1054 (42S22): Unknown column 'column_name_string' in 'OLD'

有没有办法动态引用 NEW/OLD 值?

MySQL 触发器 版本

评论

1赞 P.Salmon 11/8/2023
“有没有办法动态引用 NEW/OLD 值?”——不,没有——这是一个变更控制练习。
0赞 Akina 11/9/2023
IF COALESCE(OLD.city, '') <> COALESCE(NEW.city, '') 那么使用更清晰的 .IF NOT (OLD.city <=> NEW.city) THEN

答:

2赞 Bill Karwin 11/8/2023 #1

不。游标返回数据值(字符串/数字/日期/等),而不是标识符。如果不使用动态 SQL,则无法将字符串用作标识符。

但在触发器中,不允许使用动态 SQL。

因此,您需要在添加或删除列的同时部署更新的触发器。

我承认,如果没有停机时间,这是相当困难的。

另一种方法是在更新表后更新 using 应用程序代码。address_versionsaddresses

评论

1赞 Akina 11/9/2023
我承认,如果没有停机时间,这是相当困难的。添加/删除列通常比更改触发器所需的时间更长。此外 - 添加列时,如果旧触发器使用一段时间,则没有问题,而删除列时,新触发器可能会在旧结构中工作一段时间。因此,停机时间会很小,甚至可能为零。
1赞 Bill Karwin 11/9/2023
@Akina,也没有原子替换触发语句。您必须删除旧触发器并创建新触发器。在放置和创建之间的那一刻,您可能会错过一些更改。因此,停机时间不能为零。
1赞 Akina 11/9/2023
MySQL允许创建两个触发器,它们逐一个作用。因此,我们只需创建新触发器,然后删除旧触发器(当然触发器名称会更改,但这不会产生任何影响)。很多操作将被记录两次,以旧格式和新格式,但我怀疑这是一个问题。
1赞 Bill Karwin 11/9/2023
你可以随心所欲地怀疑它,但这取决于 OP 的要求,而不是你的假设。:-)