How to speed up stored procedure in mysql - mysql

Below given is my procedure takes too much time to execute.
BEGIN
DECLARE rank1 BIGINT DEFAULT 0;
DECLARE id1 BIGINT;
DECLARE rankskip BIGINT DEFAULT 0;
DECLARE mark DECIMAL(10,2) DEFAULT 0;
DECLARE oldmark DECIMAL(10,2) DEFAULT -100000;
DECLARE done int DEFAULT 0;
DECLARE cursor_i CURSOR FOR
SELECT
(rightmarks - negativemarks) as mark, id
FROM
testresult
WHERE
testid = testid1
ORDER BY
(rightmarks - negativemarks) DESC;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
OPEN cursor_i;
read_loop: LOOP
FETCH cursor_i INTO mark, id1;
IF done = 1 THEN
LEAVE read_loop;
END IF;
IF oldmark = mark THEN
BEGIN
IF IsRankSkip = 1 THEN
BEGIN
SET rankskip = rankskip + 1;
END;
END IF;
END;
ELSE
BEGIN
SET rank1 = rank1 + rankskip + 1;
SET rankskip = 0;
END;
END IF;
SET oldmark = mark;
UPDATE testresult SET rank = rank1 WHERE id=id1;
END LOOP;
CLOSE cursor_i;
END
This loop iterate minimum 2000 times.
Here IsRankSkip and testid1 is an argument passed to the procedure.
This procedure takes 65.343152046204 time to execute. If anybody guide me how can I reduce executing time?
Thank you in advance.

