CURSOR LOOP within TRANSACTION - mysql

I can't even get this basic CURSOR LOOP to run in my TRANSACTION.
Please help. Thanks in advance!
CREATE PROCEDURE ClearItems(IN itemName CHAR)
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE bID INT(255);
DECLARE bIDs CURSOR FOR SELECT id FROM table1 WHERE column1 = itemName AND column2 = 'b' ORDER BY column3 DESC, date ASC;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
START TRANSACTION;
OPEN bIDs;
clear_loop: LOOP
FETCH bIDs INTO bID;
IF done THEN
LEAVE clear_loop;
END IF;
INSERT INTO table2 (column1) SELECT bID;
END LOOP;
CLOSE bIDs;
COMMIT;
END //
DELIMITER ;

To fix the problem - rename declared ID variable. Field names and variables should not be the same.
Also, if you want to populate another table, try to use INSERT ... SELECT statement. It will help you to avoid using cursors. For example -
INSERT INTO table2(`2column1`)
SELECT id
FROM table1
WHERE `1column1` = itemName AND `1column2` = 'b'
ORDER BY `1column3` DESC, date ASC;

Related

error in stored procedure ...into keyword

Two tables Borrower(rollno,name,bookissue_date) and Fine(rollno,name,amount)
delimiter //
create procedure student( in roll_no int,in Nameofbook varchar(40))
begin
declare Dateofiss1 date;
Declare cur cursor for
select Dateofiss from Borrower where Roll_no = roll into Dateofiss1;
OPEN cur;
fetch cur into Dateofiss1
if(datediff(sysdate(),Dateofiss1)<15) then varchar(20))
update Borrower set status='R'where Roll_no=roll_no
elseif(datediff(sysdate(),Dateofiss1)>=15)and datediff (sysdate(),Dateofiss1<30)
SET FINEAMOUNT=5*(datediff(sysdate(),Dateofiss1)-15)
insert into Fine(Roll_no,Date,amount)values(rollno,sysdate,fineamount);
update.borrower set status='R' where Roll_no='rollno';
elseif (datediff(sysdate(),Dateofiss1)>30)
SET FINEAMOUNT=50*(datediff(sysdate(),Dateofiss1)-15)
insert into Fine(Roll_no,Date,amount)values(rollno,sysdate,fineamount);
update.borrower set status='R' where Roll_no='rollno';
close cur;
end if
select * from Borrower;
elect * from Fine;
end
You have a number of syntax errors.
You have an extraneous varchar(20)) in the first if statement.
You're missing THEN in the ELSEIF statements.
You wrote update.borrower instead of update borrower.
You have roll_no in quotes in some of your update statements.
The roll_no parameter is the same as a table column, since column names are case-insensitive. The condition where Roll_no = roll_no will match every row because of this. Give the parameter a different name.
In a SELECT, the INTO clause goes after FROM, not at the end.
There's no need to use a cursor if you're using SELECT INTO. Just execute the query and it will set the variable.
You can also simplify the code by putting the date difference in a variable, so you don't have to repeatedly calculate it. And in the ELSEIF you don't need to test >= 15, since you'll only get there if the < 15 test failed.
The UPDATE statement is the same in all conditions, so it doesn't need to be in the IF at all.
delimiter //
create procedure student( in p_roll_no int,in Nameofbook varchar(40))
begin
declare Dateofiss1 date;
declare diff INT;
select Dateofiss from Borrower into Dateofiss1 where Roll_no = p_roll_no;
OPEN cur;
SET diff = datediff(sysdate(),Dateofiss1)
IF diff BETWEEN 15 AND 29 THEN
SET FINEAMOUNT= 5 * (diff - 15)
insert into Fine(Roll_no,Date,amount)values(rollno,sysdate,fineamount);
else
SET FINEAMOUNT= 50 * (diff - 15)
insert into Fine(Roll_no,Date,amount)values(rollno,sysdate,fineamount);
end if
update Borrower set status='R'where Roll_no=p_roll_no
select * from Borrower;
select * from Fine;
end

Foreach Data in Field Insert Selected Field from One Database to Another in MySQL

