I want to call multiple procedures from within a procedure. In the following SQL, I create three procedures. upd_r_money and upd_r_fuel both work as expected when called individually from the command line. When I call upd_all, only the first call within upd_all is run; the second call to upd_r_money doesn't run.
I can't figure out why this happens - maybe something in my upd_r_fuel procedure causes my upd_all procedure to end early? I am a newby to writing procedures, and SQL in general.
There was another question here about this problem, but the answer is exactly what I'm already doing, and the answer's link was down.
drop procedure upd_r_money;
delimiter //
CREATE procedure upd_r_money(row_id int)
BEGIN
DECLARE money_rate INT DEFAULT 1;
DECLARE period INT DEFAULT 0;
SET period = (select timestampdiff(second, (select lastaccessed from gamerows where id = row_id), now()));
update gamerows
set money = money + period * money_rate,
lastaccessed = now()
where id = row_id;
END;
//
delimiter ;
drop procedure upd_r_fuel;
delimiter //
CREATE procedure upd_r_fuel(row_id int)
fuel: BEGIN
DECLARE fuel_rate INT DEFAULT 1;
DECLARE period INT DEFAULT 0;
SET period = (select timestampdiff(second, (select lastaccessed from gamerows where id = row_id), now()));
update gamerows
set fuel = fuel + period * fuel_rate,
lastaccessed = now()
where id = row_id;
END fuel;
//
delimiter ;
drop procedure upd_all;
delimiter //
CREATE PROCEDURE upd_all(row_id int)
BEGIN
call upd_r_fuel(row_id);
call upd_r_money(row_id);
END;
//
delimiter ;
If I copy and paste the above SQL commands, my procedures are created successfully with no errors and I can call all three of them. However as I wrote earlier, upd_all seems to stop after calling its first procedure within. If I switch upd_r_money with upd_r_fuel, the same behavior occurs - the first procedure is called and not the second.
I suspect that it doesn't work as expected because you update lastaccessed time and calculate difference with NOW. First work because there is significant difference. But with second stored procedure you have timestammpdiff between NOW() and NOW() - miliseconds.
Check if removing in first stored procedure lastaccessed from update helps.
drop procedure upd_r_money;
delimiter //
CREATE procedure upd_r_money(row_id int)
BEGIN
DECLARE money_rate INT DEFAULT 1;
DECLARE period INT DEFAULT 0;
SET period = (select timestampdiff(second, (select lastaccessed from gamerows where id = row_id), now()));
update gamerows
set money = money + period * money_rate
where id = row_id;
END;
//
delimiter ;
Warning: Now the order of execution matters.
Also your stored procedures are so similiar that I would combine them in one UPDATE:
update gamerows
set fuel = fuel + period * fuel_rate,
money = money + period * money_rate,
lastaccessed = now()
where id = row_id;
Related
I created a little program to show my students how to use loops in MySQL (Workbench).
The program updates a column in a table with test data. I want to add a range of interest rates. When I run the program, the same interested rate is entered into each row.
CREATE TABLE mtgPayment
(lengthMonths DECIMAL(4,0),
mInterest DECIMAL(8,6),
loanAmt DECIMAL(10,2));
DELIMITER $$
CREATE PROCEDURE test_data()
BEGIN
DECLARE i DECIMAL(8,6) DEFAULT 2.0;
WHILE i < 3.5 DO
UPDATE mtgpayment set mINTEREST = i;
SET i = i + .5;
END WHILE;
END$$
DELIMITER ;
CALL test_data();
SELECT * FROM mtgpayment;
I want to return data in the single set, however I'm getting 10 different outputs with single row. I want to have 10 rows in a single set:
DROP TABLE IF EXISTS calendar;
DROP PROCEDURE IF EXISTS p_generate_snapshot;
CREATE TABLE calendar(date date);
INSERT INTO calendar(date) VALUES
('2020-11-01'),
('2020-11-02'),
('2020-11-03'),
('2020-11-04'),
('2020-11-05'),
('2020-11-06'),
('2020-11-07'),
('2020-11-08'),
('2020-11-09'),
('2020-11-10');
DELIMITER $$
CREATE PROCEDURE p_generate_snapshot(start_date date, end_date date)
BEGIN
DECLARE d date;
DECLARE done INT DEFAULT FALSE;
DECLARE cursor_name CURSOR FOR SELECT * FROM calendar c WHERE date >= start_date AND date < end_date;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cursor_name;
fetch_loop: LOOP
FETCH cursor_name INTO d;
IF done THEN
LEAVE fetch_loop;
END IF;
SELECT d;
END LOOP;
CLOSE cursor_name;
END$$
DELIMITER ;
CALL p_generate_snapshot('20201101', '20201201');
It's not clear why you are using a cursor for this. Your procedure could be written as:
CREATE PROCEDURE p_generate_snapshot(start_date date, end_date date)
BEGIN
SELECT * FROM calendar c WHERE date >= start_date AND date < end_date;
END
That would return one result set as you said you wanted.
But it's possible that your example is simplified and you have other steps you need to do with the rows fetched by your cursor. That would be a legitimate reason to use a cursor.
But I don't think it's possible to return the rows handled by a cursor as if it's one result set. Certainly it is not possible to do it by SELECT d; in each iteration of your cursor loop. That is bound to return a separate result set per row, as you found out.
One workaround, though it is pretty awkward, is to insert the rows to a temporary table during the cursor loop. Then select from the temp table as a last step.
CREATE PROCEDURE p_generate_snapshot(start_date date, end_date date)
BEGIN
DECLARE _d date;
DECLARE done INT DEFAULT FALSE;
DECLARE cursor_name CURSOR FOR SELECT date FROM calendar c WHERE date >= start_date AND date < end_date;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
CREATE TEMPORARY TABLE t (d DATE);
OPEN cursor_name;
fetch_loop: LOOP
FETCH cursor_name INTO _d;
IF done THEN
LEAVE fetch_loop;
END IF;
INSERT INTO t SET d = _d;
END LOOP;
CLOSE cursor_name;
SELECT d FROM t;
DROP TABLE t;
END
For that matter, I don't know why you would use a procedure at all, instead of just running that SELECT statement in your client application. That would solve both problems -- handling rows one by one, but still treating it as a single query result.
I almost never use stored procedures in MySQL. I find they are really more trouble than they're worth. I posted reasons why here: https://www.quora.com/What-are-the-reasons-not-to-use-or-not-use-stored-procedures/answer/Bill-Karwin
i am trying to write a stored procedure for mysql, and basicly, it will select one id, and update selected id's operationdate.
DELIMITER //
CREATE PROCEDURE getID(
IN proc_intervaldate datetime
)
BEGIN
DECLARE RUserID BIGINT;
SELECT
UserID
INTO RUserID
FROM Tasks
WHERE OperationDate < proc_intervaldate
LIMIT 1 ;
UPDATE Tasks SET OperationDate = now() WHERE UserID = RUserID;
SELECT RUserID ;
END //
DELIMITER ;
but when i use this, procedure, it will return UserID but not update, if i comment
SELECT RUserID
then, it updates, but no data returns.
when I use this, procedure, it will return UserID but not update, if I comment 'SELECT RUserID;' then, it updates, but no data returns.
You can change definition of stored procedure to use an OUT parameter rather than just IN. With that, you can just capture the OUT value on the same OUT parameter, in your scripting language. Meaning you need not execute:
SELECT RUserID ;
in your stored procedure.
Change your SP definition as below:
DELIMITER //
CREATE PROCEDURE getID( IN proc_intervaldate datetime, OUT RUserID bigint )
BEGIN
SELECT
UserID INTO RUserID
FROM Tasks
WHERE
OperationDate < proc_intervaldate
LIMIT 1 ;
UPDATE Tasks SET OperationDate = now() WHERE UserID = RUserID;
END;
//
DELIMITER ;
You can call this stored procedure like, for example, at MySQL console:
SET #RUserID := 0;
CALL getID( now(), #RUserID );
SELECT #RUserID;
Refer to:
MySQL: CALL procedure syntax
To get back a value from a procedure using an OUT or INOUT parameter,
pass the parameter by means of a user variable, and then check the
value of the variable after the procedure returns.
Problem:
Hi I've got two stored procedures, that I try to run from another one.
- If I call them one after the other from phpmysqladmin, everything works fine.
- If I call the stored procedure, that calls the other two, I don't get an error. So far so good. But the problem is, that the operations from the second stored procedure aren't executed.
Already tried to run it in just one sp...
I also tried to run both stored procedures (sp1,sp2) in one stored procedure, with the same effect.
Could that be the problem?
In the first sp I use a statement like this:
Select #var:= ....
Here is the code:
In the first stored procedure I generate a dynamic query and execute it.
Procedure 1
CREATE PROCEDURE `sp_prepare_valid_choices`(IN p_request_id Bigint)
BEGIN
DECLARE num_rows INT DEFAULT 0;
DECLARE no_more_rows BINARY;
DECLARE no_more_subrows BINARY;
DECLARE loop_cntr INT DEFAULT 0;
DECLARE var_choice_group BIGINT DEFAULT 0;
-- Declare Cursor for the loop through the constraint_groups
DECLARE cur_constraint_group CURSOR FOR
SELECT distinct choice_constraint_group FROM casainte_choice_constraint
WHERE choice_id_rule_parameter IN (SELECT choice_id FROM casainte_request_detail
where request_id = p_request_id);
-- DECLARE 'handlers' for exceptions
DECLARE CONTINUE HANDLER FOR NOT FOUND
SET no_more_rows := TRUE;
-- DELETE OLD VALUES
DELETE FROM tmp_casainte_valid_choices
WHERE request_id = p_request_id;
DELETE FROM tmp_casainte_valid_choices_for_request
WHERE request_id = p_request_id;
-- OPEN CURSOR AN PROCESS CONSTRAINT_GROUPS
OPEN cur_constraint_group;
SELECT FOUND_ROWS() INTO num_rows;
choice_group_loop: LOOP
FETCH cur_constraint_group
INTO var_choice_group;
IF no_more_rows THEN
CLOSE cur_constraint_group;
LEAVE choice_group_loop;
END IF;
-- PAYLOAD
-- INSERT THE VALID CHOCIES INTO tmp_casainte_valid_choices
SELECT #var_sql_query := CONCAT('INSERT INTO tmp_casainte_valid_choices ','SELECT ',p_request_id,' as request_id, `casainte_choice_constraint`.`choice_constraint_id`,`casainte_choice_constraint`.`choice_constraint_group`
,AVG(IF (`casainte_request_detail`.`choice_varchar_value`', `casainte_choice_constraint`.`choice_constraint_operator`, '\'',`casainte_choice_constraint`.`choice_constraint_value`, '\'',',1,0 )) AS VALID
FROM `casainte_choice_constraint`
LEFT JOIN `casainte_request_detail` ON `casainte_request_detail`.`choice_id` = `casainte_choice_constraint`.`choice_id_rule_parameter`
WHERE `casainte_choice_constraint`.choice_constraint_group =' , var_choice_group,
' GROUP BY `casainte_choice_constraint`.choice_constraint_group')
FROM `casainte_choice_constraint` WHERE `casainte_choice_constraint`.choice_constraint_group = var_choice_group;
PREPARE SQL_STATEMENT FROM #var_sql_query;
EXECUTE SQL_STATEMENT;
-- INCREMENT THE COUNTER
SET loop_cntr = loop_cntr + 1;
END LOOP choice_group_loop;
END$$
Procedure 2
In the second stored procedure I insert the values into a table.
delimiter $$
CREATE PROCEDURE `sp_insert_valid_choices`(IN p_request_id Bigint)
BEGIN
INSERT INTO tmp_casainte_valid_choices_for_request
(request_id, choice_id)
SELECT DISTINCT p_request_id, choice_id FROM casainte_choice ac
-- RULE 1 ALL CHOICES WITHOUT CONSTRAINTS
WHERE ac.choice_id NOT IN (SELECT choice_id_rule_target FROM casainte_choice_constraint)
-- RULE 2 ALL CHOICES WITH CONSTRAINTS, THAT ARE NOT YET ANSWERED
OR ac.choice_id NOT IN (SELECT choice_id_rule_target FROM casainte_choice_constraint
WHERE choice_id_rule_parameter IN (SELECT choice_id FROM casainte_request_detail WHERE request_id = p_request_id))
-- RULE 3 ALL CHOICES WITH CONSTRAINTS, THAT ARE TRUE
OR ac.choice_id IN (SELECT choice_id_rule_target FROM casainte_choice_constraint
WHERE choice_constraint_group IN (SELECT choice_constraint_group FROM tmp_casainte_valid_choices WHERE request_id = p_request_id AND VALID = 1));
END$$
Procedure 3
The third stored procedure calls the 1st sp, then the 2nd sp.
delimiter $$
CREATE PROCEDURE `sp_generate_valid_choices`(IN p_request_id Bigint)
BEGIN
Call `sp_prepare_valid_choices`(p_request_id);
Call `sp_insert_valid_choices`(p_request_id);
END$$
You don't have "delimiter $$" at the top of Procedure 1. Could this be causing the problem?
I am running a stored procedure. The issue seems to be that it will go into the if statement. Also for some reason or another regardless of how many selects I use it will only return the first. I've copied this from another stored procedure that works like a charm, but this one just won't go. Any ideas?
DROP PROCEDURE IF EXISTS genSelPriceTier;
DELIMITER $$
CREATE PROCEDURE genSelPriceTier(tier_id INT, default_id INT)
BEGIN
DECLARE rowCount INT DEFAULT 0;
SELECT * FROM price_tier WHERE price_tier_id = tier_id;
SET rowCount = FOUND_ROWS();
IF rowCount < 1 THEN
SELECT * FROM price_tier WHERE price_tier_id = default_id;
END IF;
END$$
DELIMITER ;
There is a bug reported related to the usage of FOUND_ROWS(). So, I recommend using Count(*) for the number of rows returned. Something like the following should work.
DROP PROCEDURE IF EXISTS genSelPriceTier;
DELIMITER $$
CREATE PROCEDURE genSelPriceTier(tier_id INT, default_id INT)
BEGIN
DECLARE rowCount INT DEFAULT 0;
SELECT COUNT(*) INTO rowCount FROM price_tier WHERE price_tier_id = tier_id
IF rowCount < 1 THEN
SELECT * FROM price_tier WHERE price_tier_id = default_id;
END IF;
END$$
DELIMITER ;