Mysql On trigger each row - mysql

i need help for the below code. My problem is that i want only that code executes once per statement (after i search i checked that expression don't exists anymore only once per row).
So i tried to add:
IF NOT EXISTS
(Select count(*) FROM replay_replays_access WHERE id_game = new.id_game GROUP BY id_game HAVING count(*) <5)
THEN
But didn't work either what can i do, its duplicating sometime triplicating the information?
TRIGGER replay
AFTER UPDATE
ON table_replays FOR EACH ROW
begin
IF EXISTS
(SELECT
replay_games.room_name
FROM replay_games
WHERE replay_games.room_name = 'Tournament Room' and replay_games.id = new.id_game)
THEN
IF NOT EXISTS
(Select
count(*)
FROM replay_replays_access
WHERE id_game = new.id_game
GROUP BY id_game
HAVING count(*) <5)
THEN
INSERT INTO replay_replays_access(id_game, id_player, replay_name, do_not_hide)
SELECT
new.id_game,
replay_users.id ,
CONCAT(
(SELECT game_types
FROM replay_games
WHERE id=new.id_game),
': ',
(SELECT
descr
FROM replay_games
WHERE id=new.id_game)) ,
0
FROM replay_users
WHERE
(replay_users.admin > 0 OR
replay_users.privlevel = 'TOURNAMENT MEMBER')
AND NOT replay_users.name = (
SELECT
replay_games.creator_name
FROM replay_games
WHERE replay_games.id = new.id_game);
END IF;
END IF;
END

Related

Stored procedure is too slow in 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

Appending additional SQL tigger to existing trigger

I have the following SQL update trigger that works properly:
BEGIN
DECLARE myID INT;
SELECT user_id INTO myID FROM writer WHERE writer_id = NEW.writer_id;
IF (NEW.status_id = 2) THEN
INSERT INTO activity (
user_id,
work_id,
activity,
date_created
) VALUES (
myID,
NEW.work_id,
'confirmed',
now()
);
ELSE
INSERT INTO activity (
user_id,
work_id,
activity,
date_created
) VALUES (
myID,
NEW.work_id,
'modified',
now()
);
END IF;
END
I need to add an additional trigger as the following:
CREATE TRIGGER updateWorkStatus AFTER UPDATE ON writer_split
FOR EACH ROW
BEGIN
UPDATE work a
JOIN writer_split b
ON a.work_id = b.work_id AND a.current_version = b.version
SET a.status_id = 2
WHERE a.work_id NOT IN (
SELECT ab.work_id
FROM (SELECT s.work_id
FROM work w INNER JOIN writer_split s
ON w.work_id = s.work_id AND s.status_id != 2) ab
);
END;
when I run this create script, I am getting a syntax error. Any ideas?
For me i will use UPDATE work as a

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

WHILE syntax-error in MySQL

I'm trying to use a while loop in a one-off query on a MySQL (5.1.41-3ubuntu12.10-log) database:
WHILE ((SELECT COUNT(*) FROM
(SELECT id, COUNT(*) AS cnt
FROM foo
GROUP BY id
ORDER BY COUNT(*) DESC) cnts
WHERE cnt > 1) != 0) DO
BEGIN
SET #curr_id = (SELECT id FROM
(SELECT id, COUNT(*) AS cnt
FROM foo
GROUP BY id
ORDER BY COUNT(*) DESC) cnts
WHERE cnt > 1
LIMIT 1);
SET #new_id = (SELECT MAX(id) + 1
FROM foo);
UPDATE foo
SET id = #new_id
WHERE id = #curr_id
LIMIT 1;
END WHILE;
What this does is while there are multiple records with the same id, update one of them with the next id.
The syntax in the body is correct and the predicate used in the while statement also executes without any trouble on it's own. MySQL returns a syntax error on the beginning of the query:
Error Code : 1064
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 'WHILE ((SELECT count(*) FROM
(SELECT id, COUNT(*) AS cnt
FROM stock_produ' at line 1
I realize this might not be The Right Way of doing things, but this is a very badly (or rather not-at-all) thought out database, so it would be awesome if I could get it to work this way.
Thanks,
Robin
It looks as though you are trying to run this procedural code as an anonymous block. While this works in some databases (like Oracle) it can't be done in MySQL.
If you want to run this then put it in a stored procedure and then call the procedure. Hence:
Create procedure
DELIMITER $$
CREATE PROCEDURE `foo_update_routine`()
BEGIN
WHILE ((SELECT COUNT(*) FROM
(SELECT id, COUNT(*) AS cnt
FROM foo
GROUP BY id
ORDER BY COUNT(*) DESC
) cnts
WHERE cnt > 1) != 0)
DO
SET #curr_id = (SELECT id FROM
(SELECT id, COUNT(*) AS cnt
FROM foo
GROUP BY id
ORDER BY COUNT(*) DESC
) cnts
WHERE cnt > 1
LIMIT 1);
SET #new_id = (SELECT MAX(id) + 1 FROM foo);
UPDATE foo SET id = #new_id
WHERE id = #curr_id
LIMIT 1;
END WHILE;
END $$
Call Procedure
CALL `foo_update_routine`;
PS You might want to investigate the HAVING clause for your select statements...

