Trigger to update a table after an insert in a different one - mysql

I am executing this query:
INSERT INTO Takes(Student_SSN_N,Exam_Couse_ID,Exam_ID)
SELECT "xxx", "y", "z"
FROM Student as S, Exam as E
WHERE EXISTS (SELECT *
FROM Follows
WHERE S.Student_SSN = "xxx"
AND S.Student_SSN = Follows.Student_SSN_N
AND Follows.Course_ID = y
)
AND EXISTS (SELECT *
FROM Follows
WHERE Follows.Course_ID = y
AND E.Course_ID = y
AND E.Exam_ID = z
)
(Values x, y, z are given by the user in a java application)
It works, the student that has to take the exam is correctly added to the table after checking if he actually follows the course and if the exam relates to the course itself. I'm just performing a check on the Exam table.
In the Exam table I maintain a "student number", which tells you how many students need to take the exam. I want to update this number every time I insert a new student in the Takes table. So I thought about using a trigger, and I wrote this:
DELIMITER $$
CREATE TRIGGER `Takes_after_insert` AFTER INSERT ON `Takes`
FOR EACH ROW
UPDATE Exam
SET Exam.Student_Num = Exam.Student_Num + 1
WHERE Takes.Course_ID = Exam.Course_ID and
Take.Exam_ID = Exam.Exam.ID;
It's a simple trigger (I haven't studied anything more complex yet). But I get this error:
Error Code: 1442. Can't update table 'Exam' in stored
function/trigger because it is already used by statement which invoked
this stored function/trigger.
(I also tried pointing the trigger to a different table just to see if the logic of it was wrong, and it worked)
Ok, I can't update the Exam table because I'm using it in the query that launches the trigger. But the how can I update the table?

You don't need the exam table in the insert. In fact, the logic seems quite hard to follow, but I think you are just checking two conditions and inserting one row. You can check the conditions as subqueries in the FROM and use CROSS JOIN . . . if either returns no rows, then the CROSS JOIN returns no rows.
So, I think this insert does what you want:
INSERT INTO Takes(Student_SSN_N, Exam_Couse_ID, Exam_ID)
SELECT "xxx", "y", "z"
FROM (SELECT 1
FROM Follows f
WHERE f.Student_SSN_N = "xxx" AND
f.Course_ID = y AND
) s CROSS JOIN
(SELECT 1
FROM Follows f
WHERE f.Course_ID = y AND
E.Exam_ID = z
) e;
The structure of the Follows table is quite curious, containing exams, students, and courses. But that is not the question you are asking.
As mentioned in the comment, you want new in the trigger rather than exams.

Related

Update MySQL table any time another table changes

I am trying to reduce the number of queries my application uses to build the dashboard and so am trying to gather all the info I will need in advance into one table. Most of the dashboard can be built in javascript using the JSON which will reduce server load doing tons of PHP foreach, which was resulting in excess queries.
With that in mind, I have a query that pulls together user information from 3 other tables, concatenates the results in JSON group by family. I need to update the JSON object any time anything changes in any of the 3 tables, but not sure what the "right " way to do this is.
I could set up a regular job to do an UPDATE statement where date is newer than the last update, but that would miss new records, and if I do inserts it misses updates. I could drop and rebuild the table, but it takes about 16 seconds to run the query as a whole, so that doesn't seem like the right answer.
Here is my initial query:
SET group_concat_max_len = 100000;
SELECT family_id, REPLACE(REPLACE(REPLACE(CONCAT("[", GROUP_CONCAT(family), "]"), "\\", ""), '"[', '['), ']"', ']') as family_members
FROM (
SELECT family_id,
JSON_OBJECT(
"customer_id", c.id,
"family_id", c.family_id,
"first_name", first_name,
"last_name", last_name,
"balance_0_30", pa.balance_0_30,
"balance_31_60", pa.balance_31_60,
"balance_61_90", pa.balance_61_90,
"balance_over_90", pa.balance_over_90,
"account_balance", pa.account_balance,
"lifetime_value", pa.lifetime_value,
"orders", CONCAT("[", past_orders, "]")
) AS family
FROM
customers AS c
LEFT JOIN accounting AS pa ON c.id = pa.customer_id
LEFT JOIN (
SELECT patient_id,
GROUP_CONCAT(
JSON_OBJECT(
"id", id,
"item", item,
"price", price,
"date_ordered", date_ordered
)
) as past_orders
FROM orders
WHERE date_ordered < NOW()
GROUP BY customer_id
) AS r ON r.customer_id = c.id
where c.user_id = 1
) AS results
GROUP BY family_id
I briefly looked into triggers, but what I was hoping for was something like:
create TRIGGER UPDATE_FROM_ORDERS
AFTER INSERT OR UPDATE
ON orders
(EXECUTE QUERY FROM ABOVE WHERE family_id = orders.family_id)
I was hoping to create something like that for each table, but at first glance it doesn't look like you can run complex queries such as that where we are creating nested JSON.
Am I wrong? Are triggers the right way to do this, or is there a better way?
As a demonstration:
DELIMITER $$
CREATE TRIGGER orders_au
ON orders
AFTER UPDATE
FOR EACH ROW
BEGIN
SET group_concat_max_len = 100000
;
UPDATE target_table t
SET t.somecol = ( SELECT expr
FROM ...
WHERE somecol = NEW.family_id
ORDER BY ...
LIMIT 1
)
WHERE t.family_id = NEW.family_id
;
END$$
DELIMITER ;
Notes:
MySQL triggers are row level triggers; a trigger is fired for "for each row" that is affected by the triggering statement. MySQL does not support statement level triggers.
The reference to NEW.family_id is a reference to the value of the family_id column of the row that was just updated, the row that the trigger was fired for.
MySQL trigger prohibits the SQL statements in the trigger from modifying any rows in the orders table. But it can modify other tables.
SQL statements in a trigger body can be arbitrarily complex, as long as its not a bare SELECT returning a resultset, or DML INSERT/UPDATE/DELETE statements. DDL statements (most if not all) are disallowed in a MySQL trigger.

