madjardi
madjardi

Reputation: 5939

MYSQL delete all rows, but the same query on select return only 3 rows

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

Answers (2)

Paul Spiegel
Paul Spiegel

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;

db-fiddle

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

db-fidle

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;

db-fidle

Upvotes: 1

Tim Biegeleisen
Tim Biegeleisen

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

Related Questions