Multiple handlers conflicting - mysql

I have nested cursor loops, and I wanted to catch error 1329, no data fetch, on the outer loop. How can I do this?
I tried to add a second CONTINUE HANDLER for this outer loop cursor but it catches nested loop errors (not wanted), and it ends up not triggering the nested handler.
I have this:
DECLARE cur1 CURSOR FOR select data, valor_split, valor_div from tbl1 where data >= primeira_mov and id_papel = idpapel order by data DESC;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
DECLARE CONTINUE HANDLER FOR 1329 BEGIN
SET nodata_issue = 1;
SET dateof_issue = datasplit;
END;
Any ideas on how can I solve this?

Set a flag to 1 when you enter the inner loop, set it to 0 when you leave it.
Then:
DECLARE CONTINUE HANDLER FOR 1329 BEGIN
IF flag_innerloop = 0 THEN do_something;
ELSE do_something_else;
END;

Related

basic cursor mysql with loop

I've been trying to create a cursor but for some reason i keep getting an error saying theres something wrong with the syntax.
what im asking is for a basic cursor that i can use with a loop so that i can call a function for each row. literally a mysql equivelent of php foreach loop
function functionName ($commentId){
$i = 0;
foreach (commentId as value) {
//inside here we would do whatever function we would like but for this example i just want a basic increment to keep things simple and easy to understand
$i++;
}
echo $i;}
functionName(76);
i understand php is a little bit different as i would first need to select the rows using a mysql query and have the $commentId variable equal to the but i was just using this as an example to make this question as clear as i can make it.
here is my example that keeps failing
DELIMITER $$
create function functionName(commentOn int(11)) returns int
BEGIN
DECLARE done INT DEFAULT FALSE;
declare var int;
declare anotherVar int;
set var = 0;
set anotherVar = 0;
DECLARE cur1 CURSOR FOR SELECT id FROM comments where `commentOnComment` = commentOn;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cur1;
read_loop: LOOP
FETCH cur1 INTO anotherVar;
set var = var + 1;
IF done THEN
LEAVE read_loop;
END IF;
END LOOP;
CLOSE cur1;
return var;
END; $$
DELIMITER ;
select functionName(76);
to explain what this example is in a little more detail, there is a table full of comments, the comment id and the id of the comment the user commented on (commentOnComment). and in the end i want to loop through all of the comments and select all where the commentOnComment equals the id of the comment the user commented on so that i can see how much replies each comment has i will change the increment to a select count(*) from comments where commentOnComment = commentOn; the commentOn parameter will be the id of the comment. But for the answer shown for this question i just want an increment to keep it simple and easy for myself and other people to understand.
Thanks to P.Salmon i was able to get a basic cursor with loop working, it was just the order of my code that was the problem. Im very greatfull as a little mistake like this would have taken me forever to figure out especially with the fact that i had never done a working cursor before.
here is the code for a basic cursor that does a function for each row in a table, in this example it increments the value of a variable by 1.
DELIMITER $$
create function functionName(commentOn int(11)) returns int
BEGIN
DECLARE done INT DEFAULT FALSE;
declare var int;
declare anotherVar int;
DECLARE cur1 CURSOR FOR SELECT id FROM comments where `commentOnComment` = commentOn;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
set var = 0;
set anotherVar = 0;
OPEN cur1;
read_loop: LOOP
FETCH cur1 INTO anotherVar;
IF done THEN
LEAVE read_loop;
END IF;
/*comment here is the area where you can add whatever insert, update, select or any
type of function you would like for each row. This is literally mysql's version of
php's foreach loop*/
set var = var + 1;
END LOOP;
CLOSE cur1;
return var;
END; $$
DELIMITER ;
as you can see its very similar to the question i just needed to change the order of my code. a simple select functionName(id); will give you the output, the id can be any number, in this example it would be the id of the row.
I am also curious as to why many people viewed this question but didnt answer, consequentially causing me to have to answer myself? if P.Salmon didnt comment and tell me the problem i would still be stuck. i tried to be as clear as i could, was it difficult to understand? was the question too hard for most people? feedback would be highly appreciated as it will help me in my future questions.

