1

I'm using the latest MySQL (8.0.15) and have a recursive query which updates the user table and I want to run it every time my user table has an INSERT, UPDATE or DELETE query run against it.

Context: As you'll see in the code snippet, I'm using a materialised path approach to storing my hierarchical data in MySQL. You can probably work out the point of it by looking at the data.

So I need this recursive query to run each time the table is modified so the correct Lineage and Depth is maintained.

The current problem is, after some troubleshooting with my original query I found on this SO post that I couldn't call a SET on the table as it would cause an infinite loop, so I need to prefix the columns with new.

I can't seem to work out how to do this and have provided a working example below with my current attempt.

Edit: I've updated the code below removing the DELIMITER due to db-fiddle limitations.

select version();
| version() |
| :-------- |
| 8.0.13    |
CREATE TABLE user 
(
   `Id` INT primary key, 
   `Name` VARCHAR(55), 
   `ManagerUserID` INTEGER, 
   `Depth` INTEGER, 
   `Lineage` VARCHAR(255)
);
INSERT INTO user (`Id`, `Name`, `ManagerUserID`, `Depth`, `Lineage`) 
VALUES ('1', 'User 1', NULL, 0, '1.'), 
       ('2', 'User 2', '1', 1, '1.2.'), 
       ('3', 'User 3', '4', 3, '1.2.4.3.'), 
       ('4', 'User 4', '2', 2, '1.2.4.'), 
       ('5', 'User 5', '2', 2, '1.2.5.');
SELECT * from user;

Id | Name   | ManagerUserID | Depth | Lineage 
-: | :----- | ------------: | ----: | :-------
 1 | User 1 |          null |     0 | 1.      
 2 | User 2 |             1 |     1 | 1.2.    
 3 | User 3 |             4 |     3 | 1.2.4.3.
 4 | User 4 |             2 |     2 | 1.2.4.  
 5 | User 5 |             2 |     2 | 1.2.5.  


CREATE TRIGGER after_user_update 
AFTER UPDATE ON user
FOR EACH ROW 
BEGIN
    UPDATE user
    SET new.Lineage = CONCAT(Id, '.'),
        new.Depth = 0
    WHERE new.ManagerUserID IS NULL;
   
    WITH RECURSIVE prev AS 
    (
        SELECT *
        FROM user
        WHERE new.ManagerUserID IS NULL
        UNION
        SELECT 
            t.new.Id,
            t.new.Name,
            t.new.ManagerUserID,
            p.new.Depth + 1 as Depth,
            CONCAT(p.new.Lineage, t.new.Id, '.')
        FROM
            prev p
        JOIN 
            user t ON t.new.ManagerUserID = p.new.Id
    )
    UPDATE user t, prev p
    SET t.new.Depth = p.Depth,
        t.new.Lineage = p.Lineage
    WHERE t.new.Id = p.new.Id;
END
UPDATE user 
SET `ManagerUserID` = '1' 
WHERE (`Id` = '3');

Can't update table 'user' in stored function/trigger because it is already used by statement which invoked this stored function/trigger.

SELECT * from user;


Id | Name   | ManagerUserID | Depth | Lineage 
-: | :----- | ------------: | ----: | :-------
 1 | User 1 |          null |     0 | 1.      
 2 | User 2 |             1 |     1 | 1.2.    
 3 | User 3 |             4 |     3 | 1.2.4.3.
 4 | User 4 |             2 |     2 | 1.2.4.  
 5 | User 5 |             2 |     2 | 1.2.5.  

db<>fiddle here

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Troy Poulter
  • 677
  • 1
  • 8
  • 29
  • `NEW.column` refers to the columns that are being inserted by the query that caused the trigger to run. Setting them after the update has completed has no effect. – Barmar Feb 14 '19 at 23:39
  • 1
    You're still not allowed to modify the table that the trigger is attached to. – Barmar Feb 14 '19 at 23:40
  • 1
    Your syntax error is a limitation of db-fiddle. Just remove `$$\nDELIMITER ;` and then' you'll hit the limitations [~Barmar] described. – danblack Feb 14 '19 at 23:43
  • 2
    "You're still not allowed to modify the table that the trigger is attached to" more or less true @Barmar you can't do it in a `UPDATE|INSERT|DELETE` query indeed, you have to use `SET NEW|OLD. = ` in the trigger code to gain access and modify the table record within in the trigger definition. – Raymond Nijland Feb 14 '19 at 23:49
  • Thanks for that syntax help @danblack. I tried your suggestion RaymondNijland and updated the example but it still doesn't seem to work. Any ideas on what I need to do different? – Troy Poulter Feb 15 '19 at 00:55
  • 1
    You seem to be missing the point of the error message, Barmars comment and your linked answer: you can only modify a single row in the table that the trigger is on (`user`), namely the one that you modified, and you need to do this with `set new.columnname` (without an `update`). `new`/`old` always references that single row. You basically have two options: store `Lineage` in a different table (then you can modify all rows), or update the data outside a trigger (e.g. create a procedure for basic operations like adding and removing a user that will do all the required modifications). – Solarflare Feb 15 '19 at 09:22
  • Thanks for explaining more of the context @Solarflare! I think your suggestion of storing `Lineage` in a different table will fit my requirements. – Troy Poulter Feb 17 '19 at 20:46

0 Answers0