Problem with increment variable and stored procedure to check duplicates - mysql

I have problem with my procedure. I have table oferty_in which contain fields (id, status, ..., id_om). I want procedure which check if exist rows with the same id_om.
If exist, delete rows where status = 'N' (N - new).
My procedure almost works, but i have problem with iterate in loop. Every time I run my procedure ,procedure delete a half of rows. I don't know where is problem...
DELIMITER //
CREATE PROCEDURE check_duplicates_oferty_in()
BEGIN
DECLARE n INT DEFAULT 0;
DECLARE i INT DEFAULT 0;
DECLARE v_id_del BIGINT;
SELECT count(*) INTO n FROM oferty_in where status_oferty = 'N';
SET i=0;
WHILE i<n DO
IF EXISTS (SELECT id_om FROM oferty_in group by id_om having count(*) >= 2 LIMIT i,1) THEN
SELECT id_om INTO v_id_del FROM oferty_in group by id_om having count(*) >= 2 LIMIT i,1;
DELETE from oferty_in where id_om = v_id_del and status_oferty = 'N';
END IF;
SET i=i+1;
END WHILE;
END
//
I try also:
IF EXISTS (SELECT id_om FROM oferty_in group by id_om having count(*) >= 2 LIMIT i,1) THEN
SELECT id_om INTO v_id_del FROM oferty_in group by id_om having count(*) >= 2 LIMIT i,1;
DELETE from oferty_in where id_om = v_id_del and status_oferty = 'N';
SET i=i+1;
ELSE
SET i=i+1;
END IF;
But it's the same.
Every time half of rows. I use counter 'i' and while loop to iterate row by row on rows in oferty_in when status = 'N'. Anyone have a idea what I did wrong? Thanks for help and time.

You seem to want to delete rows with status = 'N' when id_om is duplicated.
I want procedure which check if exist rows with the same id_om. If exist, delete rows where status = 'N' (N - new).
Non-working code doesn't generally help explain logic, so this is what I am going by.
You definitely do not need a looping construct for this, nor a cursor:
delete o
from oferty_in o join
(select o2.id_om
from oferty_in o2
group by o2.id_om
having count(*) > 1 and sum(status = 'N') > 0
) o2
on o.id_om = o2.id_om
where o.status = 'N';

Related

How to delete duplicate rows if they are odd else keep one