Query with multiple EXIST

I've got a database of rooms and equipments. I want to query the database and return a list of rooms with e.g. tv, radio, sat and fridge (eq1, eq2, eq3, ...., eqN).
I have the following SELECT statement:
select * from rooms r where
exists (select id from equipments where eq_id='eq1' and room_id=r.id)
and
exists (select id from equipments where eq_id='eq2' and room_id=r.id)
and
exists (select id from equipments where eq_id='eq3' and room_id=r.id)
.......
and
exists (select id from equipments where eq_id='eqN' and room_id=r.id)
Is there any way to optimize or making this shorter?
To shorten you could
select *
from rooms r
where #N = (select count(distinct eq_id)
from equipments
where eq_id IN ('eq1','eq2',...,'eqN') and room_id=r.id)
EDIT
but not sure if it will actually make it faster... quite the opposite, the version with EXISTS AND EXISTS has a chance to prune execution branch on the first false, the above must actually count the distinct values (go through all records) and see what that value is.
So you should think what is faster:
going once through all records related to a room (one correlated subquery) or
running N (worst case) correlated (but highly selective subqueries) for each room
It depends on the statistics of your data (I would think that if most rooms don't have all the sought equipment in them then your initial version should be faster, if most rooms have all equipment in them then the proposed version might perform better; also if the EXISTS version is faster make an effort to first the queries that are most likely to fail i.e. first check for rarest equipment)
You can also try a version with GROUP BY
select r.*
from rooms r join
equipments e on r.id = e.room_id
group by r.id
where eg_id in ('eq1','eq2',...,'eqN')
having count(distinct e.eq_id) = #N
(above SQL not tested)
try this (I don't have any DB available to test it, also consider performance )
select * from
rooms r,
(
select count(distinct id) as cnt, id from equipments where eq_id in ('eq1','eq2') group by id
) as sub
where sub.id = r.id
and sub.cnt >= 2 'Options count
Note: 2 - it is the number of options that you need. In example they are: 'eq1','eq2'
select * from rooms r where
(select count(id) from equipments where eq_id='eq1' and room_id=r.id) > 0
and
...
Use an Stored Procedure.
here is the procedure for mysql:
DELIMITER $$
CREATE DEFINER=`root`#`%` PROCEDURE `GetRooms`(IN roomtable TEXT, IN equipmenttable TEXT, IN equipments TEXT )
BEGIN
DECLARE statement text;
DECLARE Pos int;
DECLARE cond text;
DECLARE element text;
DECLARE tmpTxt text;
set tmpTxt = equipments;
set cond = "";
set Pos = instr(tmpTxt,';');
while Pos <> 0 do
set element = substring(tmpTxt, 1, Pos-1);
if cond <> "" then
set cond = concat(cond,' and ');
end if;
set cond = concat(cond,'exists (select id from ' , equipmenttable ,' where eq_id=''' , element ,''' and room_id=r.id) ');
set tmpTxt = replace(tmpTxt, concat(element,';'), '');
set Pos = instr(tmpTxt,';');
end while;
if tmpTxt <> "" then
if cond <> "" then
set cond = concat(cond,' and ');
end if;
set cond = concat(cond,'exists (select id from ' , equipmenttable ,' where eq_id=''' , tmpTxt ,''' and room_id=r.id) ');
end if;
SET #statement = concat('Select * FROM ' , roomtable , " WHERE " , cond , ";");
PREPARE stmt FROM #statement;
EXECUTE stmt;
END
Execute it with: CALL GetRooms('RoomTableName','EquipmentTableName','EquipmentIDs')
Example:
Call GetRooms('rooms','equipemnts','eq1;eq2;eq3');
Hope this helps.
To Execute Query Faster use Exists
select *
from rooms as r
where exists (
select *
from equipments
where eq_id IN ('eq1','eq2',..,'eqN') and r.id= equipments.room_id);