How to recursively call a procedure when there is a deadlock?

I have jobs "stored procedures" runs over night. Every now and then I run into a deadlock. I want to write a code that will execute the same procedure after 15 minutes up to 3 time if a deadlock is found.
Assume the time now is 10AM. Then run it at 10AM, if a deadlock found, call the job again at 10:15AM, then at 10:30AM "if dead locked", then 10:45AM "if dead locked." If the last run dead locked then terminate the job completely.
Here is What I have done.
DECLARE totalTrys INT DEFAULT 0;
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
GET DIAGNOSTICS CONDITION 1
#err_num = MYSQL_ERRNO;
if(#err_num = 1213 AND totalTrys < 4) THEN
SET totalTrys = totalTrys + 1;
SELECT SLEEP(900);
CALL guardi.prod_update_guardi_accounts();
END IF;
END;
Is this a valid approach? is there a better way other than eliminating deadlocks all together?
I would keep everything in the procedure: Then all code related to this behaviour is at one location. Also: Would the deadlock not follow the execution of you CALL statement instead of proceed? If is proceeds, it is probably already solved, and we are then waiting for nothing. Also transactions following a deadlock, would just be executed after the deadlock is resolved.
My approach with your code would be:
CREATE PROCEDURE manageAccounts()
BEGIN
DECLARE totalTrys INT DEFAULT 0;
DECLARE EXIT HANDLER FOR SQLEXCEPTION
GET DIAGNOSTICS CONDITION 1
#err_num = MYSQL_ERRNO;
CALL guardi.prod_update_guardi_accounts();
WHILE(#err_num = 1213 AND totalTrys < 4) DO
SET totalTrys = totalTrys + 1;
SELECT SLEEP(900);
CALL guardi.prod_update_guardi_accounts();
END WHILE;
END;
You will have to test this: Untested, so typos are not impossible.

MySql Procedure GOTO Statement

I want to use goto statement in Mysql stored procedure in such manner that I execute my cursor whenever condition become true once again.
This is a sample code only, I just looking for goto statement
BLOCK_BACKLOG:begin
declare backlgId, oldCOR, oldATR,oldCourse,oldATR,isFormFilled,nextParentId int;
declare backlogNoMoreRow boolean default false;
Label : TestLineStart
declare backlogCur cursor for select bcklg.id,cor.id,atr.id,cr.id,atr.obtainedMarks,atr.isFormFilled,atr.parentRegistration_id
from train bcklg,bus cor,fliet atr,fair co,distance cr
where bcklg.courseofferingregistration_id=cor.id and cor.academictermregistration_id=atr.id and cor.courseoffering_id=co.id and co.course_id=cr.id
and bcklg.isDeleted is false and atr.id=parentId;
declare continue handler for not found set backlogNoMoreRow=true;
open backlogCur;
LOOP_BACKLOG: loop
fetch backlogCur into backlgId, oldCOR, oldATR,oldCourse,oldATRMarks,isFormFilled,nextParentId;
if backlogNoMoreRow then
close backlogCur;
leave LOOP_BACKLOG;
end if;
if isFormFilled==0 then
parentId=nextParentId;
GOTO TestLineStart;
end if;
You can use a LOOP on the outer block based on the value for variable isFormFilled.
Following change may help you.
-- Label : TestLineStart
TestLineStart: LOOP
-- other part of your SP as it is
and make following change:
if isFormFilled==0 then
parentId=nextParentId;
-- GOTO TestLineStart;
else
leave TestLineStart;
end if;
You have to close all loop statements properly.

Warning 1329 no data fetched

delimiter ;;
create procedure cd3(out var int)
begin
declare finished int default 0;
declare cur cursor for select id from a;
declare continue handler for not found set finished=1;
open cur;
curloop:loop
fetch cur into var;
if finished then
leave curloop;
end if;
end loop curloop;
close cur;
end;;
call cd3(#var);
This is a warning provided by MySQL. They can be a bit confusing, i suggest you read up on it: See http://dev.mysql.com/doc/refman/5./en/show-warnings.html
I would use this approach instead
declare v_var VARCHAR(200); <-- if that whats it expected to be
open cur;
-- create a loop
repeat_cur: LOOP
FETCH cur INTO var;
-- checks if it is done
if finished then
LEAVE repeat_cur;
end if;
// DO THE WORK
END LOOP;
close cur;
Im not sure since var is a expected out parameter. You can declare var as a variable and work with it, otherwise just skip the declare line i wrote.
Check out this page: How to get rid of "Error 1329: No data - zero rows fetched, selected, or processed" , he had the same problem, and was solved by adding a simple select statement into var.
If above doesnt work, try adding this before the end of the loop.
SELECT 'var' INTO var;

Calling TEdit objects based on DB query

I have a form with 7 TEdit having name EditPhone1, EditPhone2 and so on.
In the same form I query a DB to get data to fill those TEdits. Of course I cannot know in advance how many results the query will return.
How can I call the various TEdit objects when looping on the rowcount of the query?
Use FindComponent to "convert" a component name to the component itself:
var
Edit: TEdit;
I: Integer;
begin
DataSet.First;
I := 1;
while not DataSet.Eof do
begin
Edit := TEdit(FindComponent(Format('EditPhone%d', [I])));
if Edit <> nil then
Edit.Text := DataSet.FieldValues['PhoneNo'];
DataSet.Next;
Inc(I);
end;
Now, this requires to hard-code the EditPhone%d string into the source which results in all kinds of maintainability issues. For example: consider renaming the edits.
Alternative 1:
To not rely on the component names, you could instead make use of TLama's idea and add all the edits to a list:
uses
... , Generics.Collections;
type
TForm1 = class(TForm)
EditPhone1: TEdit;
...
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
FEdits: TList<TEdit>;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
FEdits := TList<TEdit>.Create;
FEdits.AddRange([EditPhone1, EditPhone2, EditPhone3, EditPhone4, EditPhone5,
EditPhone6, EditPhone7]);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
FEdits.Free;
end;
procedure TForm1.ADOQuery1AfterOpen(DataSet: TDataSet);
var
I: Integer;
begin
DataSet.First;
I := 0;
while (not DataSet.Eof) and (I < FEdits.Count) do
begin
FEdits[I].Text := DataSet.FieldValues['PhoneNo'];
DataSet.Next;
Inc(I);
end;
end;
This still requires some maintenance in case of adding edits in future.
Alternative 2:
You could also loop over all edits in the form to find the ones tagged to be added to the list, instead of adding them each explicitly:
procedure TForm1.FormCreate(Sender: TObject);
var
I: Integer;
begin
FEdits := TList<TEdit>.Create;
for I := 0 to ComponentCount - 1 do
if (Components[I] is TEdit) and (TEdit(Components[I]).Tag = 1) then
FEdits.Add(TEdit(Components[I]));
end;
But keeping those tags up to date is another burden.
Alternative 3:
I suggest you use a TDBGrid which is a data-component. Opening the linked dataset will automatically add all phone numbers to the grid. With some settings, the grid may kind of look like a couple of edits below each other.
You can, for example, use Tag property, to find needed component. Set all you TEdit's tag from 1 to 7 (or more), and find component by:
Var I: Integer;
MyEdit : TEdit;
For I = 0 To Self.ComponentCount - 1 Do
if (Self.Components[I] IS TEdit) AND (Self.Components[I] AS TEdit).Tag = YourTag
MyEdit = (Self.Components[I] AS TEdit);
You can also dynamically create so many TEdits, you need, and assign Tag property on creation, and find it this code later in runtime.
I'd suggest using DBCtrlGrid. You place your controls for one row on it, and it repeats the controls for as many rows as your data set has.
Get query result (usually using .RowCount property of TDataset return)
After getting the number of row, do iteration to make TEdit and set the text property
Here is sample of code:
...
For i:=0 to RowCount do
Begin
A:=TEdit.Create(self);
A.Parent:=AForm;
A.Top:=i*14;
A.Text:=ADataset.Field(i).AsString;
End;
...