Its a card like game.
my table is
Number
Symbol
Player
1
C
F
1
S
F
1
D
F
1
H
F
2
S
F
2
C
F
2
D
F
3
H
F
2
H
S
3
S
S
I am trying to remove all the 1 cards from Player F because he have an even number of those cards and he have collect all the cards.
And i want to remove only 2 rows of the 2 cards from Player F because he have an odd number of those and the last 2 its on the S player
I am trying to create a procedure for this i only manage to keep one row of the player with this
PROCEDURE `deleteDupl`()
BEGIN
DELETE c1 FROM cards c1, cards c2 WHERE c1.Symbol > c2.Symbol AND c1.Number = c2.Number AND c1.Player = c2.Player;
END
--edit
the point of the game is to pick cards from your opponent and once you have 2 same cards(the number of card not the symbol) you drop them (no matter what symbol just random drop 2 of the same numbers)
but in the start of the game you might get more than 2 of the same cards like the F player have all the aces so he have to drop them all
or like the F player have three times the 2 card he must drop two cards (no matter what symbol) until he pick the card with number 2 from his opponent
You have not said which MySQL version you are running. This stored procedure example works on MySQL 5.6. It runs a simple GROUP BY query to get all Number, Player groups with more than 1 card. It then loops over the cursor and runs a delete for each row returned.
CREATE PROCEDURE `sp_DeletePairs`()
BEGIN
DECLARE done BOOLEAN DEFAULT FALSE;
DECLARE _number TINYINT UNSIGNED;
DECLARE _player CHAR(20);
DECLARE _count TINYINT UNSIGNED;
DECLARE `cur` CURSOR FOR
SELECT `Number`, `Player`, COUNT(*) AS `num`
FROM `cards`
GROUP BY `Number`, `Player`
HAVING `num` > 1;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cur;
read_loop: LOOP
FETCH cur INTO _number, _player, _count;
IF done THEN
LEAVE read_loop;
END IF;
CASE
WHEN _count IN (2, 3) THEN
DELETE FROM `cards` WHERE `Number` = _number AND `Player` = _player LIMIT 2;
WHEN _count = 4 THEN
DELETE FROM `cards` WHERE `Number` = _number AND `Player` = _player LIMIT 4;
END CASE;
END LOOP;
CLOSE cur;
END
Obviously, you can wrap the following DELETE query examples in stored procedures if you so desire.
If you are on MySQL 8.0 or later you can do it using window functions -
WITH `stats` AS (
SELECT `Number`, `Symbol`, `Player`,
ROW_NUMBER() OVER (PARTITION BY `Number`, `Player` ORDER BY `Player`, `Number`, `Symbol`) AS `seq`,
COUNT(*) OVER (PARTITION BY `Number`, `Player`) AS `count_numbers`
FROM cards
)
DELETE `c`
FROM `cards` `c`
INNER JOIN `stats` `s`
ON `c`.`Number` = `s`.`Number`
AND `c`.`Symbol` = `s`.`Symbol`
AND `c`.`Player` = `s`.`Player`
WHERE `s`.`count_numbers` = 4
OR (`s`.`count_numbers` IN (2, 3) AND `s`.`seq` IN (1, 2));
Within the CTE, the ROW_NUMBER() is giving us a cumulative count within the Number, Player PARTITION. The COUNT(*) is giving us the total within the Number, Player PARTITION. We can then join between stats (the CTE) and cards on all three of the original columns. Finally, we use the WHERE clause to decide which cards to remove.
A similar approach can be taken in MySQL < 8.0, using variables for the sequence and a join to another derived table to get the count per group -
DELETE `c`
FROM `cards` `c`
INNER JOIN (
SELECT
`c`.`Number`,
`c`.`Symbol`,
`c`.`Player`,
IF(#prev_number = `c`.`Number` AND #prev_player = `c`.`Player`, #row := #row + 1, #row := 1) AS `seq`,
`counts`.`count_numbers`,
#prev_number := `c`.`Number`,
#prev_player := `c`.`Player`
FROM `cards` `c`
JOIN (SELECT #row := 0, #prev_number := 0, #prev_player:=0) t
INNER JOIN ( SELECT `Player`, `Number`, COUNT(*) AS `count_numbers` FROM `cards` GROUP BY `Player`, `Number`) AS `counts`
ON `c`.`Player` = `counts`.`Player`
AND `c`.`Number` = `counts`.`Number`
ORDER BY `c`.`Player`, `c`.`Number`
) `s`
ON `c`.`Number` = `s`.`Number`
AND `c`.`Symbol` = `s`.`Symbol`
AND `c`.`Player` = `s`.`Player`
WHERE `s`.`count_numbers` = 4
OR (`s`.`count_numbers` IN (2, 3) AND `s`.`seq` IN (1, 2));
I am definitely not suggesting using this last example, at least not in a production environment. I just included as it might be interesting to someone.

Do while in SQL procedure, limit by i

i came across a procedure with this code
DELIMITER $$
Create PROCEDURE Procedure1 (IN A int)
BEGIN
DECLARE n int DEFAULT 0;
DECLARE i int DEFAULT 0;
DECLARE column_int int DEFAULT NULL;
SELECT
COUNT(1)
FROM table1 t1
WHERE t1.id = A INTO n;
WHILE i < n DO
SELECT
t1.B
FROM table1 t1
WHERE t1.id = A
ORDER BY t1.id
LIMIT i, 1 INTO column_int;
CALL Procedure2(A, column_int);
SET i = i + 1;
END WHILE;
END
$$
DELIMITER;
i don't understand the while part of the code, if we are limiting by i , the first run should give no records ( as i would be 0), and since n will be 1 ( from the first part of the query where count(1) is inserting in n ) how will i<n work ? how will limit i work here ?
i is being used to set the offset so it stepping through table1 1 row at a time until i reaches number of rows in table.
the full form of limit is limit offset,number

Mysql On trigger each row

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

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

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...