I need to insert a discount line into a table everything time a I insert a line into the same table.
Now i know that this could end in a endless loop but I have put checks in so it wont insert anything when the discount line is inserted.
Mysql doesnt seem to allow this.It doesnt even insert into the table let alone fire off the trigger
Any suggestions on how to do this?
You cannot alter a table (other than the current row) in a trigger attached to that table.
One solution is to insert into another table and have that trigger insert 2 rows into the table you're interested in.
If you make the other table a blackhole you don't have to worry about storage.
DELIMITER $$
CREATE TRIGGER ai_bh_test_each AFTER INSERT ON bh_test FOR EACH ROW
BEGIN
INSERT INTO table1 (field1, field2, ...) VALUES (new.field1, new.field2, ....);
INSERT INTO table1 ... values for the second row
END $$
DELIMITER ;
Why don't you just change your INSERT code into something like this? :
INSERT INTO table1 (field1, field2, ...)
VALUES ( #item, #price, ....)
, ( #item, #discount, ...) ;
Another thing would be to re-evaluate your data structure. The way it is now, it seems - from the limited information you have provided - that it's not normalized. You are storing two types of info in the table.
Perhaps you can combine the two rows (that are to be inserted every time) into one, by adding a few columns in the table.
Or by splitting the table into two tables, one for the "normal" item rows and one for the discount items.
It isn't allowed in MySQL.
One solution would be to let the trigger insert two times into another table. Then you would do writes and updates to the write table and reads from the trigger managed read table.
I needed to add an additional row to the same table, based on a specific condition on an aggregate of the table and was unable to update my application queries to handle it outside of the database, due to stability lock policy.
An alternative solution is to utilize Events in MySQL to read a "staging" table that holds the pending changes. This works by eliminating the circular reference that would be caused by the trigger. The event then executes the desired changes, without initiating the trigger, by using a session variable to leave the trigger early. Please modify the event timing to suit your needs, such as EVERY 5 SECOND.
Staging Table
CREATE TABLE `table_staging` (
`id` INT NOT NULL,
`value` VARCHAR(250) NOT NULL,
`added` TINYINT(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
);
Trigger
CREATE TRIGGER `table_after_insert` AFTER INSERT ON `table` FOR EACH ROW
tableTrigger: BEGIN
IF #EXIT_TRIGGER IS NOT NULL THEN
/* do not execute the trigger */
LEAVE tableTrigger;
END IF;
/* insert the record into staging table if it does not already exist */
INSERT IGNORE INTO `table_staging`(`id`, `value`)
VALUES (NEW.id, 'custom value');
END;
Event
CREATE EVENT `table_staging_add`
ON SCHEDULE
EVERY 1 MINUTE STARTS '2020-03-31 18:16:48'
ON COMPLETION NOT PRESERVE
ENABLE
COMMENT ''
DO BEGIN
/* avoid executing if the event is currently running */
IF #EXIT_TRIGGER IS NULL THEN
SET #EXIT_TRIGGER=TRUE;
/* add the values that have not already been updated */
INSERT INTO table(`value`)
SELECT
ts.value
FROM table_staging AS ts
WHERE ts.added = 0;
/* update the records to exclude them on the next pass */
UPDATE table_staging AS ts
SET ts.added = 1;
/* alternatively remove all the records from the staging table */
/* TRUNCATE table_staging; */
/* reset the state of execution */
SET #EXIT_TRIGGER=NULL;
END IF;
END;
Notes
Be sure to enable the event scheduler in your MySQL configuration (my.ini on Windows or my.cnf on Linux).
[mysqld]
event_scheduler = ON
Related
At a high level I am already aware that you can not insert a new row to the same table and that one should consider a SPROC. Here is the use case though. There is a 3rd party web app and this MySQL DB that I have, so I have no control over the application flow. A request has come in to simplify data entry. What I do have control over is the database. The app is like a CRM and has a contacts table and there is a 2nd contact_relationships table where I am putting the trigger on. Basically the contact_relationships needs three fields. Two contactIDs (INT) from the contact table and a relationship_type varchar(45) like (Spouse, Sibling, External Family and so on).
The goal here is when a new row is added (the TRIGGER) to the contact_relationships table, that we ALSO write a 2nd row to the SAME TABLE that inverts the contactIDs and keeps the relationship_type. This ensures there is also a record relationship established for the other contact from the same single entry. (ideally should be done in the app).
I'm at my whits end over what should be a stupid simple operation. I even tried this creative implementation.
I created a new _temp table
CREATE TABLE `_temp` (
`id_temp` int(11) NOT NULL AUTO_INCREMENT,
`Contact_id` int(11) DEFAULT NULL,
`Relation_id` int(11) DEFAULT NULL,
`Relationship_Type` varchar(45) DEFAULT NULL,
PRIMARY KEY (`id_temp`)
I have two triggers on the contact_relationships table
#1
CREATE DEFINER=`root`#`localhost` TRIGGER `contact_relationships_BEFORE_INSERT` BEFORE INSERT ON `contact_relationships` FOR EACH ROW BEGIN
insert into _temp (Contact_id,Relation_id,Relationship_Type)
values(NEW.Contact_id,NEW.Relation_id,NEW.Relationship_Type);
END
#2
CREATE DEFINER=`root`#`localhost` TRIGGER `contact_relationships_AFTER_INSERT` AFTER INSERT ON `contact_relationships` FOR EACH ROW BEGIN
delete from _temp;
END
On the _temp table I have this trigger
CREATE DEFINER=`root`#`localhost` TRIGGER `_temp_AFTER_DELETE` AFTER DELETE ON `_temp` FOR EACH ROW BEGIN
#DO Sleep(2);
# Sleep commented out as it did not work and only delayed the error
insert into contact_relationships (Relation_id,Contact_id,Relationship_Type) values(OLD.Contact_id,OLD.Relation_id,OLD.Relationship_Type);
END
So my thinking here in pseudo code is like this
When a row is inserted into the contact_relationships table write the inverted row to a temp table as I can not write it to the **same table from inside the trigger**.
AFTER the triggered inserted row is complete lets delete the row in the temp table so we can create a DELETE trigger on that other table to write the desired row into the contact_relationship.
At this stage I believe the TRIGGER and TRANSACTIONS on the contact_relationships table are DONE
On the temp Table a trigger and transaction firing AFTER any contact_relationships transactions
Results are maddening and always the same albeit internal or external. I have tried creative functions and sprocs all with the same aggravating results.
Contact Relationships : Add New
Can't update table 'contact_relationships' in stored function/trigger because it is already used by statement which invoked this stored function/trigger.
Query:
insert into `Contact_Relationships` set `Contact_id`='2', `Relation_id`='3', `Relationship_Type`='Extended Family'
I'm completely brain dead on how I may accomplish this - anyone have a way to pull this off?
dsoden.
What I would do is:
Inside the trigger definition, you can get the "inserted_id" of the relationship that was inserted ( You can get it withLAST_INSERT_ID() ).
Once you have it, you can SELECT from that record into temp variables:
SELECT `Contact_id`,`Relation_id`,`Relationship_Type` INTO #cont_id, #rel_id, #rel_type
FROM contact_relationships
WHERE id = LAST_INSERT_ID()
And then do the Insert of the new relationship in the reverse order, with the same relationship type:
INSERT INTO contact_relationships (`Contact_id`,`Relation_id`,`Relationship_Type`) VALUES ( #rel_id, #cont_id, #rel_type);
If that's what you're aiming for..
Something like:
When a relationship between Anne and John as Siblings is inserted,
insert the John,Anne,Siblings relationship as well
Problem is this: I have to write the data to a physical file each time after a certain table is updated.
Normally I would do this with a trigger, for example:
CREATE TRIGGER log_to_file AFTER INSERT ON LOG
BEGIN
...
END
How do I refer to the data that triggers a database trigger in the first place from within the trigger code ? This is namely the data I have to analyze and have to write to a file.
Not sure what you want. As per my understanding , you want to refer a row that is just inserted. You can refer that with NEW in code. So this might work for you.
CREATE TABLE LOG
(
ID INT(11) NOT NULL,
Description NVARCHAR(50) NULL,
)
CREATE TRIGGER log_to_file AFTER INSERT ON LOG
FOR EACH ROW
BEGIN
/* Logic can be put here for each inserted row */
INSERT INTO any_table (ID,Description,Date) VALUES(New.ID,New.Description,SYSDATE())
END
I was trying to create trigger which can update value of column user_count of table user_details using value of u_count of table user_info.
CREATE TRIGGER `test`
AFTER INSERT ON `user_details` FOR EACH ROW
BEGIN
DECLARE default_user_count int(11);
SELECT u_count INTO #default_user_count FROM
user_info WHERE user_info.id= user_details.id_c;
IF user_details.user_count= 0
THEN UPDATE user_details SET
user_count = default_user_count
WHERE user_details.id_c = user_info.id;
END IF;
END
Trigger saved successfully but when i tried to insert value in both table it is preventing to insert record into user_details means no row inserted in 2 this table, if we delete trigger then its working.
Can anyone let me know wrong with this trigger?
THanks,
M.
It's not really clear what you're trying to accomplish, but it seems like it's something like what we have below.
There are numerous errors in and ambiguities in your trigger.
Confusion on variables -- DECLARE default_user_count INT(11); does not declare the user-defined variable #default_user_count. It declares the program variable default_user_count. The # prefix references an entirely different variable scope and namespace.
SELECT and UPDATE from the table which invoked the trigger doesn't usually make sense (SELECT) or is completely invalid (UPDATE).
With in a trigger, you are operating FOR EACH ROW -- that is, for each row included in the statement that invoked the trigger. Inside an INSERT trigger, the NEW values for the row are in a pseudo-table/pseudo-row accessible via the alias NEW. For UPDATE triggers, there are NEW and OLD row values, and for DELETE triggers, just OLD.
AFTER INSERT doesn't seem to make sense. I think you're looking for BEFORE INSERT -- that is, while processing an INSERT INTO ... query, before the newly-inserted row actually gets written into the table, modify its values accordingly. The resulting row contains the original values except where the trigger has modified them.
SELECT ... INTO a variable is a practice you should not get into the habit of, because it can bite you in a way a scalar subquery can't, by leaving a variable unexpectedly unaltered instead of setting it to NULL as would be expected. In this case, it would have made no difference, but it's still a caution worth mentioning... and in this case, I've eliminated that intermediate variable altogether, so the subquery is the only option.
If you are trying to set a value in this table using a value found in another table, all you need to do is SET NEW.column_name equal to the value you want used in the row instead of the value provided with the insert statement.
CREATE TRIGGER `test`
BEFORE INSERT ON `user_details` FOR EACH ROW
BEGIN
IF NEW.user_count = 0 /* maybe also >> */ OR NEW.user_count IS NULL /* << this */ THEN
SET NEW.user_count = (SELECT ui.u_count
FROM user_info ui
WHERE ui.id = NEW.id_c);
END IF;
END
Again, it's unclear how the two tables are connected based on the content of the original question, but this appears to do what you're trying to accomplish.
There is a declared MySQL function GETUSERID() returning an integer value. How to make a record insert faster: setting the value from inside a query like
INSERT INTO ttable
(idtoset, some_other_field...)
VALUES (GETUSERID(), value1...);
or call
INSERT INTO ttable
(some_other_field...)
VALUES (value1...);
and fill idtoset by a trigger that fires before insert?
What if the query is performing multiple row insert like
INSERT INTO ttable
(idtoset, some_other_field...)
VALUES (GETUSERID(), value1...),
(GETUSERID(), value2...),
...
(GETUSERID(), valueN...);
?
Edit
I have just investigated the answer of #Rahul.
I created a ttest table with two triggers
CREATE TRIGGER `tgbi` BEFORE INSERT ON `ttest` FOR EACH ROW BEGIN
SET NEW.testint=1;
END;
CREATE TRIGGER `tgbi` BEFORE UPDATE ON `ttest` FOR EACH ROW BEGIN
SET NEW.testint=2;
END;
If I am not mistaken, should the before insert trigger call UPDATE SET the second trigger is expected to fire as well and the created testint value might be =2, but it is =1 in every inserted row. Could that mean that the engine optimises INSERT procedure and sets the value simultaneously with that set manually by query?
Appended on request of #Rick-James. The question is not about the definite function. It is actually about any function. Any function will be called same number of times if the record is inserted from trigger or from INSERT query. That is why I am wondering what is better from the point of MySQL engine - to call it manually setting the value in inserted records or filling it by means of triggers?
CREATE DEFINER=`***`#`***` FUNCTION `GETUSERID`() RETURNS int(10)
BEGIN
DECLARE id_no INT DEFAULT -1;
SELECT `id` INTO id_no FROM `tstuff`
WHERE `tstuff`.`user_name`=
(SELECT SUBSTRING_INDEX(USER(), '#', 1)) LIMIT 1;
RETURN id_no;
END
What is faster? No idea since I haven't done a bench marking on that but doing an direct INSERT operation would better to my knowledge instead of inserting and then perform an UPDATE through trigger.
Does what you are doing currently not working? you can as well make it a INSERT .. SELECT operation like
INSERT INTO ttable (idtoset, some_other_field...)
SELECT GETUSERID(), value1..., valuen FROM DUAL;
In past versions of MySQL, using a before insert trigger to populate a not nullable column didn't work as MySQL was evaluating the provided columns before the trigger. That's why whenever I have such a situation, I usually tend to go with functions instead of triggers.
From a performance point of view, since the before insert trigger is evaluated before actually writing data so the time needed to perform this is almost the same as immediately getting the value with the function and without trigger. But if all you are doing in the trigger is set the user ID, then I really see no reason to use a trigger.
I have a view in mysql which is made of three tables unioned together:
CREATE VIEW program_operator_jct_view AS
select
program_id,
operator_code,
'PROGRAM_OPERATOR' AS type
from
program_operator_jct
UNION
(select
program_id,
operator_code,
'PROGRAM_GROUP' AS type
from
program_operator_group_jct pg_jct,
operator_group_jct og_jct
where
pg_jct.group_id = og_jct.group_id)
From this view, I create a summary table for increased performance, which is indexed so my results from this summary table can be returned via covering indexes:
CREATE TABLE `program_operator_jct_summary` (
`program_id` int(7) NOT NULL,
`operator_code` varchar(6) NOT NULL,
PRIMARY KEY (`program_id`,`operator_code`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1
//BUILD SUMMARY PROCEDURE
delimiter //
CREATE PROCEDURE update_program_operator_jct_summary ()
BEGIN
DELETE FROM program_operator_jct_summary;
INSERT INTO program_operator_jct_summary select DISTINCT program_id, operator_code from program_operator_jct_view;
INSERT INTO trigger_record (name) VALUES ('update_program_operator_jct_summary');
END
//
I attached this procedure to the insert, update and delete triggers of the underlining tables which make up the summary table:
-program_operator_jct
-program_operator_group_jct
-operator_group_jct
EXAMPLE:
delimiter //
CREATE TRIGGER trigger_program_operator_jct_insert AFTER INSERT ON program_operator_jct
FOR EACH ROW
BEGIN
CALL update_program_operator_jct_summary ();
END
//
Here's my problem when I add (5) operators to the program_operator_jct:
INSERT INTO program_operator_jct (program_id, operator_code) VALUES
('112', '000500'),
('112', '000432'),
('112', '000501'),
('112', '000264'),
('112', '000184')
This trigger runs (5) times, if I add 100 operators this trigger runs 100 times. This is a nice place to use triggers because I don't have to worry about the summary table being out of date with the underlining tables.
However rebuilding a summary table for each value in an extended inserts is way too much of a performance hit (sometimes I add hundreds of operators to programs at a time). I want the trigger to run once after the extended inserts are performed on the underlining tables. Is this possible?
The trigger is doing its job, e.g. 'FOR EACH ROW'.
I don't believe that mysql gives you the option of running a trigger once at the end.
I'd call the stored procedure from your code after the INSERT has successfully completed.
If you're worried about forgetting, setup a cron job to run it every once in a while.
Good luck.