0

As P.Salmon suggested in his comment, here is the short description of what I would like to achieve:

I have a Testing2 tatble with just two columns (check example below). I regularly do a simple insert which can look like this:

INSERT INTO Testing2 (gasessiondurationbucket, gasessions) 
VALUES
(51815,20), 
(3654,18), 
(25981,28);

I also have a second table into which I would like to insert a single row log about the whole insert to Testing2, this table is called Insert_Log_For_Testing_Tables and has 4 columns (insert_id, created_date, created_by, ID_of_Insert). The ID_of_Insert is generally not important, I used this column to test some of my solutions (you can read about this below).

What I would like to achieve is to create a trigger that would fire after each insert into Testing2 table and create a single log in my log (Insert_Log_For_Testing_Tables) table. So for example after insert using the query above, i would like this trigger to log this:

| insert_id | created_date          | created_by                | ID_of_Insert |
| --------- | --------------------- | ------------------------- | ------------ |
|'1'        |'2020-03-14 16:54:12'  | 'testing@IP'              |              |
| --------- | --------------------- | ------------------------- | ------------ |

This is how the Testing2 table would look like after this insert:

| gasessiondurationbucket | gasessions |
| ----------------------- | ---------- |
| '51815'                 |'20'        |
| '3654'                  |'18'        |
| '25981'                 |'28'        |
| ----------------------- | ---------- |

After another insert using a simple insert query i would like to create another log in this table, so after two inserts the Insert_Log_For_Testing_Tables table would look like this:

| insert_id | created_date          | created_by                | ID_of_Insert |
| --------- | --------------------- | ------------------------- | ------------ |
|'1'        |'2020-03-14 16:54:12'  | 'testing@IP'              |              |
|'2'        |'2020-03-14 16:56:15'  | 'testing@IP'              |              |
| --------- | --------------------- | ------------------------- | ------------ |

And the Testing2 table would for example look like this:

| gasessiondurationbucket | gasessions |
| ----------------------- | ---------- |
| '51815'                 |'20'        |
| '3654'                  |'18'        |
| '25981'                 |'28'        |
| '96541968'              |'50'        |
| '496185'                |'48'        |
| '3168'                  |'36'        |
| '81656'                 |'55'        |
| ----------------------- | ---------- |

Is there a way to achieve this using only MySQL version 5.7?

Below are described some approaches i tried, one worked, but i do not think it is the ideal solution, other unfortunately did not do what i need.

"----------------------------------------------------------------------------------------------------------------------------------"
I am trying to create a trigger that would fire after each insert into a table and create a log in a different table with data about this insert.

This is some example data from my table called Testing2 into which I regularly insert data.

| gasessiondurationbucket | gasessions |
| ----------------------- | ---------- |
| '2035',                 |'1'         |
| '1783',                 |'1'         |
| '1777',                 |'1'         |
| '1187',                 |'1'         |
| '1103',                 |'1'         |
| '811',                  |'1'         |
| '644',                  |'1'         |
| '568',                  |'1'         |
| '546',                  |'1'         |
| '435',                  |'1'         |
| '320',                  |'1'         |
| '135',                  |'1'         |
| '130',                  |'1'         |
| '65',                   |'1'         |
| '42',                   |'1'         |
| '36',                   |'1'         |
| '34',                   |'1'         |
| '21',                   |'1'         |
| '15',                   |'1'         |
| '0',                    |'19'        |
| ----------------------- | ---------- |

This is the information about this table:

'CREATE TABLE `Testing2` (`gasessiondurationbucket` int(11) DEFAULT NULL,  `gasessions` int(11) DEFAULT NULL) ENGINE=InnoDB DEFAULT CHARSET=utf8'

Now the second table is called Insert_Log_For_Testing_Tables and it looks like this:

