Having trouble doing multiple cursors in a MySQL stored procedure - mysql

I'm writing a store procedure to create two temporary tables do an union select of the two.
When using either first or second cursor alone with the other commented the procedure works,
but when I run the query to create the procedure with the 2 cursors, it fails.i've changed the code to reflect Ike Walker's suggestion.
Here is the script:
DELIMITER //
DROP PROCEDURE IF EXISTS joinemailsmsdailygraph//
CREATE PROCEDURE joinemailsmsdailygraph(IN previousDay VARCHAR(20), IN today VARCHAR(20))
READS SQL DATA
BEGIN
DECLARE hours INT;
DECLARE sms INT;
DECLARE email INT;
DECLARE smsdone INT DEFAULT 0;
DECLARE emaildone INT DEFAULT 0;
DECLARE cursorsms CURSOR FOR SELECT HOUR(sm.date_created) AS `HOUR OF DAY`, COUNT(*) AS smscount
FROM sms_message_delivery smd
JOIN sms_message sm ON sm.sms_message_id = smd.sms_message_id
WHERE DATE(sm.date_created) >= DATE(previousDay) AND DATE(sm.date_created) < DATE(today)
GROUP BY HOUR(sm.date_created);
DECLARE CONTINUE HANDLER FOR NOT FOUND SET smsdone =1;
DECLARE cursoremail CURSOR FOR SELECT HOUR(em.date_created) AS `HOUR OF DAY`, COUNT(*) AS emailcount
FROM email_message_delivery emd
LEFT JOIN email_message em ON emd.email_message_id=em.email_message_id
WHERE DATE(em.date_created) >= DATE(previousDay) AND DATE(em.date_created) < DATE(today)
GROUP BY HOUR(em.date_created);
DECLARE CONTINUE HANDLER FOR NOT FOUND SET emaildone =1;
DROP TEMPORARY TABLE IF EXISTS tempsms;
CREATE TEMPORARY TABLE tempsms (hours_day INT, sms_count INT, email_count INT);
OPEN cursorsms;
sms_loop: LOOP
FETCH cursorsms INTO hours , sms;
IF smsdone = 1 THEN
LEAVE sms_loop;
END IF;
INSERT INTO tempsms (hours_day, sms_count) VALUES (hours, sms);
END LOOP sms_loop;
CLOSE cursorsms;
DROP TEMPORARY TABLE IF EXISTS tempemail;
CREATE TEMPORARY TABLE tempemail (hours_day INT , sms_count INT , email_count INT);
OPEN cursoremail;
email_loop: LOOP
FETCH cursoremail INTO hours, email;
IF emaildone=1 THEN
LEAVE email_loop;
END IF;
INSERT INTO tempemail(hours_day, email_count) VALUES(hours, email);
END LOOP email_loop;
CLOSE cursoremail;
SELECT hours_day, sms_count , email_count FROM tempsms
UNION
SELECT hours_day, sms_count, email_count FROM tempemail;
END//
DELIMITER;
it gives this as error
Query : CREATE PROCEDURE joinemailsmsdailygraph(IN previousDay VARCHAR(20), IN today VARCHAR(20)) READS SQL DATA BEGIN DECLARE hours INT...
Error Code : 1338
Cursor declaration after handler declaration
Execution Time : 00:00:00:000
Transfer Time : 00:00:00:000
Total Time : 00:00:00:000
ive tried putting both continue handlers at the end of all declare section but it complains about declare block overlapping or so.
Can you please tell me what I'm doing wrong? Thanks for reading.

Why are you using cursors ? You could easily do this without a tmp table also by just using a union.
drop procedure if exists join_email_sms_daily_graph;
delimiter #
create procedure join_email_sms_daily_graph
(
in previousDay varchar(20),
in today varchar(20)
)
begin
create temporary table tmp
(
hours_day int unsigned,
sms_count int unsigned default 0,
email_count int unsigned default 0
)engine=memory;
insert into tmp (hours_day, sms_count)
select
hour(sm.date_created) as hours_day,
count(*) AS sms_count
from
sms_message_delivery smd
join sms_message sm ON sm.sms_message_id = smd.sms_message_id
where
date(sm.date_created) >= date(previousDay) and date(sm.date_created) < date(today)
group by
hour(sm.date_created);
insert into tmp (hours_day, email_count)
select
hour(em.date_created) as hours_day,
count(*) AS email_count
from
email_message_delivery emd
left join email_message em ON emd.email_message_id=em.email_message_id
where
date(em.date_created) >= date(previousDay) and date(em.date_created) < date(today)
group by
hour(em.date_created);
select * from tmp;
drop temporary table if exists tmp;
end#
delimiter;

