MySQL Pass table name to cursor select - mysql

I want the procedure to take parameter answertable and partid in the select statement,
but when i call it it doesn't replace the parameter answertable with the value
the call call updateTotalScores('quiz_participation', 'quiz_answer', 1)
returns the error: 1146 - Table 'quizdb.answertable' doesn't exist
passing the id works, but passing the table name doesn't
so how do i pass the table name to the select in
DECLARE cur1 CURSOR FOR SELECT SUM(`score`), SUM(`maxscore`) FROM answertable WHERE `idParticipation`=partid;
entire procedure:
DELIMITER $$
CREATE PROCEDURE updateTotalScores(IN participationtable CHAR(64), IN answertable CHAR(64), IN partid INT)
BEGIN
DECLARE done INTEGER DEFAULT 0;
DECLARE sscore INTEGER DEFAULT 0;
DECLARE smaxscore INTEGER DEFAULT 0;
DECLARE cur1 CURSOR FOR SELECT SUM(`score`), SUM(`maxscore`) FROM answertable WHERE `idParticipation`=partid;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
OPEN cur1;
REPEAT
FETCH cur1 INTO sscore, smaxscore;
UNTIL done = 1
END REPEAT;
CLOSE cur1;
UPDATE participationtable SET `score`=sscore, `maxscore`=smaxscore WHERE `idParticipation`=partid;
END $$
DELIMITER ;
For completeness
the table name cannot be passed to a MySql cursor, at least not yet
http://forge.mysql.com/worklog/task.php?id=3433
the answer from below (corrected a bit)
DELIMITER $$
CREATE PROCEDURE updateTotalScores(IN participation_table VARCHAR(45), IN answer_table VARCHAR(45), IN part_id INT)
BEGIN
SET #stmt_text=CONCAT("SELECT #score := SUM(`score`), #maxscore := SUM(`maxscore`) FROM ",
answer_table, " WHERE `idParticipation`=", part_id);
PREPARE stmt FROM #stmt_text;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET #stmt_text=CONCAT("UPDATE ", participation_table,
" SET `score`=?, `maxscore`=? WHERE `idParticipation`=", part_id);
PREPARE stmt FROM #stmt_text;
EXECUTE stmt USING #score, #maxscore;
DEALLOCATE PREPARE stmt;
END $$

I believe you cannot do it in this manner.
In order to achieve this, you should use Dynamic SQL.
Note that you cannot open a cursor using Dynamic SQL either. But in your case, there seems to be no need for a cursor.
If i understand your code correctly, you can just use user variables and probably achieve what you are trying to do using 2 Dynamically prepared statements.
SET #stmt_text=CONCAT("SELECT #score = SUM(`score`), #maxscore=SUM(`maxscore`) FROM ",
answertable, "WHERE `idParticipation`= ", partid);
PREPARE stmt FROM #stmt_text;
EXECUTE stmt USING #a;
And then you update the values using the below statement
SET #stmt_text=CONCAT("UPDATE", participationtable, " SET `score`=#score,
`maxscore`=#maxscore WHERE `idParticipation`=", partid);
PREPARE stmt FROM #stmt_text;
EXECUTE stmt USING #a;
DEALLOCATE PREPARE stmt;
Note: Please check the syntax. I cannot test it to verify it exactly but i hope you get the idea.

Related

How to loop comma separated values into select statement in MySQL stored procedure

