art*_*art 5 mysql sql triggers
tb_sites_21
我正在创建一个触发器
CREATE DEFINER=`root`@`localhost`
TRIGGER `color_changed`
AFTER INSERT ON `tb_tickets`
FOR EACH ROW UPDATE tb_sites_21
SET color_status = NEW.status
WHERE site_id = NEW.site_id;
Run Code Online (Sandbox Code Playgroud)
它工作正常,只有我需要的是 tb_sites_21,我希望这 21 从新条目的 tb_tickets 的 program_id 中选取。
像这样的东西:
CREATE DEFINER=`root`@`localhost`
TRIGGER `color_changed`
AFTER INSERT ON `tb_tickets`
FOR EACH ROW UPDATE tb_sites_NEW.program_id
SET color_status = NEW.status
WHERE site_id = NEW.site_id;
Run Code Online (Sandbox Code Playgroud)
或者
CREATE DEFINER=root@localhost
TRIGGER color_changed AFTER INSERT ON tb_tickets FOR EACH ROW
SET @table_name := (SELECT CONCAT("tb_sites_" , program_id)
FROM tb_tickets
WHERE ticket_id = NEW.ticket_id);
UPDATE table_name set
color_status = NEW.status WHERE site_id = NEW.site_id
Run Code Online (Sandbox Code Playgroud)
我如何实现这一目标?
您不能使用变量作为表名。这是关于 SQL 的一个事实,无论您走到哪里,查询以及查询中引用的所有表和列都必须在解析查询时修复。对于触发器来说,这意味着表名必须在您执行操作时固定CREATE TRIGGER。
另一种方法是使用带有PREPARE 和 EXECUTE的动态 SQL 。这允许您从字符串表达式构建查询,并使 MySQL 在运行时解析并执行它。
它可能看起来像这样:
SELECT CONCAT("tb_sites_" , program_id) INTO @table_name
FROM tb_tickets WHERE ticket_id = NEW.ticket_id;
SET @sql = CONCAT('UPDATE ', @table_name,
' SET color_status = ? WHERE site_id = ?');
SET @color_status = NEW.status, @site_id = NEW.site_id;
PREPARE stmt FROM @sql;
EXECUTE stmt USING @color_status, @site_id;
DEALLOCATE PREPARE stmt;
Run Code Online (Sandbox Code Playgroud)
https://dev.mysql.com/doc/refman/8.0/en/sql-syntax-prepared-statements.html说:
预准备语句的 SQL 语法可以在存储过程中使用,但不能在存储函数或触发器中使用。
P.Salmon 和 Lukasz Szozda 的其他评论和答案一直试图解释这一点,但您似乎没有在听。
有以下三种选择:
1. 对每个案例进行硬编码(Lukasz Szozda 的回答)
我会使用该CASE语句而不是一系列 IF/THEN/ELSE IF 块,但逻辑是相同的。你需要
CREATE DEFINER=`root`@`localhost`
TRIGGER `color_changed`
AFTER INSERT
BEGIN
CASE NEW.program_id
WHEN 1 THEN
UPDATE tb_sites_1
SET color_status = NEW.status
WHERE site_id = NEW.site_id;
WHEN 2 THEN
UPDATE tb_sites_2
SET color_status = NEW.status
WHERE site_id = NEW.site_id;
WHEN 3 THEN
UPDATE tb_sites_3
SET color_status = NEW.status
WHERE site_id = NEW.site_id;
...etc...
END
END
Run Code Online (Sandbox Code Playgroud)
这样做的缺点是,如果您有很多表需要更新,那么它可能会很长,并且每次添加新表时都需要重新定义触发器。
2. 使用存储过程而不是触发器(Rick James 的回答)
这不使用触发器。相反,它运行两个语句,一个 INSERT 后跟相应站点表的 UPDATE。您可以使用 PREPARE/EXECUTE 语法在存储过程中执行此操作。
CREATE PROCEDURE InsertTicket(
IN in_ticket_id INT,
IN in_program_id INT,
IN in_color_status VARCHAR(10),
IN in_site_id INT)
BEGIN
DECLARE table_name VARCHAR(64);
-- First insert into the tickets table
INSERT INTO tb_tickets
SET ticket_id = in_ticket_id,
program_id = in_program_id,
color_status = in_color_status,
site_id = in_site_id;
-- Second, do a dynamic update into the respective site table
SET table_name = CONCAT('tb_sites_', in_program_id);
SET @sql = CONCAT('UPDATE ', table_name,
' SET color_status = ? WHERE site_id = ?');
SET @color_status = in_color_status, @site_id = in_site_id;
PREPARE stmt FROM @sql;
EXECUTE stmt USING @color_status, @site_id;
DEALLOCATE PREPARE stmt;
END
Run Code Online (Sandbox Code Playgroud)
您还可以使用任何应用程序编码语言执行一对等效的语句。您不需要执行存储过程。
您可以使用事务来包装这两个语句,以便它们同时提交,或者同时回滚。
此替代方案将要求您更改应用程序插入票证的方式。所以您必须更改应用程序代码。如果您无权更改应用程序代码,那么这不是一个好的选择。
3. 将站点表重构为一张表(Paul Spiegel 的评论)
有几个人建议这样做,但您说您无权更改表格设计。这是不幸的,但很常见。更改应用程序中的表设计成本很高,因为可能有大量应用程序代码取决于当前的表设计。所有代码都需要重构以支持对表的更改。