MySQL - make "Update" a permanent change to a table?

So I have two tables - "Horses" and "Results". "Horses" lists a bunch of information about each horse, including a spot called "LTE", which totals from an "Earnings" field from the "Results" table. "Results" listed all the results of recent horse shows. I use the following code to calculate LTE -
UPDATE horses
SET horses.LTE = ( SELECT SUM(results.earnings)
FROM results WHERE horses.hname=results.hname )
Which works wonderfully - it updates the LTE column. However...I have to run this code EVERY time I add new data to the "Results" table. I will be adding data month for...well, pretty much ever.
I don't want to have to run this code every time. Is there a way to make the code "permanent," in a sense that the LTE field KNOWS it just calculates whenever new information is added? Or does MySQL not work this way?
Here's a peek at my tables with some data in them.
MySQL doesn't work that way. But you can achieve such functionality by using triggers. For example, you can update your sum each time there's insert in respective table (and/or update), this way you will always have your sum 'cached' and you can recalculate if needed.
If you want a bit more analytics on sum changes, you can follow this pattern, I think it will be helpful
You can use a TRIGGER on INSERT, UPDATE and DELETE to update your table:
-- trigger for INSERT (new rows on table result).
DELIMITER |
CREATE TRIGGER ins_result AFTER INSERT ON results
FOR EACH ROW
BEGIN
UPDATE horses SET horses.LTE = (
SELECT SUM(results.earnings) FROM results WHERE horses.hname = results.hname
);
END;
|
-- trigger for UPDATE (changed rows on table result).
CREATE TRIGGER upd_result AFTER UPDATE ON results
FOR EACH ROW
BEGIN
UPDATE horses SET horses.LTE = (
SELECT SUM(results.earnings) FROM results WHERE horses.hname = results.hname
);
END;
|
-- trigger for DELETE (removed rows on table result).
CREATE TRIGGER del_result AFTER DELETE ON results
FOR EACH ROW
BEGIN
UPDATE horses SET horses.LTE = (
SELECT SUM(results.earnings) FROM results WHERE horses.hname = results.hname
);
END;
|
Another solution could be a VIEW:
CREATE VIEW v_horses AS
SELECT h.*, SUM(r.earnings) AS 'LTE'
FROM horses h INNER JOIN results r ON h.hname = r.hname
GROUP BY h.hname;
With the created VIEW you can get the information about the horses with the following query:
SELECT * FROM v_horses;
In your case you doesn't use a DECIMAL column. So you have to convert the VARCHAR column to SUM the earnings. So in your case you have to use the following VIEW:
CREATE VIEW v_horses AS
SELECT h.*, SUM(CAST(REPLACE(REPLACE(r.earnings, ',', ''), '$', '') AS DECIMAL)) AS 'LTE'
FROM horses h INNER JOIN results r ON h.hname = r.hname
GROUP BY h.hname;
-- for a specific year (like 2017)
CREATE VIEW v_horses AS
SELECT h.*, SUM(CAST(REPLACE(REPLACE(r.earnings, ',', ''), '$', '') AS DECIMAL)) AS 'LTE'
FROM horses h INNER JOIN results r ON h.hname = r.hname
WHERE DATE_FORMAT(STR_TO_DATE(r.`Date`, '%c/%e/%y'), '%Y') = 2017
GROUP BY h.hname;
-- grouped by year (so you can use WHERE on the VIEW):
CREATE VIEW v_horses AS
SELECT h.*, SUM(CAST(REPLACE(REPLACE(r.earnings, ',', ''), '$', '') AS DECIMAL)) AS 'LTE',
DATE_FORMAT(STR_TO_DATE(r.`Date`, '%c/%e/%y'), '%Y') AS 'year'
FROM horses h INNER JOIN results r ON h.hname = r.hname
GROUP BY h.hname, DATE_FORMAT(STR_TO_DATE(r.`Date`, '%c/%e/%y'), '%Y');
Note: In case of using this VIEW you have to remove the column LTE from table horses.