I have to send comma separated values into a select statement where it will update values through #sql statement.
I have common table in all Databases I need to update the table column by one update statement in the procedure.
For Example : Input Param will be ('DataBase1','Database2',....., 'Database10')
Below is the sample procedure :
DELIMITER &&
CREATE PROCEDURE update_stmt (IN DBName varchar(100))
BEGIN
Declare DBName = #DB;
**comma seperated values loop and placed into the #DB**
use #DB;
SELECT concat(update #DB.sample SET COL = 0 where ID = \'',ID,'\','; ) as stmt FROM
Test.Sample into #s;
SET #sql = #s
PREPARE stmt from #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END &&
DELIMITER ;
so that update statement will execute in each of the databases.
Here's another approach. I don't try to split the comma-separated string, I use it with FIND_IN_SET() to match schema names in INFORMATION_SCHEMA.TABLES. This filters to schemas in the list that actually exist, and tables that actually exist in that schema.
Then use a cursor to loop over the matching rows, so you don't have to split any strings, which is awkward to do in a stored procedure.
I supposed that you would want to specify the id of the row to update too, so I added that to the procedure parameters.
Also notice the use of quotes when I create #sql. You can concatenate strings, but those must be quote-delimited like any other string literal. Variables must not be inside the quoted string. There's no feature to expand variables inside string literals in MySQL.
DELIMITER &&
CREATE PROCEDURE update_stmt (IN schema_name_list VARCHAR(100), IN in_id INT)
BEGIN
DECLARE done INT DEFAULT false;
DECLARE schema_name VARCHAR(64);
DECLARE cur1 CURSOR FOR
SELECT TABLE_SCHEMA FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME = 'sample' AND FIND_IN_SET(TABLE_SCHEMA, schema_name_list);
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = true;
SET #id = in_id;
OPEN cur1;
schema_loop: LOOP
FETCH cur1 INTO schema_name;
IF done THEN
LEAVE schema_loop;
END IF;
SET #sql = CONCAT('UPDATE `', schema_name, '`.sample SET col = 0 WHERE id = ?');
PREPARE stmt FROM #sql;
EXECUTE stmt USING #id;
DEALLOCATE PREPARE stmt;
END LOOP;
CLOSE cur1;
END &&
DELIMITER ;
Frankly, I hardly ever use stored procedures in MySQL. The procedure language is primitive, and the tasks I see people try to do in stored procedures could be done a lot more easily in virtually every other programming language.

How to create a create table statement in a loop in mysql

I want to create multiple tables with just one statement (query).
Loop
start
(
create table a
)
a =a +1
end loop
So say it has to create 100 tables labeled as TABLE1, TABLE2, ...
Try the following procedure.
DROP PROCEDURE IF EXISTS `createTableProcTest`;
delimiter //
CREATE PROCEDURE `createTableProcTest`()
BEGIN
DECLARE count INT Default 0;
simple_loop: LOOP
SET #a := count + 1;
SET #statement = CONCAT('Create table Table',#a,' ( name VARCHAR(70), age int );');
PREPARE stmt FROM #statement;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
SET count = count + 1;
IF count=100 THEN
LEAVE simple_loop;
END IF;
END LOOP simple_loop;
END//
In order to execute just do the following:
Call createTableProcTest();
By executing the above procedure 100 tables will be created having name table1,...,table100.
And the table structure would look like following:
N:B: Procedure execution might take several seconds. Don't be impatient.
You need give us more details but, I think is easier to call a stored procedure, inside the loop, to create the procedure.
You need to create a procedure to create the tables you need and call this procedure inside the loop.
Ex:
CREATE PROCEDURE SP_Create_Table(IN tableName VARCHAR(50)) BEGIN SET
#sql = CONCAT('CREATE TABLE ', tableName, '(column1 INT(11))');
PREPARE stmt FROM #sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; END
Now, call the create table procedure inside the loop
DELIMITER // CREATE FUNCTION CalcIncome ( starting_value INT ) RETURNS
INT BEGIN
DECLARE income INT; SET income = 0; label1: WHILE income <=
50 DO
call SP_Create_Table(CONVERT(VARCHAR(50),starting_value)); END WHILE label1; RETURN income; END; // DELIMITER;

Logic condition to avoid duplicate entries in stored procedure

I have the following stored procedure . I'm trying to insert the users from the table usuaris, whose admin variable is equal to 1, into the table that the stored procedure creates with the name( nombre varchar(50)) that is passed as a parameter.
When the procedure is called, it duplicates the user 'mary' with id 4. I've tried a couple of ways to implement the logic condition in order to avoid the duplication, but still, I'm missing something and I can't get the desired result. In the code below, the logic condition before the insertion is the last thing I've tried. Any ideas?
Thanks.
CREATE DEFINER=`root`#`localhost` PROCEDURE `createNewtable`(nombre varchar(50))
BEGIN
/*variable declaration*/
declare centinela int ;
declare id1 int ;
declare nom1 varchar(50);
declare admin1 enum('0','1') ;
declare cadena varchar(100); /*string to concatenate table creation and insertion*/
/*cursor declaration*/
declare cursor1 cursor for select * from users.usuaris where admin = '1' ;
declare continue handler for not found set #centinela = 1 ;
/*create the table with the name that's passed as parameter*/
set #cadena=concat("create table ",nombre,
"(
id2 int not null primary key,
nom2 varchar(50),
admin2 enum ('0','1')
)" );
prepare stmt from #cadena ;
execute stmt ;
deallocate prepare stmt;
/* loop that fetches the data from the table usuaris and
inserts them into the newly created table. */
set #centinela = 0 ;
open cursor1 ;
bucle: loop
fetch cursor1 into id1,nom1,admin1 ;
if ( centinela = 1 ) then
leave bucle ;
end if ;
/*logic condition to avoid entry duplication */
if not exists (select * from users.usuaris where admin='1' and id=#id1) then
set #cadena=concat("insert into ",nombre," values( ",id1,",'",nom1,"','",admin1,"')");
end if;
select #cadena;
prepare stmt from #cadena;
execute stmt ;
deallocate prepare stmt;
end loop bucle;
close cursor1;
END
Here is the single-table database of users :
create database if not exists `users` ;
use `users` ;
create table usuaris(
id int not null auto_increment primary key ,
nom varchar(50),
admin enum ('0','1')
);
insert into usuaris(id,nom,admin)
values
(1,'jose','1'),
(2,'maria','0'),
(3,'frank','1'),
(4,'mary','1'),
(5,'godfrey','0') ;
Also it has to duplicate jose. The reason of duplication - if the IF statement isn't TRUE then you don't set the new #cadena variable BUT anyway execute PREVIOUS #cadena statement. You should move execution into the IF statement also:
if not exists (select * from users.usuaris where admin='1' and id=#id1) then
set #cadena=concat("insert into ",nombre," values( ",id1,",'",nom1,"','",admin1,"')");
select #cadena;
prepare stmt from #cadena;
execute stmt ;
deallocate prepare stmt;
end if;
Also in SQL you should always try to avoid loops if it possible and use SQL statements instead.
You can replace your loop with one SQL statement:
INSERET INTO NEW_TABLE_NAME_HERE
SELECT id1,nom1,admin1
FROM users.usuaris where admin<>'1'
Further more you can use SELECT INTO statement syntax to automatically create new table without CREATE TABLE statement:
SELECT id1 as id2,
nom1 as nom2,
admin1 as admin2
INTO NEW_TABLE_NAME_HERE
FROM users.usuaris where admin<>'1'
Change ur below code to my new code and try-
Existing Code
if not exists (select * from users.usuaris where admin='1' and id=#id1) then
set #cadena=concat("insert into ",nombre," values( ",id1,",'",nom1,"','",admin1,"')");
end if;
select #cadena;
prepare stmt from #cadena;
execute stmt ;
deallocate prepare stmt;
New Code-
SET #cnt=SELECT count(*) FROM users.usuaris WHERE admin='1' AND id=#id1
IF #cnt>0 THEN
SET #cadena=CONCAT("insert into ",nombre," values( ",id1,",'",nom1,"','",admin1,"')");
prepare stmt from #cadena;
execute stmt ;
deallocate prepare stmt;
end if;

passing table name in stored procedure

I am trying to figure out how I can pass part of a table name into a stored procedure and get it to work.
query is
DELIMITER $$
DROP PROCEDURE IF EXISTS vorpaldev.searchLogId2$$
CREATE DEFINER = 'root'#'%'
PROCEDURE vorpaldev.searchLogId2 (userId varchar(300))
BEGIN
SET userId = CONCAT("log", userId);
SET #statment = "Select * from #userId ";
PREPARE stmt FROM #statment;
SET #a = userId;
EXECUTE stmt USING #a;
DEALLOCATE PREPARE stmt;
END
$$
DELIMITER
;
I am using
CALL searchLogId2 (131)
to call the code
I want the end results to execute as
Select * from log131
Well! are you going to create seperate table for each user????????? If yes, that is really bad.
I don't know but may be this is your answer. Why dont you pass complete userId in parameter argument 'log131' as
BEGIN
SET #statment = concat('Select * from ',userId);
PREPARE stmt FROM #statment;
EXECUTE stmt USING userId;
DEALLOCATE PREPARE stmt;
END
this will remove overhead of concat

MySQL Stored Procedures : Use a variable as the database name in a cursor declaration

I need to use a variable to indicate what database to query in the declaration of a cursor. Here is a short snippet of the code :
CREATE PROCEDURE `update_cdrs_lnp_data`(IN dbName VARCHAR(25), OUT returnCode SMALLINT)
cdr_records:BEGIN
DECLARE cdr_record_cursor CURSOR FOR
SELECT cdrs_id, called, calling FROM dbName.cdrs WHERE lrn_checked = 'N';
# Setup logging
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
#call log_debug('Got exception in update_cdrs_lnp_data');
SET returnCode = -1;
END;
As you can see, I'm TRYING to use the variable dbName to indicate in which database the query should occur within. However, MySQL will NOT allow that. I also tried things such as :
CREATE PROCEDURE `update_cdrs_lnp_data`(IN dbName VARCHAR(25), OUT returnCode SMALLINT)
cdr_records:BEGIN
DECLARE cdr_record_cursor CURSOR FOR
SET #query = CONCAT("SELECT cdrs_id, called, calling FROM " ,dbName, ".cdrs WHERE lrn_checked = 'N' ");
PREPARE STMT FROM #query;
EXECUTE STMT;
# Setup logging
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
#call log_debug('Got exception in update_cdrs_lnp_data');
SET returnCode = -1;
END;
Of course this doesn't work either as MySQL only allows a standard SQL statement in the cursor declaration.
Can anyone think of a way to use the same stored procedure in multiple databases by passing in the name of the db that should be affected?
The answer of Vijay Jadhav is the right way to solve this limitation by MySQL. Actually, you need 3 proc to accomplish it:
proc1 using Vijay Jadhav's way, works like a data collector. You need to pass the variables to proc1 and let it create the tmp table for proc2. There is one limiation of Vijay's way, he should create a TEMPORARY table by using "CREATE TEMPORARY TABLE tmp_table_name SELECT ...". Because temporary table is thread safe.
proc2 declare the cursor on the tmp table which is created by proc1. Since the tmp table is already known and hard coded into the declaration, no more "table not found" error.
proc3 works like a "main" function, with all the parameters need to be sent to proc1 and proc2. proc3 simply calls proc1 first and then proc2 with the parameters need by each proc.
p.s Need to set system variable "sql_notes" to 0, otherwise proc1 will stop on DROP TABLE command.
Here is my example:
CREATE PROCEDURE `proc1`(SourceDBName CHAR(50), SourceTableName CHAR(50))
BEGIN
DECLARE SQLStmt TEXT;
SET #SQLStmt = CONCAT('DROP TEMPORARY TABLE IF EXISTS tmp_table_name');
PREPARE Stmt FROM #SQLStmt;
EXECUTE Stmt;
DEALLOCATE PREPARE Stmt;
SET #SQLStmt = CONCAT('CREATE TEMPORARY TABLE tmp_table_name SELECT ... FROM ',SourceDBName,'.',SourceTableName,' WHERE ... ');
PREPARE Stmt FROM #SQLStmt;
EXECUTE Stmt;
DEALLOCATE PREPARE Stmt;
END$$
CREATE PROCEDURE `proc2`(TargetDBName CHAR(50), TargetTemplateTableName CHAR(50))
BEGIN
DECLARE done INT DEFAULT 0;
DECLARE FieldValue CHAR(50);
DECLARE CursorSegment CURSOR FOR SELECT ... FROM tmp_table_name;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
OPEN CursorSegment;
REPEAT
FETCH CursorSegment INTO FieldValue;
IF NOT done THEN
...
END IF;
UNTIL done END REPEAT;
CLOSE CursorSegment;
END$$
CREATE PROCEDURE `proc3`(SourceDBName CHAR(50), SourceTableName CHAR(50), TargetDBName CHAR(50), TargetTemplateTableName CHAR(50))
BEGIN
CALL proc1(SourceDBName, SourceTableName);
CALL proc2(TargetDBName, TargetTemplateTableName);
END$$
No, you can't do that in cursors.
Maybe just prepared statements may do the job? :
delimiter ;;
create procedure test(in dbName varchar(40))
begin
set #query := CONCAT("SELECT * FROM " , dbName, ".db;");
PREPARE s from #query;
EXECUTE s;
DEALLOCATE PREPARE s;
end;;
delimiter ;
call test("mysql");
Try to create (temporary) table using prepared statement in a different procedure.
SET #query = CONCAT("CREATE TABLE temp_table AS SELECT cdrs_id, called, calling FROM " ,dbName, ".cdrs WHERE lrn_checked = 'N' ");
...
And then select data from that table in your 'test' procedure.
The answer to this is that it cannot be done. You cannot use variables in the cursor declaration. I appreciate noonex's response. However, his solution does not allow me to walk through the results. It simply executes the query.
create procedure test(in dbName varchar(40))
READS SQL DATA <- this line returns will allow you to walk through the results
begin
...
$result = call test("mysql");