I have two (2) databases of dissimilar Schematics,
db1 migrated from MSSQL to MYSQL
and
db2 created from Laravel Migration.
Here's the challenge:
The tables of db1 do not have id columns (Primary Key) like is easily found on db2 tables. So I kept getting the warning message:
Current selection does not contain a unique column. Grid edit, checkbox, Edit, Copy and Delete features are not available.
So I had to inject the id columns on the tables in the db1
I need to extract fields [level_name, class_name] from stdlist in db1,
Create levels (id,level_name,X,Y) on db2
classes (id,class_name,level_id) on db2
To throw more light: The level_id should come from the already created levels table
I have already succeeded in extracting the first instance using the following snippet:
First Query to Create Levels
INSERT INTO db2.levels(level_name,X,Y)
SELECT class_name as level_name,1 as X,ClassAdmitted as Y
FROM db1.stdlist
GROUP BY ClassAdmitted;
This was successful.
Now, I need to use the newly created ids in levels table to fill up level_id column in the classes table.
For that to be possible, must I re-run the above selection schematics? Is there no better way to maybe join the table column from db1.levels to db2.stdlist and extract the required fields for the new insert schematics.
I'll appreciate any help. Thanks in advance.
Try adding a column for Processed and then do a while exists loop
INSERT INTO db2.levels(level_name,X,Y)
SELECT class_name as level_name,1 as X,ClassAdmitted as Y, 0 as Processed
FROM db1.stdlist
GROUP BY ClassAdmitted;
WHILE EXISTS(SELECT * FROM db2.levels WHERE Processed = 0)
BEGIN
DECLARE #level_name AS VARCHAR(MAX)
SELECT TOP 1 #level_name=level_name FROM db2.levels WHERE Processed = 0
--YOUR CODE
UPDATE db2.levels SET Processed=1 WHERE level_name=#level_name
END
You may need to dump into a temp table first and then insert into your real table (db2.levels) when you're done processing. Then you wouldn't need the Unnecessary column of processed on the final table.
This is what worked for me eventually:
First, I picked up the levels from the initial database thus:
INSERT INTO db2.levels(`name`,`school_id`,`short_code`)
SELECT name ,school_id,short_code
FROM db1.levels
GROUP BY name
ORDER BY CAST(IF(REPLACE(name,' ','')='','0',REPLACE(name,' ','')) AS UNSIGNED
INTEGER) ASC;
Then I created a PROCEDURE for the classes insertion
CREATE PROCEDURE dowhileClasses()
BEGIN
SET #Level = 1;
SET #Max = SELECT count(`id`) FROM db2.levels;
START TRANSACTION;
WHILE #Level <= #Max DO
BEGIN
DECLARE val1 VARCHAR(255) DEFAULT NULL;
DECLARE val2 VARCHAR(255) DEFAULT NULL;
DECLARE bDone TINYINT DEFAULT 0;
DECLARE curs CURSOR FOR
SELECT trim(`Class1`)
FROM db1.dbo_tblstudent
WHERE CAST(IF(REPLACE(name,' ','')='','0',REPLACE(name,' ','')) AS UNSIGNED INTEGER) =#Level
GROUP BY `Class1`;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET bDone = 1;
OPEN curs;
SET bDone = 0;
REPEAT
FETCH curs INTO val1;
IF bDone = 0 THEN
SET #classname = val1;
SET #levelID = (SELECT id FROM db2.levels WHERE short_code=#Level limit 1);
SET #schoolId = 1;
SET #classId = (SELECT `id` FROM db2.classes where class_name = #classname and level_id= #levelID limit 1);
IF #classId is null and #classname is not null THEN
INSERT INTO db2.classes(class_name,school_id,level_id)
VALUES(#classname,#schoolId,#levelID);
END IF;
END IF;
UNTIL bDone END REPEAT;
CLOSE curs;
END;
SELECT CONCAT('lEVEL: ',#Level,' Done');
SET #Level = #Level + 1;
END WHILE;
END;
//
delimiter ;
CALL dowhileClasses();
With this, I was able to dump The classes profile matching the previously created level_ids.
The whole magic relies on the CURSOR protocol.
For further details here is one of the documentations I used.

How to add salary from two tables in stored procedure

I want to add the salary from two tables in stored procedure on the basis of id column:
DDl:
create table salary1 (id varchar(20), salary varchar(20));
create table salary2 (id varchar(20), salary varchar(20));
DML:
insert into salary1 values('1', '100');
insert into salary1 values('2', '200');
insert into salary2 values('1', '10');
insert into salary2 values('2', '10');
Database: mysql
Output should like this:
id total_sal
1 110
2 210
My stored procedure look like:
CREATE PROCEDURE totalSal()
BEGIN
DECLARE tbl1_id varchar(30);
DECLARE tbl1_sal varchar(30);
DECLARE tbl2_id varchar(30);
DECLARE tbl2_sal varchar(30);
DECLARE total_sal varchar(30);
DECLARE c1 CURSOR FOR SELECT * FROM salary1;
DECLARE c2 CURSOR FOR SELECT * FROM salary2;
-- Open first cursor
OPEN c1;
LOOP
FETCH c1 INTO tbl1_id, tbl1_sal;
-- Open second cursor
OPEN c2;
LOOP
FETCH c2 INTO tbl2_id, tbl2_sal;
IF tbl1_id = tbl2_id THEN
set total_sal := tbl1_sal + tbl2_sal;
ELSE
set total_sal := tbl_sal;
END IF;
END LOOP;
CLOSE c2;
END LOOP;
CLOSE c1;
end $$
It got's successfully compiled, but when i am running the procedure i am getting the below error:
ERROR 1329 (02000): No data - zero rows fetched, selected, or processed
I have also used the DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1; in my procedure. but still my problem is unresolved.
If someone can solve this problem in oracle, that would also help me.
Note : I cannot perform join operation on these tables. Because of a few performance issues.
Thanks in advance !!!
Solution 1:
Using collection and only one iteration of 2 loop
You should consider to fix your performance issue on join. Performing loop is slower than a set base approach in most case.
If I follow your logic, what you realy want is to loop trough all the salary2 table for each salary1 row in order to found the right ID => millions of loop.
You can consider doing 2 separated loop and store data inside and indexed array. ( the key will be the tlb1_id).
If the key exist : sum the salary values, if not exist insert it inside the array.
At the end of the procedure, just select the array as table.
Solution 2:
Using a join on integer indexed columns
you can add a new integer column on each table
Populate this column with the casted value of the ID column
Add an index on these columns on each tables
After that you will be able to perform a join
Have a look at this fiddle http://sqlfiddle.com/#!9/c445de/1 , it can be time consuming to perform theses step and disk space consumuming to add a new columns and indexes but the join operation may be faster than before.
You can do something like this... I have moved the second cursor inside the loop so that it only goes over the id's from table 1. This should help the logic for the procedure but still I would recommend trying to figure out how to fix the join to get the results as that seems like an easier way and should be much faster if done correctly.
CREATE PROCEDURE totalSal()
BEGIN
DECLARE tbl1_id varchar(30);
DECLARE tbl1_sal varchar(30);
DECLARE tbl2_id varchar(30);
DECLARE tbl2_sal varchar(30);
DECLARE total_sal varchar(30);
DECLARE c1 CURSOR FOR SELECT * FROM salary1;
-- Open first cursor
OPEN c1;
LOOP
FETCH c1 INTO tbl1_id, tbl1_sal;
SELECT COUNT(*) INTO v_rowcount FROM salary2 WHERE id = tbl1_id;
IF v_rowcount > 0 THEN
Begin
DECLARE c2 CURSOR FOR SELECT * FROM salary2 WHERE id = tbl1_id;
-- Open second cursor
OPEN c2;
LOOP
FETCH c2 INTO tbl2_id, tbl2_sal;
IF tbl1_id = tbl2_id THEN
set total_sal := tbl1_sal + tbl2_sal;
ELSE
set total_sal := tbl_sal;
END IF;
END LOOP;
CLOSE c2;
END IF;
END
END LOOP;
CLOSE c1;
end $$
Well you asked for an answer without JOIN, but that seemed arbitrary, so here's an answer with JOIN.
SELECT
sums1.id
, S1Sum + S2Sum AS SalarySum
FROM (SELECT id, SUM(CAST(salary AS int)) AS S1Sum
FROM salary1
GROUP BY id) sums1
JOIN (SELECT id, SUM(CAST(salary AS int)) AS S2Sum
FROM salary2
GROUP BY id) sums2
ON sums1.id = sums2.id
I am guessing your performance is bad because all of your columns are varchar when they should be int or numeric. But we don't have much to go on so hopefully this helps you come to a solid solution.
Also the post was edited to add both MySQL and Oracle tags so it's difficult to determine what the syntax should be...

change one table data when single column all data of another table is equal to some value in mysql

I have created 2 tables named snag_list and defect_list. I need to change the status field of snag_list to 2 when all the defect_list status should to be 2
Not sure if this helps but try to create a trigger for defect_list
and check the distinct count of status column if it is one and the value is 2 then update the snag_list a example would look like this
DELIMITER $$
CREATE TRIGGER checkstatus
AFTER UPDATE ON defect_list
FOR EACH ROW
BEGIN
DECLARE cnt INT
SELECT COUNT(DISTINCT status) FROM defect_list INTO cnt
DECLARE st INT
SELECT DISTINCT status FROM defect_list LIMIT 1 INTO st
IF(cnt = 1 AND st = 2)
UPDATE snag_list SET status = 2
ENF IF
END$$
DELIMITER ;
Your question is very vague but I guess this is what you may be looking for.
DECLARE
count_rec VARCHAR2(10);
data_rec VARCHAR2(10);
BEGIN
SELECT COUNT(DISTINCT status) INTO count_rec FROM defect_list;
SELECT DISTINCT status INTO data_rec FROM defect_list;
IF (count_rec = '1' AND data_rec = '2') THEN
UPDATE snag_list SET status = '2';
END IF;
END;
edit -> You can change the datatype of the 2 variables as required. Go with VARCHAR2 if you're unsure whether the data would be numeric.

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.