MySQL Insert from another table with 2 option WHERE statement

I have done my research but can not figure out how to do this. It is super simple to insert from another table but I want to include WHERE statements.
I want to insert value of a single column, column_Q from table A into table B's column_Q WHERE table A's column_W = '100' and column_Q does not already exist in table B.
I tried:
INSERT INTO B (column_Q) select DISTINCT(column_Q)
from A WHERE column_W = 100 AND b.column_Q<>a.column_Q;
Where am I doing wrong?
PS. Both tables already contain values. No field is Null.
INSERT
INTO b (q)
SELECT DISTINCT q
FROM a
WHERE a.w = 100
AND a.q NOT IN
(
SELECT q
FROM b
)
If your b.q has a UNIQUE constraint defined on it, then just use:
INSERT
IGNORE
INTO b (q)
SELECT q
FROM a
WHERE w = 100
You cannot refer to the left side of the "assignment", because there is no current row from B to compare to (that would be the one you are inserting) You need to check if a similar row is already present in B, like in:
INSERT INTO B (column_Q)
SELECT DISTINCT(A.column_Q)
FROM A
WHERE A.column_W = 100
AND NOT EXISTS (
SELECT *
FROM B
WHERE B.column_Q = A.column_Q
);

MySQL sub query select statement inside Update query

I have 2 tables: tbl_taxclasses, tbl_taxclasses_regions
This is a one to many relationship, where the main record ID is classid.
I have a column inside the first table called regionscount
So, I create a Tax Class, in table 1. Then I add regions/states in table 2, assigning the classid to each region.
I perform a SELECT statement to count the regions with that same classid, and then I perform an UPDATE statement on tbl_taxclasses with that number. I update the regionscount column.
This means I'm writing 2 queries. Which is fine, but I was wondering if there was a way to do a SELECT statement inside the UPDATE statement, like this:
UPDATE `tbl_taxclasses` SET `regionscount` = [SELECT COUNT(regionsid) FROM `tbl_taxclasses_regions` WHERE classid = 1] WHERE classid = 1
I'm reaching here, since I'm not sure how robust MySQL is, but I do have the latest version, as of today. (5.5.15)
You could use a non-correlated subquery to do the work for you:
UPDATE
tbl_taxclasses c
INNER JOIN (
SELECT
COUNT(regionsid) AS n
FROM
tbl_taxclasses_regions
GROUP BY
classid
) r USING(classid)
SET
c.regionscount = r.n
WHERE
c.classid = 1
Turns out I was actually guessing right.
This works:
UPDATE `tbl_taxclasses`
SET `regionscount` = (
SELECT COUNT(regionsid) AS `num`
FROM `tbl_taxclasses_regions`
WHERE classid = 1)
WHERE classid = 1 LIMIT 1
I just needed to replace my brackets [] with parenthesis ().

INSERT TRIGGER problems: "Key column information is insufficient or incorrect. Too many rows were affected by update."

I am having some problems with my TRIGGER:
CREATE TRIGGER "tblSettingTable_INSERT"
ON dbo.tblSettingTable
FOR INSERT
AS
INSERT INTO dbo.tblSettingReportParameter (tableName, columnName, columnCaption)
SELECT tableName = CAST(OBJECT_NAME(c.object_id) AS varchar(100)),
columnName = CAST(c.name AS varchar(100)),
columnCaption = CAST(ex.value AS varchar(100))
FROM sys.columns c
LEFT OUTER JOIN sys.extended_properties ex
ON ex.major_id = c.object_id
AND ex.minor_id = c.column_id
AND ex.name = 'MS_Caption'
INNER JOIN inserted ON OBJECT_NAME(c.object_id) = inserted.tableName
WHERE OBJECTPROPERTY(c.object_id, 'IsMsShipped')=0
AND OBJECT_NAME(c.object_id) = inserted.tableName
I am trying the get some column properties from a table and INSERT it into dbo.tblSettingReportParameter but I get this thrown at my face: "Key column information is insufficient or incorrect. Too many rows were affected by update."
What am I doing wrong? Using MS-SQL 2008 RS.
Thanks,
Stefan
Should be fixed if you add SET NOCOUNT ON to the trigger.
The xx rows returned is confusing Access (which I assume issues the INSERT to SQL Server of course based on the tags for the question)