MySQL syntax error when using prepared statement with CONCAT - mysql

I'm trying to use a prepared statement within a stored procedure. I'm getting the syntax error in the initial SET #idToUpdateQuery. I already know that the CONCAT without the variable assignment using := works correctly, as it's used elsewhere and works as intended. Essentially, I just need to be able to get the result of the prepared SELECT statement into the variable #resultId. The problematic code is as follows:
SET #distinctTableXIds = (SELECT COUNT(*) FROM tempTableX);
SET #i = 0;
WHILE #i < #distinctTableXIds DO
/*CONCAT IS NEEDED HERE TO PASS USER DEFINED #i*/
SET #idToUpdateQuery = CONCAT('SELECT #result := MAX(id)
FROM tableY
WHERE tableZId = (SELECT id FROM tempTableX ORDER BY id LIMIT ', #i, ', 1)');
PREPARE #IdToUpdateStmt FROM #IdToUpdateQuery;
EXECUTE #IdToUpdateStmt;
SET #resultId = SELECT #result;
UPDATE tableY
SET someBoolean = 1
WHERE id = #resultId;
SET #i = #i + 1;
END WHILE;

You don't use the # sigil for a statement name. It's not a user variable.
See example at https://dev.mysql.com/doc/refman/8.0/en/sql-prepared-statements.html
mysql> PREPARE stmt1 FROM 'SELECT SQRT(POW(?,2) + POW(?,2)) AS hypotenuse';
mysql> SET #a = 3;
mysql> SET #b = 4;
mysql> EXECUTE stmt1 USING #a, #b;
Besides that, I don't think you need a prepared statement for this task at all. I can think of at least two other solutions. Give me a few minutes and I'll write one up.
One solution using a cursor (see https://dev.mysql.com/doc/refman/8.0/en/cursors.html):
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE max_id INT;
DECLARE cur1 CURSOR FOR SELECT MAX(id) FROM tableY JOIN tempTableX ON (tableY.tableZId = temptableX.id) GROUP BY tableZId;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cur1;
read_loop: LOOP
FETCH cur1 INTO max_id
IF done THEN
LEAVE read_loop;
END IF;
UPDATE tableY SET someBoolean = 1 WHERE id = max_id;
END LOOP;
CLOSE cur2;
END
Another solution that does it all in one UPDATE statement:
UPDATE tableY AS y1
INNER JOIN tempTableX AS x ON (y1.tableZId = x.id)
LEFT OUTER JOIN tableY AS y2 ON (y1.tableZId = y2.tableZId AND y1.id < y2.id)
SET y1.someBoolean = 1
WHERE y2.id IS NULL;
The outer join trick is a way to find the row of y1 for which no row exists with the same tableZId and a greater id. In other words, it finds the largest id for each group of rows with a given tableZId.

Related

How to increment using a SQL variable within an update statement

I am trying to increment a column using an #count variable in SQL. I have tried multiple attempts that I will list below that all result in:
Error Code: 1064
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ...
First was:
SET #count = 65;
UPDATE table t
SET t.Revision = CHAR(#count)
, #count = #count + 1
WHERE t.hidden = 0;
I am trying to increment every row currently as a proof of concept that this works.
Second was:
DECLARE t CURSOR FOR
SELECT * FROM table
WHERE t.hidden = 0;
OPEN t;
FETCH NEXT FROM t;
WHILE ##FETCH_STATUS = 0
BEGIN
UPDATE table t2 SET t2.Revision = 'D' WHERE t2.id1 = t.id1 AND t2.id2 = t.id2;
END;
END
CLOSE t;
DEALLOCATE t;
Once again I am just trying to see if I can set a standard variable using a while loop before I implement incrementing as a proof of concept that it works.
I am not sure why either of these attempts is failing but any help would be appreciated.
Here is how your first example should work(inside of some loop):
first you set your count value, then you update
SET #count = 65;
UPDATE CUSTOMER t
SET t.LName = CONVERT(#count, char)
where t.FName = 'a';
...and then increase that count and you update again...
set #count = #count + 1;
UPDATE CUSTOMER t
SET t.LName = CONVERT(#count, char)
where t.FName = 'a';
But that should be in a procedure for example.
Here is the DEMO. I it a small example and I hope you will find it helpful.
Cheers!
You can try the following solution:
SET #count = 64; -- so the first increment is 65 (starting on A).
UPDATE table_name t
SET t.Revision = CHAR(#count:=#count+1)
WHERE t.hidden = 0;
or (shorter):
UPDATE table_name t, (SELECT #count:=64) t2
SET t.Revision = CHAR(#count:=#count+1)
WHERE t.Hidden = 0;
demo on dbfiddle.uk

Mysql while with variable dynamic

I need to dynamically retrieve the variable in my while, but do not know if I'm using the correct way, in the code below I need to retrieve the table name to make the inclusion, but returns an error. This test is only an example, actually I will use this procedure to create a Trigger for the tables.
Ex:
DECLARE i INT DEFAULT 0;
DECLARE tb_name VARCHAR(8000);
SET #db_name = "test_ph";
SET #qtd_tables = (SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = #db_name);
WHILE i < #qtd_tables DO
SET tb_name = (SELECT table_name FROM information_schema.tables WHERE table_schema = #db_name LIMIT i,1);
INSERT INTO tb_name(name, email) VALUES('test', 'test#test.com');
SET i = i + 1;
END WHILE;

How to change my sql to loop in MySql stored procedure in mysql?

When I write my sql in each statement,it works well.Now my sql is:
SELECT COUNT(*) INTO v_Point1Num from tbpoint where Point=1;
SELECT COUNT(*) INTO v_Point2Num from tbpoint where Point=2;
SELECT COUNT(*) INTO v_Point3Num from tbpoint where Point=3;
SELECT COUNT(*) INTO v_Point4Num from tbpoint where Point=4;
SELECT COUNT(*) INTO v_Point5Num from tbpoint where Point=5;
and it work well.
Now I try to change it to loop,but it is wrong,how to fix it?I wonder it is the reason that I do not use "#" like "#v".
--can not work.
CREATE `SP_Point`()
BEGIN
DECLARE v INT DEFAULT(0);
DECLARE pointlStr VARCHAR(800);
SET v = 1;
WHILE v <= 5 DO
SELECT COUNT(*) INTO
(case v
when 1 then concat('v_Point',v,'Num')
when 2 then concat('v_Point',v,'Num')
when 3 then concat('v_Point',v,'Num')
when 4 then concat('v_Point',v,'Num')
when 5 then concat('v_Point',v,'Num')
)
from tbpoint
where Point=v;
SET v = v + 1;
END WHILE;
END
I try to change it to the other way,but it is still wrong.
SET v = 1;
WHILE v <= 5 DO
set pointlStr=
'SELECT COUNT(*) INTO #v_Point'+#v+'Num from tbpoint
where Point='+#v;
prepare stmt from #pointlStr;
execute stmt;
SET v = v + 1;
END WHILE;
You are trying to create a new variables(v_Point1Num, v_Point2Num.... etc) at run time which is not possible into mysql. you must declare a variable before using it.
You can achieve the same output by running single query as well... rather then running multiple queries
SELECT Point, COUNT(*) from tbpoint
group by Point
having point > 0 and point <= 5;
Concat() function return the varchar/String not the variable name. declare only one variable "v_pointNum"... fetch the value into variable inside loop.... and in the same loop update the other table as well. –
CREATE `SP_Point`()
BEGIN
DECLARE v INT DEFAULT(0);
-- declare a variable to hold count value
DECLARE v_pointNum INT DEFAULT(0);
DECLARE serviceAttitudeLevelStr VARCHAR(800);
SET v = 1;
WHILE v <= 5 DO
SELECT COUNT(*) INTO v_pointNum from tbpoint where Point=v;
-- update another table
update <mytable> set <mycol> = v_pointNum where <condition>;
SET v = v + 1;
END WHILE;
END

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).

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;
...