Skip to content
Advertisement

Fetch dynamic table name in trigger

tb_tickets enter image description here

tb_sites_21

enter image description here

I am creating a trigger

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;

it is working fine only thing I need is tb_sites_21, I want this 21 to be picked from program_id of tb_tickets for which new entry is made.

Something like this:

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;

or

 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

How do I achieve this ?

Advertisement

Answer

You can’t use a variable for a table name. This is a fact about SQL wherever you go, that the query and all tables and columns referenced in the query must be fixed at the time the query is parsed. In the case of a trigger, that means the table name must be fixed at the time you CREATE TRIGGER.

The alternative is to use dynamic SQL with PREPARE and EXECUTE. This allows you to build a query from a string expression, and make MySQL parse and execute it at runtime.

It might look something like this:

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;

Unfortunately, this doesn’t work in a trigger.

https://dev.mysql.com/doc/refman/8.0/en/sql-syntax-prepared-statements.html says:

SQL syntax for prepared statements can be used within stored procedures, but not in stored functions or triggers.

Other comments and answers from P.Salmon and Lukasz Szozda have been trying to explain this, but you don’t seem to be listening.

There are three alternatives:

1. Hard-code each case (Lukasz Szozda’s answer)

I would have used the CASE statement instead of a chain of IF/THEN/ELSE IF blocks, but the logic is the same. You need

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

This has a disadvantage that it can get long if you have many tables to update, and you need to redefine the trigger each time you add a new table.

2. Use a stored procedure instead of a trigger (Rick James’ answer)

This does not use a trigger. Instead, it runs two statements, an INSERT followed by an UPDATE of the respective site table. You can do this in a stored procedure using the PREPARE/EXECUTE syntax.

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

You can also do an equivalent pair of statements in any application coding language. You don’t need to do a stored procedure.

You can use a transaction to wrap the two statements, so they are both committed simultaneously, or else both rolled back.

This alternative will require you to change the way the application inserts tickets. So you must change application code. If you don’t have permission to change application code, this isn’t a good alternative.

3. Refactor the sites table into one table (Paul Spiegel’s comment)

Several people suggested this, but you said you don’t have permission to change the table designs. This is unfortunate, but very common. It’s expensive to change the table design in an application, because there may be lots of application code depending on the current table design. All the code needs to be refactored to support a change to the tables.

User contributions licensed under: CC BY-SA
6 People found this is helpful
Advertisement