Reputation: 5939
I can't understand why the same query for select and delete have different behavior.
I need to delete all rows except 5 newest rows.
I know my solution for this task is no good, but my question is about why MySQL no delete the same rows, that return select for the same query clause
see code
drop table if exists tbl;
create table tbl
(
id serial,
cal date COMMENT 'some column',
created_at datetime default NOW()
);
insert into tbl
values
(default, '2018-07-15', '2018-07-15 12:00'),
(default, '2018-07-16', '2018-07-16 12:00'),
(default, '2018-07-17', '2018-07-17 12:00'),
(default, '2018-07-18', '2018-07-18 12:00'),
(default, '2018-08-01', '2018-08-01 12:00'),
(default, '2018-08-04', '2018-08-04 12:00'),
(default, '2018-08-16', '2018-08-16 12:00'),
(default, '2018-08-17', '2018-08-17 12:00');
select *
from tbl;
# +----+------------+---------------------+
# | id | cal | created_at |
# +----+------------+---------------------+
# | 1 | 2018-07-15 | 2018-07-15 12:00:00 |
# | 2 | 2018-07-16 | 2018-07-16 12:00:00 |
# | 3 | 2018-07-17 | 2018-07-17 12:00:00 |
# | 4 | 2018-07-18 | 2018-07-18 12:00:00 |
# | 5 | 2018-08-01 | 2018-08-01 12:00:00 |
# | 6 | 2018-08-04 | 2018-08-04 12:00:00 |
# | 7 | 2018-08-16 | 2018-08-16 12:00:00 |
# | 8 | 2018-08-17 | 2018-08-17 12:00:00 |
# +----+------------+---------------------+
now I need delete rows with id 1,2,3
SET @row_number = 0;
select *
from tbl
where tbl.id in (
select T.id
from (SELECT (@row_number := @row_number + 1) as num, tbl.id
from tbl
order by created_at desc
) as T
where T.num > 5);
# +----+------------+---------------------+
# | id | cal | created_at |
# +----+------------+---------------------+
# | 3 | 2018-07-17 | 2018-07-17 12:00:00 |
# | 2 | 2018-07-16 | 2018-07-16 12:00:00 |
# | 1 | 2018-07-15 | 2018-07-15 12:00:00 |
# +----+------------+---------------------+
Now I use delete operation
SET @row_number = 0;
delete
from tbl
where tbl.id in (
select T.id
from (SELECT (@row_number := @row_number + 1) as num, tbl.id
from tbl
order by created_at desc
) as T
where T.num > 5);
select * from tbl; # <-- result empty
# +----+-----+------------+
# | id | cal | created_at |
# +----+-----+------------+
I cry;
Upvotes: 1
Views: 146
Reputation: 31802
Get the highest ID to delete using LIMIT
and OFFSET
:
set @last_id_to_delete = (
select id
from tbl
order by id desc
limit 1
offset 5
);
Then delete all rows with ID equal or smaller than the obove value:
delete tbl
from tbl
where id <= @last_id_to_delete;
You can combine the two queries into one. Either with a subquery in the WHERE clause:
delete tbl
from tbl
where id <= (select id from(
select id
from tbl
order by id desc
limit 1
offset 5
)x);
(Note that you need to wrap the subquery result into a derived table, to avoid the error: "You can't specify target table 'tbl' for update in FROM clause".)
or by joining with a single-row subquery:
delete t
from tbl t
join (
select id as last_id_to_delete
from tbl
order by id desc
limit 1
offset 5
) x on t.id <= x.last_id_to_delete;
Upvotes: 1
Reputation: 521289
We can try doing a delete limit join here:
DELETE t1
FROM tbl t1
LEFT JOIN
(
SELECT id
FROM tbl
ORDER BY created_at DESC
LIMIT 5
) t2
ON t1.id = t2.id
WHERE
t2.id IS NULL;
The idea behind this anti-join is that we will delete any record which does not match to one of the first five records, as ordered descending by the created_at
column.
Note that we can't use a WHERE IN
query here, because MySQL will return the dreaded error message that LIMIT
is not yet supported in this version.
Upvotes: 2