You need to declare all of the cursors up front, before the logic of your procedure begins.
If you do things in this order it should work:
DECLARE variables
DECLARE cursors
DECLARE handlers
...
logic

Related

declaring variable inside mysql stored procedure

we are trying to declare a variable inside mysql stored procedure that has transaction implemented in it. but it seems to be giving a syntax error :
following is the syntax of the stored procedure:
CREATE PROCEDURE `sp_MarkAppointmentRefferal`(
p_AppId bigint,
p_NewLocation bigint,
p_userId bigint,
p_ReferralReason varchar(500),
p_NewLocationName varchar(100)
)
begin
declare v_OldLocation int default 0;
set v_OldLocation = (select LocationId FROM appointments where iAppID = p_AppId limit 1 );
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
select -1;
END;
START TRANSACTION;
update table
set is_referred = 1,
referred_timestamp = now(),
referral_reason = p_ReferralReason
where iAppID = p_AppId
limit 1;
-- create a new appointment for the new referred location..
insert into appointments
(vAppName, vAppType, dAppDate, vCell, iPatID, iAppStatus, iuserid, iActive,
dInsertDate, iHSID, daily_ticket_no, LocationId, visit_id, encounter_id, ReferredFrom,ReferredOPDName, opd_name )
select vAppName, vAppType, now(), vCell, iPatID, iAppStatus, p_userId,
1, now(), iHSID, fn_GenerateNextAppointmentTicket(now(),p_NewLocation) , p_NewLocation, visit_id, encounter_id+1,
(select LocationId FROM appointments where iAppID = p_AppId limit 1),
(select OPD_Name FROM appointments where iAppID = p_AppId limit 1), p_NewLocationName
FROM appointments
where iAppID = p_AppId limit 1;
select LAST_INSERT_ID();
COMMIT;
end;
the syntax checker is saying that declare command is not valid here.
have also tried to place this inside the transaction clause and similar error shows up ..
any help is appreciated..
All declare statements should be at the top of the stored procedure body. Moving DECLARE EXIT HANDLER before the SET statement should fix the problem.

MySQL stored procedure pass select as parameter

could you please give me an advice how to CALL prcd with SELECT results? Or advice me pls better solution.. I am open minded to all working solution
I have a procedure to control inserting data ...
CREATE PROCEDURE control_insert (
)
And I need to pass data from SELECT results to procedure ...
SELECT t.c1, t.c2
FROM table t1
LEFT JOIN other_table t2
ON t1.id = t2.id
WHERE 1=1
The point is, I need to get some data via SELECT (around 6 tables joined to the base table) and I need to do control for each row before insert.. each row should meet some conditions .. if it doesn't meet them, it should just skip it and process next one ...
The procedure should look like:
CREATE PROCEDURE control_insert (
IN v_c1 INT,
IN v_c2 INT
)
BEGIN
IF v_c1 > 1 THEN
INSERT INTO controlled_table (id, type) VALUES (v_c1, v_c2);
ELSE
/* do nothing */
END IF;
END;
CALL control_insert ( SELECT .... );
Could you help me with that? Is there any possibility to do this via MySQL? I can write a PERL skript, but I want to avoid this type of solution ... I just one to do it only in MySQL way
Thank you
EDIT1: I need to check if ID of the SELECT result and LABEL is already in this table for specific date ... this code above is only an example to demonstrate the situation
SOLUTION
I've found the solution ... so for the other visitors:
calling procedure:
CALL controlInsert();
procedure body:
CREATE PROCEDURE controlInsert()
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE v_id INT;
DECLARE v_id_dupl INT;
DECLARE v_label INT;
DECLARE v_date DATE;
DECLARE v_type VARCHAR(100);
DECLARE v_category VARCHAR(255);
DECLARE v_user VARCHAR(255);
DECLARE v_country VARCHAR(255);
DECLARE c1 CURSOR FOR SELECT id, label, date, type, category, user, country FROM t1 LEFT JOIN ... /* whole select with 6 joins ended by ; */
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
## open cursor
OPEN c1;
## loop through the cursor
read_loop: LOOP
## fetch cursor into variables
FETCH c1 INTO v_id , v_label, v_date, v_type, v_category, v_user, v_country;
## check if there is any record
IF done THEN
LEAVE read_loop;
END IF;
## get count of existing records
SELECT count(*) INTO v_id_dupl
FROM
WHERE 1=1
AND id = v_id
AND label= v_label
AND date = v_date;
## if v_id_dupl = 0 => no rows found (ok to load)
IF (v_id_dupl = 0) THEN
INSERT INTO target_table (id, label, date, type, category, user, country)
VALUES (v_id , v_label, v_date, v_type, v_category, v_user, v_country);
END IF;
END LOOP;
CLOSE c1;
END
If that is all your stored procedure is doing, then you don't actually need it. You can do the whole thing in a single statement:
INSERT INTO controlled_table (id, type)
SELECT t.c1, t.c2
FROM table t1
LEFT JOIN other_table t2 ON t1.id = t2.id
WHERE something = somethingElse
AND t.c1 > 1
Essentially, I've just combined your original query with the INSERT statement in your procedure.
If your procedure is more complex and needs to do multiple operations on each row, then you should look into using a cursor.