| insert_id | created_date          | created_by                | ID_of_Insert |
| --------- | --------------------- | ------------------------- | ------------ |
|'1',       |'2020-03-14 16:54:12', | 'testing@IP',             | '2'          |
|'2',       |'2020-03-14 16:54:12', | 'testing@IP',             | '2'          |
|'3',       |'2020-03-14 16:54:12', | 'testing@IP',             | '2'          |
|'4',       |'2020-03-14 16:54:12', | 'testing@IP',             | '2'          |
|'5',       |'2020-03-14 16:54:12', | 'testing@IP',             | '2'          |
|'6',       |'2020-03-14 16:54:12', | 'testing@IP',             | '2'          |
|'7',       |'2020-03-14 16:54:12', | 'testing@IP',             | '2'          |
|'8',       |'2020-03-14 16:54:12', | 'testing@IP',             | '2'          |
|'9',       |'2020-03-14 16:54:12', | 'testing@IP',             | '2'          |
|'10',      |'2020-03-14 16:54:12', | 'testing@IP',             | '2'          |
|'11',      |'2020-03-14 16:54:12', | 'testing@IP',             | '2'          |
|'12',      |'2020-03-14 16:54:12', | 'testing@IP',             | '2'          |
|'13',      |'2020-03-14 16:54:12', | 'testing@IP',             | '2'          |
|'14',      |'2020-03-14 16:54:12', | 'testing@IP',             | '2'          |
|'15',      |'2020-03-14 16:54:12', | 'testing@IP',             | '2'          |
|'16',      |'2020-03-14 16:54:12', | 'testing@IP',             | '2'          |
|'17',      |'2020-03-14 16:54:12', | 'testing@IP',             | '2'          |
|'18',      |'2020-03-14 16:54:12', | 'testing@IP',             | '2'          |
|'19',      |'2020-03-14 16:54:12', | 'testing@IP',             | '2'          |
|'20',      |'2020-03-14 16:54:12', | 'testing@IP',             | '2'          |
| --------- | --------------------- | ------------------------- | ------------ |

This is the information about this second table:

