tags:

views:

3765

answers:

8

Is it possible to build a single mysql query (without variables) to remove all records from the table, except latest N (sorted by id desc)?

Something like this, only it doesn't work :)

delete from table order by id ASC limit ((select count(*) from table ) - N)

Thanks.

A: 
DELETE FROM table WHERE ID NOT IN
(SELECT MAX(ID) ID FROM table)
Dave Swersky
This will only leave single latest row
Justin Wignall
+4  A: 
DELETE  i1.*
FROM    items i1
LEFT JOIN
        (
        SELECT  id
        FROM    items ii
        ORDER BY
                id DESC
        LIMIT 20
        ) i2
ON      i1.id = i2.id
WHERE   i2.id IS NULL
Quassnoi
+1  A: 

If your id is incremental then use something like

delete from table where id < (select max(id) from table)-N

Justin Wignall
One big problem in this nice trick: serials are not always contiguous (for instance when there were rollbacks).
bortzmeyer
+1  A: 

DELETE FROM table WHERE id NOT IN (SELECT id FROM table ORDER BY id, desc LIMIT 0, 10)

Mike Reedell
Good, and simpler than Alex Barrett's version.
bortzmeyer
+5  A: 

Unfortunately for all the answers given by other folks, you can't DELETE and SELECT from a given table in the same query.

DELETE FROM mytable WHERE id NOT IN (SELECT MAX(id) FROM mytable);

ERROR 1093 (HY000): You can't specify target table 'mytable' for update 
in FROM clause

Nor can MySQL support LIMIT in a subquery. These are limitations of MySQL.

DELETE FROM mytable WHERE id NOT IN 
  (SELECT id FROM mytable ORDER BY id DESC LIMIT 1);

ERROR 1235 (42000): This version of MySQL doesn't yet support 
'LIMIT & IN/ALL/ANY/SOME subquery'

The best answer I can come up with is to do this in two stages:

SELECT id FROM mytable ORDER BY id DESC LIMIT n;

Collect the id's and make them into a comma-separated string:

DELETE FROM mytable WHERE id NOT IN ( ...comma-separated string... );

(Normally interpolating a comma-separate list into an SQL statement introduces some risk of SQL injection, but in this case the values are not coming from an untrusted source, they are known to be id values from the database itself.)

note: Though this doesn't get the job done in a single query, sometimes a more simple, get-it-done solution is the most effective.

Bill Karwin
You're terribly right :(
Quassnoi
But you can do inner joins between a delete and select. What I did below should work.
achinda99
You need to use an intermediary subquery to get LIMIT working in the subquery.
Alex Barrett
@achinda99: I'm not seeing an answer from you on this thread...?
Bill Karwin
I got pulled for a meeting. My bad. I don't have a test environment right now to test the sql I wrote, but I've done both what Alex Barret did and I've gotten it to work with an inner join.
achinda99
your doing it wrong
DFectuoso
@DFectuoso: Please learn to spell "you're" correctly.
Bill Karwin
It's a stupid limitation of MySQL. With PostgreSQL, `DELETE FROM mytable WHERE id NOT IN (SELECT id FROM mytable ORDER BY id DESC LIMIT 3);` works fine.
bortzmeyer
+8  A: 

You cannot delete the records that way.

DELETE FROM `table`
WHERE id NOT IN (
  SELECT id
  FROM (
    SELECT id
    FROM `table`
    ORDER BY id DESC
    LIMIT 42 -- keep this many records
  ) foo
);

Tested, working (MySQL 5.0.67). The intermediate subquery is required.

Alex Barrett
Okay that works -- but to me, it's inelegant and unsatisfying to have to resort to arcane tricks like that. +1 nevertheless for the answer.
Bill Karwin
I mark it as an accepted answer, because it does what I asked for. But I personally will do it probably in two queries just to keep it simple :) I thought maybe there was some quick and easy way.
serg
+2  A: 

Why not

DELETE FROM table ORDER BY id DESC LIMIT 1, 123456789

Just delete all but the first row (order is DESC!), using a very very large nummber as second LIMIT-argument. See here

craesh
A: 

This should work as well:

DELETE FROM [table] INNER JOIN (SELECT [id] FROM (SELECT [id] FROM [table] ORDER BY [id] DESC LIMIT N) AS Temp) AS Temp2 ON [table].[id] = [Temp2].[id]
achinda99