How to reduce time to process data in procedure of mysql

Thank you forum...please help me..
I have table which contain TagName and other table contains taglog .im passing tag names to procedure called GetAvg which will return avg of all tags.it works well, but it takes about 35 sec to show 100 tag values.how to reduce time .please help me im new in database.
DELIMITER $$
CREATE DEFINER=`root`#`localhost` PROCEDURE `GetAvg`(IN FromTime datetime, IN ToTime datetime)
BEGIN
DECLARE no_more_alarms INT DEFAULT 0;
DECLARE TempTagName VARCHAR(45);
DECLARE val FLOAT;
DECLARE cur_tag CURSOR FOR
select Tag_AVG from Report
where(Tag_AVG IS NOT NULL );
DECLARE CONTINUE HANDLER FOR NOT FOUND
SET no_more_alarms = 1;
DROP TABLE IF EXISTS `tempAVG`;
CREATE TABLE tempAVG (
val FLOAT
);
OPEN cur_tag;
FETCH cur_tag INTO TempTagName;
REPEAT
SELECT AVG(value) INTO val
FROM jas_taglog
WHERE ((TagId = (select TagId from jas_tags where jas_tags.Name = TempTagName)) AND jas_taglog.LogTime between FromTime and ToTime) ;
INSERT INTO tempAvg(Val)
VALUES (val);
FETCH cur_tag INTO TempTagName;
UNTIL no_more_alarms = 1
END REPEAT;
CLOSE cur_tag;
SELECT * FROM tempAVG;
END
You are manually implementing loops that could be written in a single query:
...
BEGIN
INSERT INTO tempAvg
SELECT AVG(jas_taglog.value)
FROM jas_taglog
JOIN jas_tags USING (TagId)
JOIN Report ON jas_tags.Name = Tag_AVG
WHERE jas_taglog.LogTime BETWEEN FromTime AND ToTime
GROUP BY Tag_AVG;
END

Stored Procedure taking ages to execute?