'CREATE TABLE `Insert_Log_For_Testing_Tables` (
  `insert_id` int(11) NOT NULL AUTO_INCREMENT,
  `created_date` datetime DEFAULT NULL,
  `created_by` varchar(25) DEFAULT NULL,
  `ID_of_Insert` int(11) DEFAULT NULL,
  PRIMARY KEY (`insert_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8'

Basically every insert into the first table consists of random amount of the two values, meaning sometimes I insert 20 new values, sometimes I insert 100 values.

What I would like to achieve is to make a single log using a trigger every time I do a new insert.

The problem I have is the FOR EACH ROW part of the trigger.

When I write the trigger simply like this:

CREATE DEFINER=`root`@`%` TRIGGER `MyDatabase`.`Testing2_AFTER_INSERT` AFTER INSERT ON `Testing2` FOR EACH ROW
BEGIN

DECLARE vUser varchar(50);

SELECT USER() INTO vUser;

INSERT INTO Insert_Log_For_Testing_Tables
( created_date,
created_by)
VALUES
( SYSDATE(),
vUser);
END

It works and fills insert_id(auto increment), created_date and created_by, but by its definition it does this for every new inserted row, meaning I get the log multiple (check Insert_Log_For_Testing_Tables Table) times whereas I would like just a single row log. What I came up with is that I can do delete after every insert to log table based on created_date, created_by and ID_of_Insert so it looks like this:

CREATE DEFINER=`root`@`%` TRIGGER `MyDatabase`.`Testing2_AFTER_INSERT` AFTER INSERT ON `Testing2` FOR EACH ROW
BEGIN
DECLARE vUser varchar(50);

SELECT USER() INTO vUser;

INSERT INTO Insert_Log_For_Testing_Tables
( created_date,
created_by)
VALUES
( SYSDATE(),
vUser);

DELETE t1 FROM Insert_Log_For_Testing_Tables t1
INNER JOIN Insert_Log_For_Testing_Tables t2 
WHERE 
    t1.insert_id < t2.insert_id AND
    t1.created_date = t2.created_date AND 
    t1.created_by = t2.created_by AND
    t1.ID_of_Insert = t2.ID_of_Insert;
END

This trigger works for me and only leaves one record in my log table after each and every insert with the biggest insert_id number, so when I create insert with another 20 rows I get the following log:

| insert_id | created_date          | created_by                | ID_of_Insert |
| --------- | --------------------- | ------------------------- | ------------ |
|'20',      |'2020-03-14 16:54:12', | 'testing@IP',             | '2'          |
|'40',      |'2020-03-14 16:56:15', | 'testing@IP',             | '2'          |
| --------- | --------------------- | ------------------------- | ------------ |

What I wonder though is that, because I am not sure how this works under the hood, for example, if I were to insert a lot of rows into the Testing2 table, lets say over million, if created_date into which I insert just SYSDATE() for each row (beacause of the FOR EACH ROW part of the trigger) could differ for some rows, meaning when i would do the delete part I might actually end up with two logs for one insert because the created_date column would contain more unique values (first 500000 rows would be inserted at 2020-03-14 16:54:12 and the other 500000 rows would be inserted at 2020-03-14 16:54:13) . I am not sure if this is possible, but I do not want to take any chances, so i thought i could try something else.

My second approach to my problem was to actually create two triggers (one BEFORE INSERT and one AFTER INSERT) and one stored procedure.

Now the BEFORE INSERT stored procedure is really simple and looks like this:

CREATE DEFINER=`root`@`%` TRIGGER `MyDatabase`.`Testing2_BEFORE_INSERT` BEFORE INSERT ON `Testing2` FOR EACH ROW
BEGIN
call MyDatabase.new_procedure('1', @IDOfRow2);
END

It just calls stored procedure with IN parameter of 1, I do this only to set up my one variable.

My stored procedure is also very simple:

CREATE DEFINER=`root`@`%` PROCEDURE `new_procedure`(IN  IDOfRow1 INT, OUT IDOfRow2 INT)
BEGIN
SELECT IDOfRow1 INTO IDOfRow2;
END

I only want to assign value of 1 to the variable so I can use it inside of the AFTER INSERT trigger.

The AFTER INSERT trigger looks like this:

CREATE DEFINER=`root`@`%` TRIGGER `MyDatabase`.`Testing2_AFTER_INSERT` AFTER INSERT ON `Testing2` FOR EACH ROW
BEGIN
DECLARE vUser varchar(50);

SELECT USER() INTO vUser;

SET @IDOfRow2 = @IDOfRow2 + 1;

IF @IDOfRow2 = 2 THEN
INSERT INTO Insert_Log_For_Testing_Tables
( created_date,
created_by,
ID_of_Insert)
VALUES
( SYSDATE(),
vUser,
@IDOfRow2);
END IF;

END

What I thought is that because od this line: SET @IDOfRow2 = @IDOfRow2 + 1; I would be able to change the value of @IDOfRow2 to 2 and therefore insert into the Insert_Log_For_Testing_Tables table only the first record - log, nothing else would be inserted because @IDOfRow2 would be bigger than 2. But unfortunately this does not work as, according to the research I've done, the BEFORE INSERT triggers for the first row, then AFTER INSERT triggers for the first row, then BEFORE INSERT triggers for the second row, then AFTER INSERT triggers for the second row etc. This happens for each and every row, so both triggers kind of alternate during the execution, meaning that my variable always gets refreshed so I insert log for every row, instead of just one for every insert.

Is there some other solution I could try to achieve what I need, or should I just stick with the DELETE statement inside of my AFTER UPDATE trigger and not worry about duplicated logs?

I would feel better if there is some solution similar to the one with two triggers and variable one which I proposed. Is there any solution to my problem or do I have to stick with the DELETE statement one?

Thank you very much, if there is something unclear please comment and I will try to clarify.

Raymond_90
  • 383
  • 5
  • 15
  • Are you aware of INSERT ON DUPLICATE KEY? – P.Salmon Mar 15 '20 at 09:24
  • @P.Salmon , thanks I was actually thinking about this, but I do not know how this could help me, I am by no means an expert on the matter, but I do not see any way i could utilize this. I mean my primary key is just auto_increment, meaning there will never be a duplicate, so i could do this based on created_date, but that would basically be very similar solution to the one with DELETE statement i wrote about in my question. If there is a way to increment variable for each INSERT and use this as primary key, than i could possibly use this. – Raymond_90 Mar 15 '20 at 11:19
  • @P.Salmon, do you know if there is a way to simulate {FOLLOWS|PRECEDES} for trigger on MySQL version 5.7? This would solve my problem i think. I am also trying [this](https://stackoverflow.com/questions/3884344/mysql-on-duplicate-key-insert-into-an-audit-or-log-table) , but i have problem with not being able to increment my variable again. Thanks – Raymond_90 Mar 15 '20 at 11:21
  • This is a very long question but it doesn't say what you are trying to do (it does describe how you have unsuccessfully attempted to something). Does Testing2 only have 2 columns? Do you only want 1 log entry per gassessions or some other criterion? Where does id_of _insert come from (it's not on testing2)? How are you batching the inserts to testing2? IS mysql the only technology in play? – P.Salmon Mar 15 '20 at 12:16
  • @P.Salmon , thank you for you comment, i have edited my question, there is a general explanation of what am I trying to achive above the line. The ID_of_Insert is just a testing column which i tried to use to achive what i need using a stored procedure (last approach i tried). – Raymond_90 Mar 15 '20 at 15:23
  • I would also like to mention, that I can not do this with stored procedure only, because the insert query is generated from a different service and I do not have any way to actually change it, so it is always a single insert query. I would somehow need to simulate statement level trigger or {FOLLOWS|PRECEDES} part of the trigger as in 5.7.2 to achieve what I need. – Raymond_90 Mar 15 '20 at 15:42

0 Answers0