在触发器中获取动态表名

art*_*art 5 mysql sql triggers

tb_tickets 在此处输入图片说明

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)

我如何实现这一目标?

Bil*_*win 4

您不能使用变量作为表名。这是关于 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 的评论)

有几个人建议这样做,但您说您无权更改表格设计。这是不幸的,但很常见。更改应用程序中的表设计成本很高,因为可能有大量应用程序代码取决于当前的表设计。所有代码都需要重构以支持对表的更改。