DELIMITER $$
CREATE PROCEDURE Load_Fact_List()
BEGIN
DECLARE Project_Number_Temp INT;
DECLARE Panel_Id_Temp INT;
DECLARE Employee_Id_Temp INT;
DECLARE Zip_Temp VARCHAR(255);
DECLARE Created_Date_Temp DATE;
DECLARE Country_Temp VARCHAR(255);
DECLARE no_more_rows BOOLEAN;
DECLARE loop_cntr INT DEFAULT 0;
DECLARE num_rows INT DEFAULT 0;
DECLARE load_cur CURSOR FOR
SELECT Project_Id, Panel_Id, Employee_Id, Zip, Created_Date
FROM Fact_List;
DECLARE CONTINUE HANDLER FOR NOT FOUND
SET no_more_rows = TRUE;
OPEN load_cur;
select FOUND_ROWS() into num_rows;
the_loop: LOOP
FETCH load_cur
INTO Project_Number_Temp, Panel_Id_Temp, Employee_Id_Temp, Zip_Temp, Created_Date_Temp;
IF no_more_rows THEN
CLOSE load_cur;
LEAVE the_loop;
END IF;
SET Country_Temp= (select Country from Zip where Zip= Zip_Temp);
INSERT INTO Test_Fact
(
Project_Key,
Campaign_Key,
Respondents_Key,
Event_Key,
Employee_Key,
Geography_Key,
Date_Key
)
SELECT (SELECT Project_Key from Project_Dim where Project_Id= Project_Number_Temp AND Quota_Country= Country_Temp),0,(SELECT MAX(Respondents_Key) from Respondents_Dim WHERE Panel_Id= Panel_Id_Temp),1,(select MAX(Employee_Key) from Employee_Dim WHERE Employee_Id= Employee_Id_Temp),(Select Geography_Key from Geography_Dim where Zip= Zip_Temp), (Select Date_Key from Date_Dim where Full_Date= Created_Date_Temp);
SET loop_cntr = loop_cntr + 1;
END LOOP the_loop;
select num_rows, loop_cntr;
END $$
The above code is properly working but it is damn slow. For every 1 hour it is loading 1000 records. I got lacks of records to load into fact table. can anyone suggest me any optimization?
Requirement is to load fact table by looping through other table and gathering required key values from dimension tables.
The usual procedure is actually like this.
You have your dimensions built and you just gathered the data you want to insert into your fact table in a temporary table. Then you insert this data in another temporary table like this:
INSERT INTO tmp_fact_table
(
fact_key,
dim1_key,
dim2_key,
...
fact1,
fact2
...
)
SELECT
ISNULL (f.fact_key, 0),
ISNULL (d1.sid, 0) as whatever,
ISNULL (d2.sid, 0) as whatever2,
...
ISNULL (tt.fact1, 0),
ISNULL (tt.fact2, 0)
FROM
yourTempTable tt
LEFT JOIN Dim1 d1 ON tt.identifying_column = d1.identifying_column
...
LEFT JOIN fact_table f ON
f.dim1_key = d1.sid
AND f.dim2_key = d2.sid
where
fact_key is the identifying column in your fact table
dim1_key is the foreign key in your fact table to the dimensions
fact1 and so on are the facts you want in your fact table, clear
the ISNULL() function returns 0 when no entry is found. 0 is the id of your dummy row in each dimension for unknown data
Then you will have a table where you have the IDs of your dimensions linked to the data you want to import into your fact table with 0 as fact key when the entry in the fact table does not already exist and the ID of the fact table entry otherwise.
Then you update the fact table where tmp_fact_table.fact_key != 0
Then you insert into the fact table where tmp_fact_table.fact_key = 0
That's it.
I'm doing this with millions of rows and it takes about half an hour. 300,000 rows is peanuts.

Temporary table definition in MySQL

I have a stored procedure which uses temporary tables so that I can summarize the sales of all the products within a certain product category. When I tried to run the code it failed. I search on google and here on stackoverflow but couldn't find what I had done wrong. I'm using MySQL server 5.5 on Windows Server.
CREATE PROCEDURE `getStatistics`(IN `startDate` date,IN `endDate` date,IN `categoryName` varchar)
BEGIN
CREATE TEMPORARY TABLE procResult(productName VARCHAR, amount INT);
CREATE TEMPORARY TABLE tblProductID(SELECT ID, `name` FROM product WHERE categoryID = (SELECT ID FROM category WHERE `name` = categoryName));
DECLARE done_amt, done_PID INT DEFAULT FALSE;
DECLARE amount, productID INT DEFAULT 0;
DECLARE pidCursor CURSOR FOR SELECT ID, `name` FROM tblProductID;
DECLARE amtCursor CURSOR FOR SELECT orderlines.amount FROM orderlines WHERE orderlines.productID = productID;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done_amt = TRUE;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done_PID = TRUE;
OPEN pidCursor;
pid_loop:LOOP
DECLARE productName VARCHAR;
FETCH pidCursor INTO productID, productName;
IF done_PID THEN
LEAVE pid_LOOP;
END IF;
OPEN amtCursor;
amt_loop:LOOP
DECLARE tmpAmount INT DEFAULT 0;
FETCH amtCursor INTO tmpAmount;
IF done_amt THEN
LEAVE amt_loop;
END IF;
amount = amount + tmpAmount;
END LOOP;
CLOSE amtCursor;
IF amount > 0 THEN
INSERT INTO procResult VALUES (productName, amount);
amount = 0;
END IF;
END LOOP;
CLOSE pidCursor;
END;
You must define the length of VARCHAR type variables, such as the categoryName parameter to your stored procedure;
You must DECLARE all local variables at the very start of a BEGIN ... END compound statement block, before any other commands;
Your syntax for CREATE TABLE ... SELECT is incorrect;
You have declared two handlers for the same SQL condition, only one of which will be executed (indeterminately);
You will need to change your client's statement delimiter in order for it to understand that the semicolons appearing within the procedure body do not terminate the CREATE PROCEDURE statement;
Your entire procedure is an extremely complicated way of doing a fairly simple task in SQL:
CREATE TEMPORARY TABLE procResult
SELECT product.name, SUM(orderlines.amount) AS amount
FROM orderlines
JOIN product ON product.ID = orderlines.productID
JOIN category ON category.ID = product.categoryID
WHERE category.name = ?
GROUP BY product.ID
HAVING amount > 0