You can do this with a single update statement, making use of variables that change during the execution of it:
UPDATE testresult a
JOIN ( SELECT id,
#row := #row + 1 row_number,
#rank := if(mark = #lastmark, #rank, #row) as rank,
#dense_rank := #dense_rank + if(mark = #lastmark, 0, 1) as dense_rank,
#lastmark := mark as mark
FROM ( SELECT rightmarks - negativemarks as mark,
id
FROM testresult
WHERE testid = testid1
ORDER BY 1 DESC
) data,
(SELECT #row := 0, #dense_rank := 0) r
) b
ON a.id = b.id
SET a.rank = if(IsRankSkip, b.rank, b.dense_rank);
The query with alias b calculates the rank and adds it as a column in the result set. In fact, it adds three kinds of numbers:
#row: the sequential row number without special treatment of equal values
#rank: the same as row number of the value differs from the previous value, otherwise it is the same as in the previous row
#dense_rank: this increments when the value differs from the previous value, otherwise it is the same as in the previous row
You can choose which one to update your rank column with. In the above SQL I have used the two procedure variables IsRankSkip and testid1.
Remark
If you are always calling your procedure for all testid values, then the above can be further improved, so all these updates are done with only one update statement.

Related

I have a table with a set of data and I would want to display specific records from it using Mysql:

Here is the table: Sorry couldn't depict the table properly here.
Id Date1 Rank1 date2 Rank2
100 1/1/01 1 1/2/01 1
100 1/2/01 1 1/3/01 1
100 1/3/01 1 1/5/01 4
Now, I would want to see the result where the rank changes but the date1 should display the date from which Rank = 1 started,
An output like this:
Id Date1 Rank1 date2 Rank2
100 1/1/01 1 1/5/01 4
Can anyone please let me know how to go about doing this in MYSQL? I thought about using SQL cursor.Is there another way?
I will create a stored procedure and a cursor to iterate through each record.
My stored procedure will look as follows:
CREATE PROCEDURE `GetChangedRank`()
BEGIN
# Variables containing the final result
DECLARE ID INTEGER;
DECLARE DATE1 DATE;
DECLARE Rank1 INTEGER;
DECLARE DATE2 DATE;
DECLARE Rank2 INTEGER;
DECLARE ROWNUM INTEGER DEFAULT 0;
# Temporary variables to store values of each row.
DECLARE v_finished INTEGER DEFAULT 0;
DECLARE ID_VALUE INT;
DECLARE DATE1_VALUE DATE;
DECLARE Rank1_VALUE INT;
DECLARE DATE2_VALUE DATE;
DECLARE Rank2_VALUE INT;
# testtable is the table that contains data
DECLARE RECORDS_CURSOR CURSOR FOR SELECT * FROM testtable;
# To check if we reached the end of the result set
DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_finished = 1;
# Open cursor
OPEN RECORDS_CURSOR;
# Loop to fetch one row at a time
get_records: LOOP
# Fetch row values to different variables
FETCH RECORDS_CURSOR INTO ID_VALUE, DATE1_VALUE, Rank1_VALUE, DATE2_VALUE, Rank2_VALUE
# To track the row number
SET ROWNUM = ROWNUM + 1;
# Quit the loop when the end of resultset is reached.
IF v_finished = 1 THEN
LEAVE get_records;
END IF;
# Assign the first row values to the final values. Date2 and Rank2 will be updated when the rank is changed.
IF ROWNUM = 1 THEN
BEGIN
SET ID = ID_VALUE;
SET DATE1 = DATE1_VALUE;
SET Rank1 = Rank1_VALUE;
SET DATE2 = DATE2_VALUE;
SET Rank2 = Rank2_VALUE;
END;
ELSE
BEGIN
# Date2 and Rank2 is updated with the row values as the rank is changed.
IF Rank1_VALUE != Rank2_VALUE THEN
BEGIN
SET DATE2 =DATE2_VALUE;
SET Rank2 = Rank2_VALUE;
END;
END IF;
END;
END IF;
END LOOP get_records;
# Select the final values
SELECT ID, DATE1, Rank1, DATE2, Rank2;
# Close cursor
CLOSE RECORDS_CURSOR;
END
Once the procedure is created, you can run this procedure as follow
CALL GetChangedRank;
Is this what you want?
SELECT * FROM <table name>
WHERE Rank1 <> Rank2
Try this nested query
Select Id, (select Date1 from TableName group by Rank1) as Date1, Rank1, Date2, Rank2 from TableName where Rank1<>Rank2;

how to apply while loop for loop query in mysql

CREATE DEFINER=`root`#`localhost` PROCEDURE `test4`()
BEGIN
declare GroupArray text;
DECLARE i INT DEFAULT 0;
DECLARE loopcount INT DEFAULT 0;
declare GroupId varchar (5);
set GroupArray=(select group_concat(distinct groupId) from EventList_View);
SET loopcount = (SELECT LENGTH(GroupArray) -
LENGTH(REPLACE(GroupArray,',', '')));
select loopcount;
WHILE i <= loopcount DO
SET i = i + 1;
SET GroupId = (SELECT SUBSTRING_INDEX(SUBSTRING_INDEX(GroupArray,
',', i),',', -1));
select GroupId;
END WHILE;
END
this is my query i am trying to apply while loop and i want get all groupId but GroupArray [1,2,3]i am getting value but in loop count i am getting 0 can any one please tell me where am doing wrong . why i am unable to get loop count null and why i am not getting value 1,2,3 for group

MySQL If Statement and Increment

I am having issues with a MySQL If statement that creates a group rank. here is the MySQL Statement:
SELECT EnCode, EnName, QuScore,
#scorerank := IF(#currathlete = EnCode, #scorerank + 1, 1),
#currathlete := EnCode
FROM ranking ORDER BY EnCode, QuScore DESC
It currently gives the following output
'1004277','Ashe','1628','1','1004277'
'1004277','Ashe','1309','1','1004277'
'1004277','Ashe','1263','1','1004277'
'1004277','Ashe','648','1','1004277'
'1004277','Ashe','645','1','1004277'
'1004277','Ashe','1628','1','1004277'
'1015934', 'Sabina', '544', '1', '1015934'
'1015934', 'Sabina', '455', '1', '1015934'
'1015934', 'Sabina', '276', '1', '1015934'
'1015934', 'Sabina', '216', '1', '1015934'
What it should be doing is incrementing each of the '1' numbers by one for each row that has the same code, and then starting from 1 again when it sees a different code number (1004277, then 1015934 in this case)
Any help is appreciated as i have followed a number of examples online using the above method but seem to hit the same issue a this point.
Try this way in stored Procedure:
drop PROCEDURE if EXISTS INCREMENTME;
create PROCEDURE INCREMENTME()
BEGIN
DECLARE OldEnNamevar VARCHAR(10) DEFAULT NULL;
DECLARE done INT DEFAULT FALSE;
DECLARE Encodevar VARCHAR(10);
DECLARE EnNamevar VARCHAR(10);
DECLARE QuScorevar VARCHAR(10);
DECLARE scorerankvar VARCHAR(10);
DECLARE currathalthletevar VARCHAR(10);
DECLARE countcode int(29) DEFAULT(1);
DECLARE counter int(20) default 0;
DECLARE get_cur CURSOR FOR select `Encode`,`EnName`,`QuScore`,`scorerank`,`currathalthlete` from tbl_ranking;
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done=1;
drop table if exists temp_temptable;
create TEMPORARY table temp_temptable(Encodevar VARCHAR(50) NULL,EnNamevar VARCHAR(50) NULL,QuScorevar VARCHAR(50) NULL,scorerankvar VARCHAR(50) NULL,currathalthletevar VARCHAR(50) NULL,recordCount int(10) null);
OPEN get_cur;
REPEAT
set counter = counter + 1;
FETCH get_cur INTO Encodevar,EnNamevar,QuScorevar,scorerankvar,currathalthletevar;
if (OldEnNamevar = EnNamevar) THEN
set countcode = countcode +1;
ELSE
if(counter=1) then
set countcode = 1;
ELSE
set countcode = 0;
end if;
end if;
if (OldEnNamevar != EnNamevar) THEN
set countcode = 1;
end if;
if(OldEnNamevar=NULL) then
set countcode = 1;
end if;
insert into temp_temptable (Encodevar,EnNamevar,QuScorevar,scorerankvar,currathalthletevar,recordCount) values(Encodevar,EnNamevar,QuScorevar,scorerankvar,currathalthletevar,countcode);
set OldEnNamevar = EnNamevar;
UNTIL done END REPEAT;
select * from temp_temptable;
drop temporary table if exists temp_temptable;
CLOSE get_cur;
END
call the procedure like this:
call INCREMENTME();
Here's the result:
You have to initialize your variables, otherwise they are null (at least at the beginning of the session, probably not anymore if you run it twice), and your query will give strange results. Try
SELECT EnCode, EnName, QuScore,
#scorerank := IF(#currathlete = EnCode, #scorerank + 1, 1),
#currathlete := EnCode
FROM ranking, (select #currathlete := '', #scorerank := 0) init
ORDER BY EnCode, QuScore DESC

1172 - Result consisted of more than one row in mysql

How can I solve this problem (Result consisted of more than one row in mysql)
DROP PROCEDURE IF EXISTS `doMarksApplication`;
CREATE PROCEDURE `doMarksApplication`(
in kuser varchar(20),
out idpro int(11))
SP:BEGIN
declare no_more_rows int default FALSE;
declare total_marks decimal(10,2) default 0;
declare idfor int(11) default 0;
declare sskod int(5) default getCurSession();
declare bdata int(5) default 0;
declare nopmh varchar(20);
# Data PB [Permohonan Baru] DM [Proses Pemarkahan]
declare cur1 cursor for
select ind_nopmh from pinduk
left join pprses on pro_nopmh = ind_nopmh
where ind_sskod = sskod and
concat(pro_stats,pro_statp) in ('PB','DM') and
not exists (select mar_idnum from pmrkah where mar_nopmh = ind_nopmh)
order by ind_nopmh;
declare continue handler for not found set no_more_rows = TRUE;
begin
select count(ind_nopmh) into bdata
from pinduk
left join pprses on pro_nopmh = ind_nopmh
where ind_sskod = sskod and
concat(pro_stats,pro_statp) in ('PB','DM') and
not exists (select mar_idnum from pmrkah where mar_nopmh = ind_nopmh);
end;
begin
select count(for_idnum) into idfor from xkod_markah_00_formula
where for_stats = 'A' and
curdate() between for_tkhdr and for_tkhhg;
end;
if idfor = 1 and sskod <> 0 then
begin
select for_idnum into idfor from xkod_markah_00_formula
where for_stats = 'A' and
curdate() between for_tkhdr and for_tkhhg;
end;
begin
insert into pprmar
(pma_tkmla,pma_msmla,pma_puser,pma_sskod,pma_idfor,pma_bdata)
values
(curdate(),curtime(),kuser,sskod,idfor,bdata);
end;
begin
select last_insert_id() into idpro;
end;
open cur1;
LOOP1:loop
fetch cur1 into nopmh;
if no_more_rows then
close cur1;
leave LOOP1;
end if;
begin
call getMarksAnakPerak(nopmh,#total_perak);
call getMarksAkademik(nopmh,#total_akdmk);
call getMarksSosioekonomi(nopmh,#total_sosio);
end;
set total_marks = #total_perak + #total_akdmk + #total_sosio;
begin
insert into pmrkah
(mar_idpro,mar_nopmh,mar_idfor,mar_perak,mar_akdmk,mar_sosio,mar_total)
values
(idpro,nopmh,idfor,#total_perak,#total_akdmk,#total_sosio,total_marks);
end;
begin
update pprses
set pro_stats = 'D',
pro_statp = 'M',
pro_tkmsk = curdate(),
pro_msmsk = curtime(),
pro_kuser = kuser
where pro_nopmh = nopmh;
end;
end loop;
begin
update pprmar
set pma_tktmt = curdate(),
pma_mstmt = curtime()
where pma_idnum = idpro;
end;
end if;
END;
i have been programming in mysql for 15 years and this is easily the most confusing stored procedure i have ever seen.
None the less, one possible place for your issue is here
select for_idnum into idfor from xkod_markah_00_formula
where for_stats = 'A' and
curdate() between for_tkhdr and for_tkhhg;
I know it does not seem to be the reason but without knowing the content of the other three stored procedures you are calling this is the only candidate. You should add a limit 1 to it, and to every select into statement that reads from a table (i.e. not a sum() or a count() etc...) as that would always have the potential to cause the error you are seeing.
select for_idnum into idfor from xkod_markah_00_formula
where for_stats = 'A' and
curdate() between for_tkhdr and for_tkhhg limit 1;
In addition, you should comment out the three stored procedure calls and see if the error goes away. My guess is that the issue is one of those stored procedures due to a select into similar to above has more than one row in the result set but does not use limit 1 and does not filter properly.

MySQL limit by sum

I want to limit my SELECT results in mySQL by sum.
For Example, this is my table:
(id, val)
Data Entries:
(1,100),
(2,300),
(3,50),
(4,3000)
I want to select first k entries such that the sum of val in those entries is just enough to make it to M.
For example, I want to find entries such that M = 425.
The result should be (1,100),(2,300),(3,50).
How can I do that in a mysql select query?
Try this variant -
SET #sum = 0;
SELECT id, val FROM (
SELECT *, #sum:=#sum + val mysum FROM mytable2 ORDER BY id
) t
WHERE mysum <= 450;
+------+------+
| id | val |
+------+------+
| 1 | 100 |
| 2 | 300 |
| 3 | 50 |
+------+------+
This stored procedure might help:
DELIMITER ;;
CREATE PROCEDURE selectLimitBySum (IN m INT)
BEGIN
DECLARE mTmp INT DEFAULT 0;
DECLARE idTmp INT DEFAULT 0;
DECLARE valTmp INT DEFAULT 0;
DECLARE doneLoop SMALLINT DEFAULT 0;
DECLARE crsSelect CURSOR FOR SELECT id, val FROM test3;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET doneLoop = 1;
OPEN crsSelect;
aloop: LOOP
SET idTmp = 0;
SET valTmp = 0;
FETCH crsSelect INTO idTmp, valTmp;
if doneLoop THEN
LEAVE aloop;
END IF;
SELECT idTmp, valTmp;
SET mTmp = mTmp + valTmp;
if mTmp > m THEN
LEAVE aloop;
END IF;
END LOOP;
CLOSE crsSelect;
END ;;
DELIMITER ;
Please feel free to change the table names or variable names as per your needs.
from mysql reference manual:
The LIMIT clause can be used to constrain the number of rows returned by the SELECT statement. LIMIT takes one or two numeric arguments, which must both be nonnegative integer constants (except when using prepared statements).
So you cannot use limit the way you proposed. To achieve what you want you need to use your application (java, c, php or whatever else), read the result set row by row, and stop when your condition is reached.
or you can use a prepared statement, but anyway you cant have conditional limit (it must be a constant value) and it is not exactly what you asked for.
create table #limit(
id int,
val int
)
declare #sum int, #id int, #val int, #m int;
set #sum=0;
set #m=250; --Value of an entry
declare limit_cursor cursor for
select id, val from your_table order by id
open limit_cursor
fetch next from limit_cursor into #id, #val
while(##fetch_status=0)
begin
if(#sum<#m)
begin
set #sum = #sum+#val;
INSERT INTO #limit values (#id, #val);
fetch next from limit_cursor into #id, #val
end
else
begin
goto case1;
end
end
case1:
close limit_cursor
deallocate limit_cursor
select * from #limit
truncate table #limit