提问人:mario199 提问时间:11/8/2023 更新时间:11/8/2023 访问量:35
有没有办法在MySQL触发器中动态引用NEW/OLD值?
Is there a way to reference NEW/OLD value in MySQL trigger dynamically?
问:
我正在尝试在数据库触发器中实现行的版本控制。我有 2 张桌子:
- 地址(ID、城市、街道、国家/地区)
- 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 值?
答:
2赞
Bill Karwin
11/8/2023
#1
不。游标返回数据值(字符串/数字/日期/等),而不是标识符。如果不使用动态 SQL,则无法将字符串用作标识符。
但在触发器中,不允许使用动态 SQL。
因此,您需要在添加或删除列的同时部署更新的触发器。
我承认,如果没有停机时间,这是相当困难的。
另一种方法是在更新表后更新 using 应用程序代码。address_versions
addresses
评论
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 的要求,而不是你的假设。:-)
评论
IF COALESCE(OLD.city, '') <> COALESCE(NEW.city, '') 那么
使用更清晰的 .IF NOT (OLD.city <=> NEW.city) THEN