MySQL - 3 updates need to be run 1 by 1 in Procedure - mysql

I have to run 3 updates in one procedure. My 3 separates update work well when I call them one bye one in php but I would like to have them regroup to perform only one call. My procedure is
create
definer = root#localhost procedure UpdateScorePredictionParticipant(IN predPts int(10), IN predId int(10), IN partId int(10))
BEGIN
UPDATE predictions
SET pred_pts = #predPts
WHERE pred_game_id = #predId and participant_id = #partId;
UPDATE ranking
SET pts_wildcard = (SELECT sum(pred_pts)
FROM predictions
INNER JOIN games g on predictions.pred_game_id = g.id
WHERE predictions.participant_id = #partId)
WHERE participant_id = #partId;
UPDATE ranking
SET total = (pts_wildcard+pts_conference+pts_division+pts_pred_sb+pts_sb)
WHERE participant_id = #partId;
END;
How could I perform those 3 updates 1 by 1 but in the same call.
Thanks,

As you are updating 3 different tables, you will need to do three separate updates. However if you put them in a procedure, you only need call the procedure from the PHP code.
To fix the procedure code, remove the # from the used parameter names.
It is also a good practice to prefix the parameters, so they will not mix up with the column names.
Last thing you can do, is to use a transaction inside the procedure, so all updates complete or if some of them fail, none of them gets done. This ensures the atomicity in the operation.
create procedure UpdateScorePredictionParticipant(
in_predPts int,
in_predId int,
in_partId int
)
BEGIN
START TRANSACTION;
UPDATE predictions
SET pred_pts = in_predPts
WHERE pred_game_id = in_predId and participant_id = in_partId;
UPDATE ranking
SET pts_wildcard = (
SELECT sum(pred_pts)
FROM predictions
INNER JOIN games g on predictions.pred_game_id = g.id
WHERE predictions.participant_id = in_partId
)
WHERE participant_id = in_partId;
UPDATE ranking
SET total = (pts_wildcard+pts_conference+pts_division+pts_pred_sb+pts_sb)
WHERE participant_id = in_partId;
COMMIT;
END

Related

Optimize MySQL stored procedure that is blocking my back-end transactions

I have this simple stored procedure that executes once per day to update the "energy" of the users depending on how many materials they have. But this takes around 2 minutes to end and I am wondering if there is a better way to do it:
BEGIN
SET #energy_premium = 10;
SET #energy_free = 5;
UPDATE user
SET energy = #energy_premium
WHERE id IN (
SELECT fk_user
FROM material
GROUP BY fk_user
HAVING COUNT(fk_user)>=2 AND user.id = material.fk_user);
UPDATE user
SET energy = #energy_free
WHERE id IN (
SELECT fk_user
FROM material
GROUP BY fk_user
HAVING COUNT(fk_user)=1 AND user.id = material.fk_user);
END
Also, when this stored procedure is executing my back-end services can't make transactions to the database.
Test this:
BEGIN
SET #energy_premium = 10;
SET #energy_free = 5;
UPDATE user
JOIN ( SELECT fk_user, CASE COUNT(fk_user) WHEN 1
THEN #energy_free
ELSE #energy_premium
END energy
FROM material
GROUP BY fk_user ) mat ON user.id = mat.fk_user
SET user.energy = mat.energy;
END;

How do I create a trigger with Inner Joins

I'm trying to create a trigger to populate an aud Loan Table when there is an insert in the Loan table. I want this aud Table to have data from both the Loan table and another table, so I'm trying to set variables that get this data.
When creating the trigger I'm getting the error "Unknown system variable 'var1'"
This is the database layout:
https://cdn.discordapp.com/attachments/582912082450710528/583696750322253824/unknown.png
DELIMITER $$
CREATE TRIGGER Loan_Insert AFTER INSERT ON loan
FOR EACH ROW
BEGIN
SET var1 =
(SELECT loan_type.type_of_loan
FROM loan INNER JOIN loan_type ON
loan.loan_type_idloan_type = loan_type.idloan_type
AND
loan.loan_type_idapp_type = loan_type.idapp_type
WHERE loan.loan_type_idloan_type = new.loan_type_idloan_type
AND loan.loan_type_idapp_type = new.loan_type_idapp_type);
SET var2 =
(SELECT loan_type.app_type
FROM loan INNER JOIN loan_type ON
loan.loan_type_idloan_type = loan_type.idloan_type
AND
loan.loan_type_idapp_type = loan_type.idapp_type
WHERE loan.loan_type_idloan_type = new.loan_type_idloan_type
AND loan.loan_type_idapp_type = new.loan_type_idapp_type);
INSERT INTO Aud_Loan(bk_Loan, type_of_loan, type_of_loan_description, application_type,
application_type_description, insert_date)
VALUES(new.idloan, new.loan_type_idloan_type, var1, new.loan_type_idapp_type, var2,CURDATE());
END $$
Local variables must be DECLAREd. docs
When using SET, the right hand side must only return one value (single result with single field).
In you particular case, you can probably tweak the queries used in your SET statements like this:
SET var1 = (
SELECT loan_type.type_of_loan
FROM loan_type
WHERE loan_type.idloan_type = NEW.loan_type_idloan_type
AND loan_type.idapp_type = NEW.loan_type_idapp_type
);
and you can probably even reduce it to one query with SELECT INTO:
SELECT loan_type.type_of_loan, loan_type.app_type
INTO var1, var2
FROM loan_type
WHERE loan_type.idloan_type = NEW.loan_type_idloan_type
AND loan_type.idapp_type = NEW.loan_type_idapp_type
;

Update differents fields based on criterias in MySQL

I store in my DB the demands some users can do. The demands can have differents status (stored as events), such as in progress, finished, waiting, and so on (there's 30ish differents status). The demands have differents deadlines corresponding of differents steps of the treatment.
I need to "freeze" some deadlines of the demands, if their current status belongs to a list of pre-defined ones.
In example :
If a demand has the status "A", I have to "freeze" the deadline 2 to 5.
If the status is "B" or "C", I have to "freeze" the deadline 3 to 5.
If the status is "D", I have to "freeze" the deadline 4 and 5.
I plan to use an EVENT that runs every day, at 19:00 to update (add 1 day) the differents deadlines of the concerned demands.
Table structures :
Table demand
id | someDatas | deadline1 | deadline2 | deadline3 | deadline4 | deadline5
---+-----------+-----------+-----------+-----------+-----------+-----------
| | | | | |
Table status
id | name
---+-----
|
Table events
id | id_demand | someOthersDatas | id_status
---+-----------+-----------------+----------
| | |
I wrote a query to get the demands corresponding of a list of status :
SELECT dem.*, st.`name` as 'statusName'
FROM `status` st
INNER JOIN `events` eve
ON eve.id_status = st.id
INNER JOIN `demand` dem
ON eve.id_demand = dem.id
WHERE st.`name` IN ('A', 'B', 'C', 'D')
AND eve.id IN
(
SELECT MAX(even.id) ev
FROM `demand` de
INNER JOIN `events` even
ON even.id_demand = de.id
GROUP BY de.id
);
This query works perfectly and I can get the desired informations for my treatment, I have the id of the demands, its deadlines and the name of the current status.
I don't mind storing this result in a temporary table such as :
DROP TEMPORARY TABLE IF EXISTS pendingDemands;
CREATE TEMPORARY TABLE IF NOT EXISTS pendingDemands
SELECT /* the query shown above */
To make sure the day I want to add to the deadline is valid (= not a day off) I wrote a function that calculate the next valid day :
DELIMITER //
DROP FUNCTION IF EXISTS `get_next_valid_date`;
CREATE FUNCTION `get_next_valid_date`(MyDate DATETIME) RETURNS DATETIME
BEGIN
REPEAT
SET MyDate = (DATE_ADD(MyDate, INTERVAL 1 DAY));
SET #someCondition = (select isDayOff(MyDate));
UNTIL (#someCondition = 0) END REPEAT;
RETURN MyDate;
END//
This function works perfectly and I get the expected results, and isDayOff() don't need to be detailed.
My problem is that I don't know how to use them (the temporary table pendingDemands and the function get_next_valid_date) together to update the table demand, I'm not skilled enough in SQL to build such pretty UPDATE query.
Any direction I could take?
I finally found a work around based on this answer
I created a stored procedure in which I'm using a cursor storing the query I was using to feed the pendingDemands temporary table.
Then, I looped over that cursor and used a CASE WHEN statement to determine the values to modify :
DELIMITER $$
DROP PROCEDURE IF EXISTS `freezePendingDeadlines` $$
CREATE PROCEDURE `freezePendingDeadlines`()
BEGIN
-- from http://stackoverflow.com/questions/35858541/call-a-stored-procedure-from-the-declare-statement-when-using-cursors-in-mysql
-- declare the program variables where we'll hold the values we're sending into the procedure;
-- declare as many of them as there are input arguments to the second procedure,
-- with appropriate data types.
DECLARE p_id INT DEFAULT 0;
DECLARE pT2P DATETIME DEFAULT NULL;
DECLARE pT3P DATETIME DEFAULT NULL;
DECLARE pT4P DATETIME DEFAULT NULL;
DECLARE pT5P DATETIME DEFAULT NULL;
DECLARE pstatusName VARCHAR(255) DEFAULT NULL;
-- we need a boolean variable to tell us when the cursor is out of data
DECLARE done TINYINT DEFAULT FALSE;
-- declare a cursor to select the desired columns from the desired source table1
-- the input argument (which you might or might not need) is used in this example for row selection
DECLARE demandCursor
CURSOR FOR
SELECT p.id,
p.T2P,
p.T3P,
p.T4P,
p.T5P,
P.statusName
FROM
(
SELECT dem.*, st.`name` as 'statusName'
FROM `status` st
INNER JOIN `events` eve
ON eve.id_status = st.id
INNER JOIN `demand` dem
ON eve.id_demand = dem.id
WHERE st.`name` IN ('A', 'B', 'C', 'D')
AND eve.id IN
(
SELECT MAX(even.id) ev
FROM `demand` de
INNER JOIN `events` even
ON even.id_demand = de.id
GROUP BY de.id
)
) AS p;
-- a cursor that runs out of data throws an exception; we need to catch this.
-- when the NOT FOUND condition fires, "done" -- which defaults to FALSE -- will be set to true,
-- and since this is a CONTINUE handler, execution continues with the next statement.
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
DROP TEMPORARY TABLE IF EXISTS days_off;
CREATE TEMPORARY TABLE IF NOT EXISTS days_off
(
date_off VARCHAR(5)
);
INSERT INTO days_off VALUES('01-01'),
('05-01'),
('05-08'),
('07-14'),
('08-15'),
('11-01'),
('11-11'),
('12-25');
-- open the cursor
OPEN demandCursor;
my_loop: -- loops have to have an arbitrary label; it's used to leave the loop
LOOP
-- read the values from the next row that is available in the cursor
FETCH demandCursor INTO p_id, pT2P, pT3P, pT4P, pT5P, pstatusName;
IF done THEN -- this will be true when we are out of rows to read, so we go to the statement after END LOOP.
LEAVE my_loop;
ELSE
CASE pstatusName
WHEN 'A' THEN
SET pT2P=get_next_valid_date(pT2P);
SET pT3P=get_next_valid_date(pT3P);
SET pT4P=get_next_valid_date(pT4P);
SET pT5P=get_next_valid_date(pT5P);
WHEN 'B' THEN
SET pT3P=get_next_valid_date(pT3P);
SET pT4P=get_next_valid_date(pT4P);
SET pT5P=get_next_valid_date(pT5P);
WHEN 'C' THEN
SET pT3P=get_next_valid_date(pT3P);
SET pT4P=get_next_valid_date(pT4P);
SET pT5P=get_next_valid_date(pT5P);
WHEN 'D' THEN
SET pT4P=get_next_valid_date(pT4P);
SET pT5P=get_next_valid_date(pT5P);
END CASE;
UPDATE `demand`
SET T2P=pT2P,
T3P=pT3P,
T4P=pT4P,
T5P=pT5P
WHERE id=p_id;
END IF;
END LOOP;
CLOSE demandCursor;
DROP TEMPORARY TABLE IF EXISTS days_off;
END$$

Updating a column name of a same table which has a parent child relationship using mysql

I searched a lot of doing a task but found no appropriate solution.
Basically the scenario is. I have a user_comment table in which there are 5 column(id,parent_id,user_comments,is_deleted,modified_datetime). There is a parent child relationship like 1->2,1->3,2->4,2->5,5->7 etc. Now i am sending the id from the front end and i want to update the column is_deleted to 1 and modified_datetime on all the records on
this id as well as the all the children and children's of children.
I am trying to doing this by using a recursive procedure. Below is the code of my procedure
CREATE DEFINER=`root`#`localhost` PROCEDURE `user_comments`(
IN mode varchar(45),
IN comment_id int,
)
BEGIN
DECLARE p_id INT DEFAULT NULL ;
if(mode = 'delete')
then
update user_comment set is_deleted = 1, modified_datetime = now()
where id = comment_id ;
select id from user_comment where parent_id = comment_id into p_id ;
if p_id is not null
then
SET ##GLOBAL.max_sp_recursion_depth = 255;
SET ##session.max_sp_recursion_depth = 255;
call user_comments('delete', p_id);
end if;
end if;
END
By using this procedure it give me an error of more than one row.
If i return the select query without giving it to variable then shows me the the appropriate results on the select query but i have to call this procedure recursively based on getting the ids of the select query.
I need help i have already passed 2 days into this.
I used cursor also. Below is the code of cursor
CREATE DEFINER=`root`#`localhost` PROCEDURE `user_comments`(
IN mode varchar(45),
IN comment_id int,
)
BEGIN
DECLARE p_emp int;
DECLARE noMoreRow INT;
DECLARE cur_emp CURSOR FOR select id from user_comment where parent_id = comment_id ;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET noMoreRow = 0;
if(mode = 'delete')
then
OPEN cur_emp;
LOOPROWS: LOOP
IF noMoreRow = 0 THEN
update user_comment set is_deleted = 1, modified_datetime = now() where id = comment_id
CLOSE cur_emp;
LEAVE LOOPROWS;
END IF;
FETCH cur_emp INTO p_emp;
update user_comment set is_deleted = 1, modified_datetime = now() where id = p_emp ;
SET ##GLOBAL.max_sp_recursion_depth = 255;
SET ##session.max_sp_recursion_depth = 255;
call user_comments('delete', p_emp);
END LOOP;
end if;
END
After using cursor i am getting a thread error.i don't know how can overcome this problem!!!
Mysql's documentation on select ... into varlist clearly says:
The selected values are assigned to the variables. The number of
variables must match the number of columns. The query should return a
single row. If the query returns no rows, a warning with error code
1329 occurs (No data), and the variable values remain unchanged. If
the query returns multiple rows, error 1172 occurs (Result consisted
of more than one row). If it is possible that the statement may
retrieve multiple rows, you can use LIMIT 1 to limit the result set to
a single row.
Since you wrote in the OP that a comment can be parent of many comments, using simple variables cannot be a solution. You should use a CURSOR instead, that can store an entire resultset.
You loop through the records within the cursos as shown in the sample code in the above link and call user_comments() in a recursive way.
UPDATE
If your receive
Error Code: 1436. Thread stack overrun
error, then you can do 2 things:
Increase the thread_stack setting in the config file and restart mysql server.
You can try to simplify your code to use less recursions and therefore less stack space. For example, when you fetch all children into the cursor, then rather calling the user_comments() recursively for each, you can set all direct children's status within the code and call the function recirsively on grand-childrens only (if any). You can also change your data structure and use nested set model to approach hierarchical structures.
Nested set model is more complex to understand, it is less resource intensive to traverse, but more resource intensive to maintain.

MySQL procedure gone wrong

I have a MySQL database in which I have the following rows (by exemple) created by default (id, task and case may be different but the current value is always 1)
....idtaskcaseuser............datecurrent
238......31001.....0..............null..........1
239......41001.....0..............null..........1
I have to randomly create rows like this with insert statement (new rows). As you can see a date is filled and de current equal 0
....idtaskcaseuser............datecurrent
240......51001.....12015.04.03..........0
241......21002.....12015.04.03..........0
When I come across one of the lines created by default I want to use an update instead of an insert statement.
So I created the following procedure in MySQL
DELIMITER //
DROP PROCEDURE IF EXISTS FillProgress//
CREATE PROCEDURE FillProgress ( get_case INT(10),get_task INT(10), get_user INT(10) )
BEGIN
DECLARE test tinyint(1);
SET test = (SELECT COUNT(*) FROM progress WHERE case_id = get_case AND task_id = get_task);
IF test = 1 THEN
UPDATE progress SET current = 0, date = NOW(), user_id = get_user WHERE task_id = get_id AND case_id = get_case;
ELSE
INSERT INTO progress(task_id,case_id,user_id,date,current) VALUES (get_task,get_case,get_user,NOW(),0);
END IF;
END; //
DELIMITER ;
I use count to see if a already have a row with the same case and task. If it's true (test=1) I use UPDATE, otherwise and use INSERT.
If I test with the following row already wrote in the database
....idtaskcaseuserdatecurrent
241......41001.....0..null..........1
I use CALL FillProgress(1001,4,1);
The row is not updated, but I do not have any error message.
11:38:02 CALL FillProgress(1001,4,1) 0 row(s) affected 0.000 sec
And if I manually use my update query
UPDATE progress SET current = 0, date = NOW(), user_id = 1 WHERE task_id = 4 AND case_id = 1001;
It works like a charm.
The insert query also works fine.
The UPDATE query within the procedure has a "WHERE task_id = get_id" clause, however I don't see get_id being defined in the procedure; there is a "get_task" parameter for the stored procedure, though.