在触发器中获取动态表名称

时间:2019-07-05 07:28:46

标签: mysql sql triggers

tb_tickets enter image description here

tb_sites_21

enter image description here

我正在创建触发器

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;

它工作正常,我只需要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;

 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

我该如何实现?

3 个答案:

答案 0 :(得分:3)

每个program_id的表分区是一个棘手的设计:SELECT * FROM sales + @yymm


您不能在触发器内部动态SQL / prepared语句,因此唯一的vialble选项使用IF语句,但是您需要提前知道表名。

CREATE DEFINER=`root`@`localhost` 
       TRIGGER `color_changed` 
       AFTER INSERT
BEGIN
 IF NEW.program_id = 1 THEN
   UPDATE tb_sites_1 
   SET color_status = NEW.status 
   WHERE site_id = NEW.site_id;
 ELSEIF NEW.program_id = 2 THEN
   UPDATE tb_sites_2 
   SET color_status = NEW.status 
   WHERE site_id = NEW.site_id;
 ELSEIF ... THEN
   -- ...
 ELSE
   -- ...
 END
END;

答案 1 :(得分:3)

与论坛上的许多问题一样,我将尝试退后一步,以了解真正的问题是什么。然后解决它。

一个明显的问题是您决定使用TRIGGER,但是由于两件事而碰壁:(1)模式具有多个相同的表,并且(2)触发器不允许“准备”。

备份, real 问题是基于“ program_id”存储一些数据。

因此,如果我们退出使用触发器(2),则可以使用存储过程和/或应用程序代码来完成任务。伴随着我经常说的话:“触发器(和外键)有局限性;您不一定要对它们做任何想做的事。”

但是,我认为(1)是更严重的设计缺陷。具有多个相同的表几乎总是“不正确的架构设计”,它们之间的区别仅在于表名。从长远来看,通常在单个表中有一个额外的列(program_id)很容易,而且从长远来看更有效。

我了解“单表”方法也有例外。如果您认为必须有单独的表格,请详细说明,以便我们进一步讨论。

答案 2 :(得分:2)

表名不能使用变量。无论您走到哪里,这都是关于SQL的事实,在解析查询时必须固定查询以及查询中引用的所有表和列。对于触发器,这意味着表名称必须在您CREATE TRIGGER时固定。

替代方法是将动态SQL与PREPARE and EXECUTE一起使用。这使您可以从字符串表达式构建查询,并使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;

不幸的是,这在触发器中不起作用。

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

这有一个缺点,如果要更新的表很多,它可能会很长,并且每次添加新表时都需要重新定义触发器。

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

您还可以使用任何应用程序编码语言编写一对等效的语句。您不需要执行存储过程。

您可以使用事务包装这两个语句,以便将它们同时提交,否则都回滚。

此替代方法将需要您更改应用程序插入票证的方式。因此,您必须更改应用程序代码。如果您没有更改应用程序代码的权限,那么这不是一个很好的选择。

3。将sites表重构为一个表(Paul Spiegel的评论)

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