Stored procedure is too slow in mysql - mysql

I have a routine. But it' s too slow. How can I improve the query?
My records: http://www.sqlfiddle.com/#!9/14cceb/1/0
My query:
CREATE DEFINER = 'root'#'localhost'
PROCEDURE example.ssa()
BEGIN
drop table if exists gps_table;
drop table if exists exam_datas;
CREATE TEMPORARY TABLE gps_table(ID int PRIMARY KEY AUTO_INCREMENT,timei
int,
trun_date_time datetime, tadd_meter int, tin_here int null);
insert into gps_table(timei,trun_date_time,tadd_meter,tin_here) select
imei, run_date_time, add_meter, in_here from example_table;
CREATE TEMPORARY TABLE exam_datas(ID int PRIMARY KEY AUTO_INCREMENT,vimei
int, vbas_run_date_time datetime, vbit_run_date_time datetime, vdifff int);
select tin_here from gps_table limit 1 into #onceki_durum;
select count(id) from gps_table into #kayit_sayisi;
set #i = 1;
set #min_mes = 0;
set #max_mes = 0;
set #frst_id = 0;
set #imei = 0;
set #run_date_time = '0000-00-00 00:00:00';
set #run_date_time2 = '0000-00-00 00:00:00';
myloop: WHILE (#i <= #kayit_sayisi) DO
select tin_here from gps_table where id = #i into #in_here_true;
if (#in_here_true = 1) then
select id,trun_date_time, tadd_meter from gps_table where id = #i into #frst_id,#run_date_time2, #min_mes;
select id from gps_table where id > #frst_id and tin_here =0 order by id asc limit 1 INTO #id;
SET #id = #id-1;
select id, timei, trun_date_time, tadd_meter from gps_table
where id = #id and tin_here =1 limit 1 into #i, #imei, #run_date_time, #max_mes;
if(#i-#frst_id>3) then
set #i:=#i+1;
insert into exam_datas(vimei,vbas_run_date_time,vbit_run_date_time,vdifff) Values (#imei, #run_date_time2, #run_date_time, #max_mes-#min_mes);
SELECT * FROM exam_datas;
SET #asd =1;
elseif 1=1 then
set #i:=#i+1;
End if;
ELSEIF 1=1
THEN SET #i:=#i+1;
End if;
IF (#i = #kayit_sayisi)
THEN set #tamam =1; LEAVE myloop;
END IF;
END WHILE myloop;
select DISTINCT * from exam_datas;
drop table if exists exam_datas;
drop table if exists gps_table;
END
I need: id= 6 first true and id= 11 last_true
firs_trure - last_true = 304-290= 14
id=14 first true and id=18 last_true
firs_true - last_true = 332-324= 8
This routine is too slow.
MySql version is 5.7 and There are 2 milions record in the table.
UPDATE:
Query is here. HERE
Thank you #LukStorms

It's possible to get such results in 1 query.
Thus avoiding a WHILE loop over records.
This example works without using window functions. Just using variables inside the query to calculate a rank. Which is then used to get the minimums and maximums of the groups.
select
imei,
min(run_date_time) as start_dt,
max(run_date_time) as stop_dt,
max(add_meter) - min(add_meter) as diff
from
(
select imei, id, run_date_time, add_meter, in_here,
case
when #prev_imei = imei and #prev_ih = in_here then #rnk
when #rnk := #rnk + 1 then #rnk
end as rnk,
#prev_imei := imei as prev_imei,
#prev_ih := in_here as prev_ih
from example_table t
cross join (select #rnk := 0, #prev_ih := null, #prev_imei := null) vars
order by imei, id, run_date_time
) q
where in_here = 1
group by imei, rnk
having count(*) > 4
order by imei, min(id);
In the procedure such query can be used to fill that final temporary table.
A test on db<>fiddle here

Related

MySql Stored Procedure out id return null

I hava stored procedure, that insert into adress table, it has 3 in parametrs and 1 out parametr. If I call this procedure out parametr is null
CREATE DEFINER=`root`#`localhost`
PROCEDURE `insert_adress`(
in d varchar(50),
in r varchar(50),
in c varchar(50),
in adress_text varchar(255),
out id int)
BEGIN
declare disc_id int;
declare reg_id int;
declare coun_id int;
if ((select count(*) from district where district.dist like d) <> 0)
then set disc_id := (select id from district where district.dist like d);
else
insert into district(dist) values(d);
set disc_id := (select max(id) from district);
end if;
if ((select count(*) from region where region.reg like r) <> 0)
then set reg_id := (select id from region where region.reg like r);
else
insert into region(reg) values(r);
set reg_id := (select max(id) from region);
end if;
if ((select count(*) from country where country.coun like c) <> 0)
then set coun_id := (select id from country where country.coun like c);
else
insert into country(coun) values(c);
set coun_id := (select max(id) from country);
end if;
insert into adress(district,region,country,adress_text) values(disc_id,reg_id,coun_id,adress_text);
set id := (select max(id) from adress);
END
I call it:
set #id = 0;
call mustaqil.insert_adress('test1', 'test1', 'test1', 'test1', #id);
select #id;
District,Region,Country tables have 'test1' row
I except that, out id, disc_id, reg_id, coun_id should be number, but it is null
P.S : stored procedure inserts row
just change set id := (select max(id) from adress);
to
select max(id) into id from adress;
it should work

Trigger Not Calculating Sum Properly MySQL

I have a trigger that calculates the sum using a previous row and a current row and outputs the sum in balance field of the current row,The problem is that it doesnt seem to calculate the sum of the first row inserted into the table my trigger is:
delimiter $$
CREATE TRIGGER `ledger_calc` AFTER INSERT ON `sp_records`
FOR EACH ROW BEGIN
SET #PrevBal := (SELECT balance FROM ledger WHERE cmp_name = NEW.cmp_name AND inv_for = NEW.inv_for ORDER BY id DESC
LIMIT 1);
SET #CmpName := NEW.cmp_name;
SET #InvFor := NEW.inv_for;
IF (NEW.type = 'sales') THEN
SET #Type := 'sales';
INSERT INTO ledger (id,inv_for,cmp_name,date,debit,balance)
SELECT
TransactDet.id,
TransactDet.inv_for,
TransactDet.cmp_name,
TransactDet.date,
TransactDet.tot_amnt,
#PrevBal := #PrevBal + TransactDet.tot_amnt as balance
from
( select
Transact.id,
Transact.date,
Transact.tot_amnt,
Transact.cmp_name,
Transact.inv_for
from
sp_records Transact
where Transact.type = #Type
AND Transact.inv_for = #InvFor
AND Transact.cmp_name = #CmpName
order by
Transact.id DESC
limit 1 ) as TransactDet;
ELSE
SET #Type := 'purchase';
INSERT INTO ledger (id,inv_for,cmp_name,date,credit,balance)
SELECT
TransactDet.id,
TransactDet.inv_for,
TransactDet.cmp_name,
TransactDet.date,
TransactDet.tot_amnt,
#PrevBal := #PrevBal - TransactDet.tot_amnt as balance
from
( select
Transact.id,
Transact.date,
Transact.tot_amnt,
Transact.cmp_name,
Transact.inv_for
from
sp_records Transact
where Transact.type = #Type
AND Transact.inv_for = #InvFor
AND Transact.cmp_name = #CmpName
order by
Transact.id DESC
limit 1) as TransactDet;
END IF;
END;
#
This query returned what i want.. but it doesnt seem to convert to a trigger...
SELECT
PreAgg.id,
PreAgg.tot_amnt,
#PrevBal := #PrevBal + PreAgg.tot_amnt as balance
from
( select
YT.id,
YT.tot_amnt
from
sp_records YT
order by
YT.id ) as PreAgg,
( select #PrevBal := 0.00 ) as SqlVars
Please have a try with this one:
delimiter $$
CREATE TRIGGER `ledger_calc` BEFORE INSERT ON `sp_records`
FOR EACH ROW
BEGIN
SET #PrevBal : = (
SELECT balance
FROM ledger
WHERE cmp_name = NEW.cmp_name
AND inv_for = NEW.inv_for
ORDER BY id DESC LIMIT 1
);
INSERT INTO ledger (id, inv_for, cmp_name, DATE, debit, balance)
SELECT Transact.id, Transact.inv_for, Transact.cmp_name, Transact.DATE, Transact.tot_amnt, #PrevBal + Transact.tot_amnt AS balance
FROM sp_records Transact
WHERE Transact.type = NEW.type
AND Transact.inv_for = NEW.inv_for
AND Transact.cmp_name = NEW.cmp_name
ORDER BY Transact.id DESC limit 1;
END $$
I don't know, what your table structure looks like, but you might as well forget about the trigger and just do
INSERT INTO ledger (id, inv_for, cmp_name, DATE, debit, balance)
SELECT t.id, t.inv_for, t.cmp_name, t.DATE, t.tot_amnt, l.balance + t.tot_amnt AS balance
FROM sp_records t
INNER JOIN ledger l ON t.inv_for = l.inv_for AND t.cmp_name = l.cmp_name
WHERE t.type = 'a_value'
AND t.inv_for = 'another_value'
AND t.cmp_name = 'yet_another_value'
ORDER BY t.id DESC limit 1;
or as a trigger
DELIMITER $$
CREATE TRIGGER `ledger_calc` AFTER INSERT ON `sp_records`
FOR EACH ROW
BEGIN
INSERT INTO ledger (id, inv_for, cmp_name, DATE, debit, balance)
SELECT t.id, t.inv_for, t.cmp_name, t.DATE, t.tot_amnt, l.balance + t.tot_amnt AS balance
FROM sp_records t
INNER JOIN ledger l ON t.inv_for = l.inv_for AND t.cmp_name = l.cmp_name
WHERE t.type = NEW.type
AND t.inv_for = NEW.inv_for
AND t.cmp_name = NEW.cmp_name
ORDER BY t.id DESC limit 1;
END $$
If all this doesn't help, sample data and desired result would be helpful, preferably on http://sqlfiddle.com

Mysql procedure increment variable

I'm trying to get rank of user in the table with stored time.
RAW SQL query is working fine but I can't make it work as procedure.
SET #rownum := 0;
SELECT rank, user_id, best_time
FROM (
SELECT #rownum := #rownum +1 AS rank,id, best_time, user_id
FROM user_round WHERE round_id=1 ORDER BY best_time ASC
) AS result WHERE user_id = 1
My try to procedure:
BEGIN
DECLARE variable INT DEFAULT 0;
SELECT rank,best_time, user_id
FROM (
SELECT SET variable=variable+1 AS rank, best_time, user_id
FROM database.user_round WHERE round_id=1 ORDER BY best_time ASC
) AS result WHERE user_id = 1;
END
You need to continue using a 9.4. User-Defined Variables, not a 13.6.4.1. Local Variable DECLARE Syntax:
BEGIN
-- DECLARE variable INT DEFAULT 0;
SELECT rank, best_time, user_id
FROM (
-- SELECT SET variable = variable + 1 AS rank, best_time, user_id
SELECT #variable := #variable + 1 AS rank, best_time, user_id
FROM database.user_round, (SELECT #variable := 0) init
WHERE round_id = 1
ORDER BY best_time ASC
) AS result
WHERE user_id = 1;
END

MySQL - Insert multiple rows based on column value

I have the query working, just wondering if there is a better way to do this without cursors/loops/php side. I've been a DBA for 5+ years and just came across the := statement. Very cool.
Table (tblPeople) with the person ID and the number of tickets they bought.
PersonId NumTickets
1 3
2 1
3 1
I then want to assign individual tickets to each person in a new table (tblTickets), depending on how many tickets they bought. The TicketId is a key, auto increment column.
TicketId PersonId
100 1
101 1
102 1
103 2
104 3
Here is the code. It loops through the whole tblPeople over and over again incrementing a new calculated column called rowID. Then I filter out the rows based on the number of tickets they bought in the WHERE clause. The problem I see is the subquery is huge, the more people I have, the bigger the subquery gets. Just not sure if there is a better way to write this.
INSERT INTO tblTickets (PersonId)
SELECT PersonId
FROM (
SELECT s.PersonId, s.NumTickets,
#rowID := IF(#lastPersonId = s.PersonId and #lastNumTickets = s.NumTickets, #rowID + 1, 0) AS rowID,
#lastPersonId := s.PersonId,
#lastNumTickets := s.NumTickets
FROM tblPeople m,
(SELECT #rowID := 0, #lastPersonId := 0, #lastNumTickets := 0) t
INNER JOIN tblPeople s
) tbl
WHERE rowID < NumTickets
I'd add a utility table Numbers which contains all the numbers from 1 up to the maximal number of tickets a person may buy. Then you can do something like this:
INSERT INTO tblTickets (PersonId)
SELECT s.PersonId
FROM tblPeople s, Numbers n
WHERE n.number <= s.NumTickets
Following Stored procedure will serve your purpose...
DELIMITER $$
USE <your database name> $$
DROP PROCEDURE IF EXISTS `update_ticket_value2`$$
CREATE PROCEDURE `update_ticket_value2`()
BEGIN
DECLARE index_value INT;
DECLARE loop_variable INT;
SET #KeyValue = 100;
SET #LastPersonID = 0;
SET #TicketNum = 0;
SET #PersonIDToHandle = 0;
SELECT #PersonIDToHandle = PersonID, #TicketNum = NumTickets
FROM tblPeople
WHERE PersonId > #LastPersonID
ORDER BY PersonId
LIMIT 0,1;
WHILE #PersonIDToHandle IS NOT NULL
DO
SET loop_variable = 0;
WHILE(loop_variable < #TicketNum) DO
INSERT INTO tblTickets(TicketId, PersonId) VALUES(#KeyValue + loop_variable, #PersonIDToHandle);
SET loop_variable = loop_variable + 1;
END WHILE;
SET #LastPersonID = #PersonIDToHandle;
SET #PersonIDToHandle = NULL;
SET #KeyValue = #KeyValue + #TicketNum;
SELECT #PersonIDToHandle := PersonID, #TicketNum := NumTickets
FROM tblPeople
WHERE PersonId > #LastPersonID
ORDER BY PersonId
LIMIT 0,1;
END WHILE;
END$$
DELIMITER ;
Call the procedure as:
CALL update_ticket_value2();
Hope it helps...

SQL SERVER generating row number based on criteria

I have a table which has columns UserID,DIFFTIME. when I select these columns from the table, I also want to have a derived column which is : If the DiffTime is > 20 I want to increment the count per user id.
For example if the table has:
User ID DIFF TIME
1 0
1 5
1 10
2 0
2 21
2 5
I want a result set that is something like this:
User ID DIFF TIME SESSION NUMBER
1 0 1
1 5 1
1 10 1
2 0 1
2 21 2
2 5 2
How do I accomplish this.
Ideas and suggestions are much appreciated!
Use this statement:
select t1.User_Id, t1.Diff_Time,
isnull(count(t2.User_Id), 0) + 1 as Session_Number
from #table t1
left join #table t2
on t1.User_Id = t2.User_Id
and t1.eventTime >= t2.eventTime
and t2.Diff_Time > 20
group by t1.User_Id, t1.Diff_Time, t1.eventTime
order by t1.User_Id, t1.eventTime
(replace #table with your actual table name)
Note: I assume that the fifth row of your table has the value 21 in the Diff_Time column, and there's a typo in the question, as #AaronBertrand pointed out in the comments
create table #t
(
id int,
Diff int,
SessionNumber int
)
insert into #t(id, diff)values(1, 0)
insert into #t(id, diff)values(1, 5)
insert into #t(id, diff)values(1, 10)
insert into #t(id, diff)values(2, 0)
insert into #t(id, diff)values(2, 21)
insert into #t(id, diff)values(2, 5)
Select ROW_NUMBER() over(order by Id) as RowID, * into #Temp1 from #t
Declare #diff int
Declare #RowId int
Declare #Previous int
Declare #NewValue int
DECLARE #Cur CURSOR SET #Cur = CURSOR FOR select RowId, diff from #Temp1
OPEN #Cur
FETCH NEXT FROM #Cur INTO #RowId, #diff
WHILE ##FETCH_STATUS = 0
BEGIN
if(#RowId = 1)
Begin
Update #Temp1 Set SessionNumber = 1 Where rowid = 1
Set #Previous = #Diff
Set #NewValue = 1
End
Else
Begin
if(#Diff - #Previous > 20)
Begin
Set #Previous = #Diff
Set #NewValue = #NewValue + 1
Update #Temp1 Set SessionNumber = #NewValue Where rowid = #RowId
End
else
Update #Temp1 Set SessionNumber = #NewValue Where rowid = #RowId
End
FETCH NEXT FROM #Cur INTO #RowId, #diff
END
CLOSE #Cur
DEALLOCATE #Cur
select * from #temp1
drop table #t
drop table #temp1