i'm abit lost and i'm hoping for your help. I'm currently learning to work with Mysql and i got this procedure:
delimiter //
drop procedure price_update2 //
create procedure price_update2()
BEGIN
DECLARE n INT default 0;
DECLARE i INT default 1;
DECLARE cond varchar(255) default '';
SET cond = CONCAT('SELECT customer_id FROM customers LIMIT ',i,',1');
prepare stt FROM cond;
execute stt;
END //
for some reason i always get an error near the "cond; execute stt....". Probably it's just an easy thing, but i can't find it.
Also tried without concat:
delimiter //
drop procedure price_update2 //
create procedure price_update2()
BEGIN
DECLARE n INT default 0;
DECLARE i INT default 1;
DECLARE cond varchar(255) default '';
SET cond = 'SELECT customer_id FROM customers LIMIT 1';
prepare stt FROM cond;
execute stt;
END //
if i use the statement directly, it works fine.
This is an odd thing, but you need to use user-defined variables at this point:
delimiter //
drop procedure price_update2 //
create procedure price_update2()
BEGIN
DECLARE n INT default 0;
DECLARE i INT default 1;
SET #cond = CONCAT('SELECT customer_id FROM customers LIMIT ',i,',1');
prepare stt FROM #cond;
execute stt;
DEALLOCATE PREPARE stt; # don't forget to deallocate
END //
The manual states this:
A statement prepared in stored program context cannot refer to stored procedure or function parameters or local variables because they go out of scope when the program ends and would be unavailable were the statement to be executed later outside the program. As a workaround, refer instead to user-defined variables, which also have session scope
Related
I have a procedure that returns multiple rows, but separately. Please take a look at its result:
I causes some issues when I want to fetch the result in the code (backend side). Now I want to create a temporary table and insert all rows inside it and then return that temp table as the result of the stored procedure. How can I do that inside procedure?
Not sure it above idea is a good idea .. that's the only thing I can probably be useful to merge all rows all in one table as SP's result.
Here is my current procedure:
DELIMITER $$
CREATE DEFINER=`administrator`#`localhost` PROCEDURE `lending_ewallets_balance_in_merchant`(IN `user_id_param` BIGINT UNSIGNED, IN `business_id_param` INT UNSIGNED)
NO SQL
BEGIN
DECLARE dossier_id INT;
DECLARE query_string VARCHAR(255) DEFAULT '';
DECLARE cursor_List_isdone BOOLEAN DEFAULT FALSE;
DECLARE user_dossiers CURSOR FOR
Select ld.id, lwp.query_string
FROM lending_users_dossiers ld
JOIN lending_where_to_pays lwp ON ld.lending_where_to_pay_id = lwp.id
WHERE user_id = user_id_param
AND (ld.status = 'activated' OR ld.status = 'finished');
# 'finished' is for loans
DECLARE CONTINUE HANDLER FOR NOT FOUND SET cursor_List_isdone = TRUE;
Open user_dossiers;
loop_List: LOOP
FETCH user_dossiers INTO dossier_id, query_string;
IF cursor_List_isdone THEN
LEAVE loop_List;
END IF;
SET #qry = CONCAT(
"SELECT ld.id lending_dossier_id, ld.type, SUM(let.credit) balance
FROM lending_users_dossiers ld
JOIN lending_ewallet_transactions let
ON ld.id = let.lending_dossier_id
WHERE ld.id = ", dossier_id,
" AND ", business_id_param, " IN(", query_string, ")",
"GROUP BY ld.id, ld.type");
PREPARE stmt FROM #qry;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END LOOP loop_List;
Close user_dossiers;
END$$
DELIMITER ;
Noted that, the MySQL version I use is MySQL v8.0.20.
The logic should be something like this. Outside the loop create a temp table if not exists and delete the data from it:
CREATE TEMPORARY TABLE
IF NOT EXISTS
user_dossiers_tmp (your columns);
DELETE FROM user_dossiers_tmp;
In your loop:
INSERT INTO user_dossiers_tmp VALUES (your data);
After your loop:
SELECT * FROM user_dossiers_tmp;
END$$
Below i have created one procedure, which took 2 parameter called company_name and SN_F,
I want to pass the table name to procedure as parameter for which i want to create cursor.
DELIMITER //
CREATE PROCEDURE transfer_t(IN company_name varchar(50), SN_f int)
BEGIN
DECLARE done BOOLEAN DEFAULT 0;
DECLARE dates_f date default null;
DECLARE high_f float default 0.0;
DECLARE low_f float default 0.0;
DECLARE open_f float default 0.0;
DECLARE close_f float default 0.0;
DECLARE volume_f int default 0;
DECLARE adj_close_f float default 0.0;
DECLARE Company_detail cursor for
select Date, high, low, open, close, volume, adj_close from company_name;
Declare CONTINUE HANDLER FOR NOT FOUND SET done=1;
OPEN Company_detail;
REPEAT
FETCH company_detail into dates_f, high_f, low_f, open_f, close_f, volume_f, adj_close_f;
insert into historic_data(sn ,Date ,High ,low ,open ,close , volume , adj_close) values (SN_f,dates_f,high_f,low_f,open_f,close_f, volume_f, adj_close_f);
until done END REPEAT;
close company_detail;
END//
DELIMITER ;
above procedure gets created successfully, but whenever i called it like,
call transfer_t('tcs_temp', 1);
it gives the following error
Error Code: 1146. Table 'test_schema.company_name' doesn't exist
Please help me solve this...
You seem to be looking to copy all records from a table name given as parameter to anther, fixed table. I don't see the need for a cursor here. SQL is a set-based language, which is built to natively perform such kind of operation. You would typically use the insert into ... select ... syntax.
On the other hand, you need dynamic SQL if you want to use the table name as variable - which your current code is missing.
The following code should do what you want:
delimiter //
create procedure transfer_t(in p_company_name varchar(50), p_sn_f int)
begin
set #p_sn_f = p_sn_f;
set #sql = concat(
'insert into historic_data(sn ,Date ,High ,low ,open ,close , volume , adj_close)',
' select ?, date, high, low, open, close, volume, adj_close from ', p_company_name
);
prepare stmt from #sql;
execute stmt using #p_sn_f;
deallocate prepare stmt;
end//
delimiter ;
I can't get this code to work, i keep getting syntax errors, and i don't see anything wrong with the declare sentences.
delimiter //
create procedure Plsql1 (Count int)
begin
CREATE TABLE alumnos(
nombre VARCHAR(7) primary key,
edad INT (7),
sexo INT (2));
DECLARE Count int default 10;
DECLARE Number int default 0;
DECLARE done int = 0;
etiq1: loop
if not done then
INSERT INTO alumnos VALUES(CONCAT('Victor',Number), Number*5, Number%2);
SET N=N+1;
if (Number=Count) SET done=1;
else
leave etiq1;
end if;
end loop;
SELECT * FROM alumnos;
end //
delimiter ;
When i try to get this to work, i get this:
ERROR 1064 (42000): 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 'DECLARE Count int default 10;
DECLARE Number int default 0;
DECL' at line 10
However, i can't see any syntax errors in there. Some advice for this, please?
You can create the table like that in the stored procedure:
CREATE PROCEDURE procedure1(IN tableName VARCHAR(255))
BEGIN
SET #sql = CONCAT('CREATE TABLE ', tableName, '(column1 INT(11))');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
// Do all other stuff then...
END
That should work.
I would like to use LIMIT in a cursor. The cursor should be used and updated several times within a loop, each time with different parameters of LIMIT. Here some code:
DELIMITER $$
CREATE PROCEDURE `updateIt`() READS SQL DATA
BEGIN
declare done int(1) default 0;
declare counter int(10) default 0;
declare xabc int(10) default 0;
declare tab1Cursor cursor for select abc from tab1 limit 100000*counter, 100000;
declare continue handler for not found set done=1;
loopCounter: LOOP
set done = 0;
open tab1Cursor;
igmLoop: loop
fetch tab1Cursor into xabc;
if done = 1 then leave igmLoop; end if;
-- do something
end loop igmLoop;
close tab1Cursor;
if (counter = 1039)
leave loopCounter;
end if;
set counter = counter + 1;
END LOOP loopCounter;
END $$
DELIMITER ;
This, however, does not work (I also tried it with the cursor in the LOOP counterLoop). Can Mysql deal with dynamic cursors?
From the MySQL Manual
a cursor cannot be used for a dynamic statement that is prepared and
executed with PREPARE and EXECUTE. The statement for a cursor is
checked at cursor creation time, so the statement cannot be dynamic.
However there are 2 ways, according to this post in mysql forums:
The first is for cases where absolutely only one user at a time will be running the procedure. A prepare statement can be used to create a view with the dynamic SQL and the cursor can select from this statically-named view. There's almost no performance impact. Unfortunately, these views are also visible to other users (there's no such thing as a temporary view), so this won't work for multiple users.
Analogously, a temporary table can be created in the prepare statement and the cursor can select from the temporary table. Only the current session can see a temporary table, so the multiple user issue is resolved. But this solution can have significant performance impact since a temp table has to be created each time the proc runs.
Bottom line: We still need cursors to be able to be created dynamically!
Here's an example of using a view to pass the table name and column name into a cursor.
DELIMITER //
DROP PROCEDURE IF EXISTS test_prepare//
CREATE PROCEDURE test_prepare(IN tablename varchar(255), columnname varchar(50))
BEGIN
DECLARE cursor_end CONDITION FOR SQLSTATE '02000';
DECLARE v_column_val VARCHAR(50);
DECLARE done INT DEFAULT 0;
DECLARE cur_table CURSOR FOR SELECT * FROM test_prepare_vw;
DECLARE CONTINUE HANDLER FOR cursor_end SET done = 1;
SET #query = CONCAT('CREATE VIEW test_prepare_vw as select ', columnname, ' from ', tablename);
select #query;
PREPARE stmt from #query;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
OPEN cur_table;
FETCH cur_table INTO v_column_val;
WHILE done = 0 DO
SELECT v_column_val;
FETCH cur_table INTO v_column_val;
END WHILE;
CLOSE cur_table;
DROP VIEW test_prepare_vw;
END;
//
DELIMITER ;
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");