Hidden syntax errors in MYSQL procedure - mysql

I'm new to MYSQL and this is my first time creating a procedure so bear with me. I'm trying to loop through information_scheme.tables and populate a new table with info taken from some of the tables found there. I cannot save the procedure in workbench because of 'syntax errors'. I cannot figure out what those errors are however.
CREATE PROCEDURE `populate` ()
BEGIN
DECLARE thisDay varchar(100);
DECLARE i int;
DECLARE n int;
DECLARE cur CURSOR FOR SELECT table_name FROM (information_schema.tables);
SET i=0;
SELECT COUNT(table_name) FROM information_schema.tables INTO n;
OPEN cur;
getDay: LOOP
IF cur LIKE 'transactions_20%' THEN
FETCH cur INTO thisDay;
INSERT INTO days_totals
(date,num_of_ppl,punches,admission_total,pass_total,misc_total,food_total,drink_total,grand_total)
VALUES(
(SELECT SUBSTRING(thisDay,14,23)),
(SELECT COUNT(amount) FROM thisDay WHERE name = 'adult admission' OR name = 'punch a pass' OR name = 'child admission'),
(SELECT COUNT(amount) FROM thisDay WHERE name = 'punch a pass'),
(SELECT SUM(total) FROM thisDay WHERE name = 'adult admission' OR name = 'child admission'),
(SELECT SUM(total) FROM thisDay WHERE name = 'ten visit pass'),
(SELECT SUM(total) FROM thisDay WHERE type = 'misc' AND name != 'adult admission' AND name != 'child admission' AND name != 'ten visit pass'),
(SELECT SUM(total) FROM thisDay WHERE type = 'food'),
(SELECT SUM(total) FROM thisDay WHERE type = 'drink'),
(SELECT SUM(total) FROM thisDay WHERE type = 'misc' OR type = 'food' OR type = 'drink')
);
SET i = i + 1;
ELSE IF
i < n AND cur NOT LIKE 'transactions_20%' THEN SET i = i + 1;
ELSE
LEAVE getDay;
END IF;
END LOOP getDay;
CLOSE cur;
END

There are numerous problems with your procedure code.
You need to set the DELIMITER, even when using MySQL Workbench. See https://dev.mysql.com/doc/refman/8.0/en/stored-programs-defining.html
You can't use a variable in place of the table name. To make the table name dynamic, you must format the table name into an SQL query string, and then use PREPARE. See https://dev.mysql.com/doc/refman/8.0/en/sql-prepared-statements.html
You are comparing the cursor to a string, but you should compare the value fetched by the cursor.
You don't need a condition anyway, because you should just use a WHERE clause in your query against information_schema.tables.
Your cursor has no way of continuing. You don't declare a continue handler, and you just LEAVE the loop during the first iteration. Read examples more carefully: https://dev.mysql.com/doc/refman/8.0/en/cursors.html
Your i and n variables appear to have no purpose. You count up, but the value you count is not used.
I made the following version and tested it on MySQL 8.0. I used MySQL Workbench to create the procedure.
DELIMITER $$
CREATE PROCEDURE test.`populate`()
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE thisDay varchar(100);
DECLARE cur CURSOR FOR SELECT table_name FROM information_schema.tables WHERE table_name LIKE 'transactions_20%';
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cur;
getDay: LOOP
FETCH cur INTO thisDay;
IF done THEN
LEAVE getDay;
END IF;
SET #sql = CONCAT(
'SELECT
COUNT(CASE WHEN name IN (''adult admission'', ''punch a pass'', ''child admission'') THEN amount END),
COUNT(CASE WHEN name IN (''punch a pass'') THEN amount END),
SUM(CASE WHEN name IN (''adult admission'', ''child admission'') THEN total END),
SUM(CASE WHEN name IN (''ten visit pass'') THEN total END),
SUM(CASE WHEN name IN (''ten visit pass'') THEN total END),
SUM(CASE WHEN type = ''misc'' AND name NOT IN (''adult admission'', ''child admission'', ''ten visit pass'') THEN total END),
SUM(CASE WHEN type = ''food'' THEN total END),
SUM(CASE WHEN type IN (''misc'', ''food'', ''drink'') THEN total END)
FROM `', thisDay, '`
INTO #v_num_of_ppl, #v_punches, #v_admission_total, #v_pass_total, #v_misc_total, #v_food_total, #v_drink_total, #v_grand_total'
);
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
INSERT INTO days_totals
SET date = SUBSTRING(thisDay, 14, 23),
num_of_ppl=COALESCE(#v_num_of_ppl, 0),
punches=COALESCE(#v_punches, 0),
admission_total=COALESCE(#v_admission_total, 0),
pass_total=COALESCE(#v_pass_total, 0),
misc_total=COALESCE(#v_misc_total, 0),
food_total=COALESCE(#v_food_total, 0),
drink_total=COALESCE(#v_drink_total, 0),
grand_total=COALESCE(#v_grand_total, 0);
END LOOP;
CLOSE cur;
END
By using CASE expressions inside the aggregation, this collects all the subtotals in one pass of reading the transactions table.
Frankly, I would not use a stored procedure at all. This task would be easier in virtually any other application programming language. There is no advantage to using a stored procedure in this case, and it's a language you are not accustomed to.

Related

mysql. Stored Procedure Error 1328

This is really baffling me. I am converting a simple procedure from informix into mysql. What it basically does is tell me what the next event is from an event table and a calendar table. In informix the procedure is simple.
FOREACH
SELECT date,weekno,event
INTO l_date,l_week,l_event
FROM event,calendar
WHERE dayno = dayno
AND date = l_today
AND start >= l_now
UNION
SELECT date,weekno,event
FROM event,calendar
WHERE dayno = dayno
AND date > l_today
UNION
SELECT TODAY,9999,9999
FROM event,calendar
WHERE dayno = dayno
AND event = (SELECT MAX(event) FROM event)
ORDER BY 3
if l_event = 9999 then <error> end if;
EXIT FOREACH
END FOREACH
So basically the query finds the next event and returns it. l_today and l_event are parameters that are passed. So on to the mysql version.
looper: BEGIN
DECLARE curs1 CURSOR FOR
SELECT CONCAT("SELECT date, weekno, event FROM event INNER JOIN calendar ON dayno = dayno",
" WHERE date = '", lv_today ,"' AND start >= '", lv_time ,"'",
" UNION SELECT date, weekno, event FROM event INNER JOIN calendar ON dayno = dayno WHERE date > '", lv_today ,"'",
" UNION SELECT DATE(NOW()) AS date, 9999 AS weekno, 9999 AS event FROM event INNER JOIN calendar ON dayno = dayno",
" WHERE (SELECT MAX(event) FROM event) ORDER BY event ");
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done := TRUE;
OPEN curs1;
loop1: LOOP
FETCH curs1 INTO ldate, lweek, levent;
SELECT ldate, lweek, levent;
LEAVE looper;
END LOOP loop1;
END;
I haven't checked that the rest of the methodology works because I get this error:
Incorrect number of FETCH variables.
Does this mean that I have declare a different variable for each of the query returns? I am new to mysql. If this is the case what would be the best way to solve this conundrum? I have also changed to column and table names.
Many thanks
looper: BEGIN
DECLARE curs1 CURSOR FOR
SELECT eve_date, dia_weekno, eve_event
FROM game_event
INNER JOIN stan_calendar ON eve_abs_dayno = dia_abs_dayno
WHERE eve_date = lv_today
AND eve_start >= lv_time
UNION
SELECT eve_date, dia_weekno, eve_event
FROM game_event
INNER JOIN stan_calendar ON eve_abs_dayno = dia_abs_dayno
WHERE eve_date > lv_today
UNION SELECT DATE(NOW()) AS eve_date, 9999 AS dia_weekno, 9999 AS eve_event
FROM game_event
INNER JOIN stan_calendar ON eve_abs_dayno = dia_abs_dayno
WHERE (SELECT MAX(eve_event) FROM game_event) ORDER BY eve_event;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done := TRUE;
OPEN curs1;
curs_loop: LOOP
FETCH curs1 INTO lv_date, lv_week, lv_event;
SELECT lv_date, lv_week, lv_event;
LEAVE looper;
CLOSE curs1;
END LOOP curs_loop;
Thank you for answering my question, I have put it back to how I thought it was... and it now works. Here is the loop in full.
https://dev.mysql.com/doc/refman/5.7/en/fetch.html The number of columns retrieved by the SELECT statement must match the number of output variables specified in the FETCH statement so yes but in fact you are only selecting 1 concatenated string - I think your first problem is with the select syntax which doesn't need the concat, the brackets or the quotes (unless you are trying to create a prepared statement for some reason - and even if you were I doubt if the code would be correct)
Simple Cursor
DROP PROCEDURE IF EXISTS EC;
DELIMITER $$
CREATE PROCEDURE `EC`(
IN `inemp_no` varchar(255)
)
LANGUAGE SQL
NOT DETERMINISTIC
CONTAINS SQL
SQL SECURITY DEFINER
COMMENT ''
LOOPER:begin
DECLARE done INT DEFAULT FALSE;
declare ename varchar(20);
declare esalary int default 0;
declare emp_cursor CURSOR FOR
SELECT last_name,salary FROM employees where emp_no= inemp_no ;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
open emp_cursor;
read_loop: loop
fetch emp_cursor into ename,esalary;
if done then leave read_loop; end if;
insert into debug_table (msg) values(concat('employee:',ename,' earns:',esalary));
end loop;
close emp_cursor;
end $$
DELIMITER ;
MariaDB [sandbox]> truncate table debug_table;
Query OK, 0 rows affected (0.22 sec)
MariaDB [sandbox]> call ec(2);
Query OK, 0 rows affected (0.03 sec)
MariaDB [sandbox]> select * from debug_table;
+----+--------------------------+------+
| id | msg | MSG2 |
+----+--------------------------+------+
| 1 | employee:BBB earns:39500 | NULL |
+----+--------------------------+------+
1 row in set (0.00 sec)

Convert SQL Server Cursor To MySQL

How to convert a SQL Server cursor to MySQL?
ALTER function [dbo].[Terlambat](#proses int) returns #tempKK table (krk int,terlambat int) as
begin
declare #uid int, #total_hari_kerja int
select #total_hari_kerja = SUM(alokasi) from proses
declare krk_uid cursor for select distinct krk from kartu_kendali where proses <= 8
open krk_uid
fetch next from krk_uid into #uid
while (##FETCH_STATUS = 0)
begin
insert into #tempKK select top 1 krk, dbo.GetTerlambat(realisasi_tgl_terima, #total_hari_kerja) terlambat from kartu_kendali where proses = 1 and krk = #uid order by RecID
fetch next from krk_uid into #uid
end
close krk_uid
deallocate krk_uid
return
end
For changing a cursor to MySQL that has no cursor you should follow this solution:
Add a Row Number to your SELECT statement that help you to fetch data from your SELECT over it.
Now, you can use a variable that starts with 1 and grows inside the WHILE in each step by 1 and filter your SELECT statement over Row Number and this variable.

Check MySQL database for unique value over many tables

I'm looking for a way to easily check each table of a MySQL database and make sure that a certain field contains one value only. I have tables named Authors, Titles, Places, etc.
Each table contains a field called xuser and it needs to ask "does the field xuser contain the value xy in all records of all tables".
Can someone push me in the right direction how to do this with a SQL query if this is possible?
Thanks for reading, regards
Nico
I've created stored procedure which checks all table for provided db:
DELIMITER $$
DROP PROCEDURE IF EXISTS `UTL_CHECK_BACKUP_FOR_USER` $$
CREATE PROCEDURE `UTL_CHECK_BACKUP_FOR_USER`(
IN i_database_name VARCHAR(255),
IN i_user_column_name VARCHAR(255),
IN i_user_column_value VARCHAR(255),
OUT o_result TINYINT(1)
)
BEGIN
DECLARE v_table_name VARCHAR(255);
DECLARE v_last_row_fetched TINYINT(3) DEFAULT 0;
DECLARE tables_cursor CURSOR FOR
SELECT table_name
FROM information_schema.tables
WHERE table_schema = i_database_name
;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_last_row_fetched = 1;
SET v_last_row_fetched = 0;
OPEN tables_cursor;
SET #query =
CONCAT(
'SELECT SUM(IF(user_column=''',
i_user_column_value,
''', 1, -1)) = 1 INTO #o_result FROM ( SELECT ''test'' AS user_column FROM information_schema.tables WHERE 1<>1 '
)
;
table_loop: LOOP
FETCH tables_cursor INTO v_table_name;
IF (v_last_row_fetched = 1) THEN
LEAVE table_loop;
END IF;
SET #query =
CONCAT(
#query,
' UNION SELECT DISTINCT(',
i_user_column_name,
') AS user_column FROM ',
v_table_name
)
;
END LOOP table_loop;
CLOSE tables_cursor;
SET v_last_row_fetched=0;
SET #query =
CONCAT(
#query,
' ) all_xusers;'
)
;
PREPARE stmt FROM #query;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET o_result = COALESCE(#o_result, 0);
END $$
DELIMITER ;
Just deploy this stored procedure to database.
And then it could be executed in the following way:
-- db_name, user_column_name, user_column_value, result
call UTL_CHECK_BACKUP_FOR_USER('test', 'xuser', 'xxx', #result);
select #result;
To get the rows from all three tables where xuser has the same value in all three tables you could use:
SELECT *
FROM authors a
JOIN titles t
ON t.xuser = a.xuser
JOIN places p
ON p.xuser = t.xuser
If you want to look at a specific xuser value you could add the following WHERE clause:
WHERE a.xuser = 'xy'
The first thing comes to my mind:
select sum(if(xuser='xxx', 1, -1)) = 1
from (
select distinct(xuser) from authors
union
select distinct(xuser) from titles
union
select distinct(xuser) from places
) all_xusers;
This will return 1 (true) if all tables contains records belonging ONLY to 'xxx' user. Otherwise (if there is no 'xxx' records or there is some other user records) it will return 0 (false).

No data return after calling the stored procedures with multiple cursors in it

I have a stored procedures with the following code. The reason i use cursor is to join table which something will return NULL value and cause the record to be disappear. By using this method, I am able to get all data without losing any.
The only problem now is that when i try to call the stored precedures, it return
Error Code : 1329
No data - zero rows fetched, selected, or processed
but when i do a manual select * from TMOMain, the table is created and there is data in it but no data from SignUpCur and UnSubCur mean it was not updated.
1st time using mysql stored procedures so there might be something i miss out.
My Code
ROOT:BEGIN
DECLARE pTotal,pShortCode,pSignUp,pUnSub,pJunk,pT INT;
DECLARE pTc NVARCHAR(10);
DECLARE SignTotal,UnSubTotal, JunkTotal INT;
DECLARE pSignTotal,pSignTeamID,pUnSubTotal,pUnSubT,pSignUpS,pUnSubS INT;
DECLARE pSignTeam,pUnSubTeam NVARCHAR(10);
DECLARE no_more_rows BOOLEAN;
DECLARE MoMainCur CURSOR FOR
SELECT COUNT(*) AS GrandTotal,pShort,(CASE WHEN r= 1 THEN 'A'
WHEN r= 2 THEN 'B' WHEN r= 3 THEN 'C' ELSE 'UV' END) AS Team,recvTeamID
FROM tbli
INNER JOIN tblK ON keywordid = rkey
WHERE recvDate >='2011-11-15' AND recvDate < '2011-11-16'
GROUP BY pShort,Team,recvTeamID;
DECLARE SignUpCur CURSOR FOR
SELECT COUNT(*) AS SignUp,(CASE WHEN r= 1 THEN 'A'
WHEN r= 2 THEN 'B' WHEN r= 3 THEN 'C' ELSE 'UV' END) AS Team,
recvTeamID,pShort
FROM tbli INNER JOIN tbl_user ON recvphone = userphone
INNER JOIN tblK ON keywordid = userpublicstatus
WHERE userdatejoined >='2011-11-15' AND userdatejoined < '2011-11-16'
AND recvdate >='2011-11-15' AND recvdate < '2011-11-16'
GROUP BY Team,recvTeamID,pShort;
DECLARE UnSubCur CURSOR FOR
SELECT COUNT(*) AS UnSub,(CASE WHEN r= 1 THEN 'A'
WHEN r= 2 THEN 'B' WHEN r= 3 THEN 'C' ELSE 'UV' END) AS Team,
recvTeamID,pShort
FROM tbliINNER JOIN tbl_user ON recvphone = userphone
INNER JOIN tblK ON keywordid = userpublicstatus
WHERE userdateExpire >='2011-11-15' AND userdateExpire <'2011-11-16'
AND recvdate >='2011-11-15' AND recvdate < '2011-11-16'
GROUP BY Team,recvTeamID,pShort;
DROP TABLE IF EXISTS `TMoMain`;
CREATE TEMPORARY TABLE TMOMain
(GrandTotal INT,ShortCode INT,Team NVARCHAR(10),SignUp INT,UnSub INT, Junk INT, TeamID INT);
OPEN MoMainCur;
-- Main Table
read_loop:LOOP
FETCH MoMainCur INTO pTotal,pShortCode,pTc,pT;
INSERT INTO TMOMain
VALUES
(pTotal,pShortcode,pTc,0,0,0,pT);
END LOOP read_loop;
CLOSE MoMainCur;
-- Insert Signup Details into Main Table
OPEN SignUpCur;
SignUp_Loop:LOOP
FETCH SignUpCur INTO pSignTotal,pSignTeam,pSignTeamID,pSignUpS;
UPDATE TMOMain
SET SignUp = pSignTotal
WHERE Team = pSignTeam AND Shortcode =pSignUpS;
END LOOP SignUp_Loop;
CLOSE SignUpCur;
-- Insert UnSub Details into Main Table
OPEN UnSubCur;
UnSub_Loop:LOOP
FETCH UnSubCur INTO pUnSubTotal,pUnSubTeam,pUnSubT,pUnSubS;
UPDATE TMOMain
SET UnSub = pSignTotal
WHERE Team = pUnSubTeam AND pShort = pUnSubShortCode;
END LOOP UnSub_Loop;
CLOSE UnSubCur;
SELECT * FROM TMOMain;
END$$
Please try this out:
Add this declaration once (at the top):
DECLARE curIsDone INT DEFAULT FALSE;
Then after you declare your cursor add this:
DECLARE CONTINUE HANDLER FOR NOT FOUND SET curIsDone = TRUE;
After your FETCH commands and before the actions you intend to perform:
IF curIsDone THEN
LEAVE read_loop;
END IF;

MySQL Stored Procedures : cursor declaration

Sorry for the vague title, here is my problem. I have stored procedures for DB2 that i try to convert for MySQL. I'd like to know if i can write the SELECT statement in the cursor declaration as a string variable. For example with DB2 i have this :
(...)
-- Declare cursors
DECLARE c_very_init CURSOR WITH RETURN FOR s_very_init;
DECLARE c_date CURSOR WITH RETURN FOR s_date;
DECLARE CONTINUE HANDLER FOR not_found
SET at_end = 1;
-- In case the_date is 0, retrieve the first date
IF the_date = 0 THEN
SET sql_end_date = '
SELECT DATE
FROM ACCOUNTS
WHERE REF = ''' || the_ref || '''
ORDER BY ID ASC FETCH FIRST 1 ROWS ONLY';
PREPARE s_date FROM sql_end_date;
OPEN c_date;
FETCH FROM c_date INTO data_ins;
SET the_last_date = data_ins;
CLOSE c_date;
ELSE
SET the_last_date = the_date;
END IF;
-- Get the 'very' initial value
SET sql_very_init = '
SELECT in, out
FROM MOVEMENTS
WHERE REF = ''' || the_ref || '''
AND DATE < ' || the_last_date;
PREPARE s_very_init FROM sql_very_init;
OPEN c_very_init;
FETCH FROM c_very_init INTO dare, avere;
-- Loop through the results
(...)
I declare a c_very_init cursor, but at the time of the cursor declaration in the SP i still don't know the full select statement because i need to fetch (if necessary) the the_last_date value. It seems i can't do this :
DECLARE c_very_init CURSOR WITH RETURN FOR s_very_init;
with MySQL, the syntax being with the statement directly in the declaration :
DECLARE c_very_init CURSOR FOR SELECT blaablaa...;
Am i wrong?
Thank you.
fabien.
No, you cannot declare cursors in this way. But if 'the_ref' is a variable, you could do it like this -
...
DECLARE the_ref INT DEFAULT 10;
DECLARE cur1 CURSOR FOR SELECT column1 FROM table1 WHERE column1 = the_ref;
...