SQL查询:删除表中除最新N之外的所有记录?

ser*_*erg 81 mysql sql

是否可以构建一个单独的mysql查询(没有变量)来删除表中的所有记录,除了最新的N(按id desc排序)?

像这样的东西,只有它不起作用:)

delete from table order by id ASC limit ((select count(*) from table ) - N)
Run Code Online (Sandbox Code Playgroud)

谢谢.

Ale*_*ett 128

您不能以这种方式删除记录,主要问题是您不能使用子查询来指定LIMIT子句的值.

这工作(在MySQL 5.0.67中测试):

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
);
Run Code Online (Sandbox Code Playgroud)

中间子查询必需的.没有它我们会遇到两个错误:

  1. SQL错误(1093):您无法在FROM子句中为更新指定目标表'table' - MySQL不允许您在直接子查询中引用要删除的表.
  2. SQL错误(1235):此版本的MySQL尚不支持'LIMIT&IN/ALL/ANY/SOME子查询' - 您不能在NOT IN运算符的直接子查询中使用LIMIT子句.

幸运的是,使用中间子查询允许我们绕过这两个限制.


NickC指出,对于某些用例(例如此用例),可以显着优化此查询.我建议您阅读该答案,看看它是否适合您.

  • Perroloco,我尝试没有foo并得到了这个错误:ERROR 1248(42000):每个派生表必须有自己的别名所以我们的答案,每个派生表必须有自己的别名! (8认同)
  • 一个问题:什么是"foo"? (6认同)
  • 好吧,这是有效的 - 但对我来说,不得不诉诸这样的神秘技巧是不够优雅和不满意的.尽管如此仍为+1答案. (4认同)

Nic*_*ole 99

我知道我复活了一个很老的问题,但我最近遇到了这个问题,但需要能够很好地扩展到大量问题的东西.没有任何现有的性能数据,因为这个问题引起了很多关注,我想我会发布我发现的内容.

实际工作的解决方案是Alex Barrett的NOT IN双子查询/方法(类似于Bill Karwin的)和Quassnoi的LEFT JOIN方法.

遗憾的是,上述两种方法都会创建非常大的中间临时表,并且随着删除的记录数量变大,性能会迅速降低.

我决定使用Alex Barrett的双子查询(谢谢!)但使用<=而不是NOT IN:

DELETE FROM `test_sandbox`
  WHERE id <= (
    SELECT id
    FROM (
      SELECT id
      FROM `test_sandbox`
      ORDER BY id DESC
      LIMIT 1 OFFSET 42 -- keep this many records
    ) foo
  )
Run Code Online (Sandbox Code Playgroud)

它用于OFFSET获取第N条记录的id 并删除该记录和所有先前的记录.

由于排序已经是这个问题的假设(ORDER BY id DESC),<=因此非常适合.

它更快,因为子查询生成的临时表只包含一个记录而不是N个记录.

测试用例

我在两个测试用例中测试了上述三种工作方法和新方法.

两个测试用例使用10000个现有行,而第一个测试保留9000(删除最旧的1000),第二个测试保持50(删除最旧的9950).

+-----------+------------------------+----------------------+
|           | 10000 TOTAL, KEEP 9000 | 10000 TOTAL, KEEP 50 |
+-----------+------------------------+----------------------+
| NOT IN    |         3.2542 seconds |       0.1629 seconds |
| NOT IN v2 |         4.5863 seconds |       0.1650 seconds |
| <=,OFFSET |         0.0204 seconds |       0.1076 seconds |
+-----------+------------------------+----------------------+
Run Code Online (Sandbox Code Playgroud)

有趣的是,该<=方法在整个过程中看到了更好的性能,但实际上越多越好,而不是更糟.

  • 我在4.5年后再次阅读这篇帖子.好的补充! (10认同)
  • 在SQL和mySQL之间切换时,@ KenPalmer使用SELECT TOP而不是LIMIT (3认同)

Bil*_*win 9

不幸的是,通过其他人给,你不能在所有的答案DELETE,并SELECT从表中给出相同的查询.

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
Run Code Online (Sandbox Code Playgroud)

MySQL也不能支持LIMIT子查询.这些是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'
Run Code Online (Sandbox Code Playgroud)

我能想出的最佳答案是分两个阶段完成:

SELECT id FROM mytable ORDER BY id DESC LIMIT n; 
Run Code Online (Sandbox Code Playgroud)

收集id并将它们组成逗号分隔的字符串:

DELETE FROM mytable WHERE id NOT IN ( ...comma-separated string... );
Run Code Online (Sandbox Code Playgroud)

(通常将逗号分隔列表插入到SQL语句中会引入一些SQL注入的风险,但在这种情况下,值不是来自不受信任的源,它们是来自数据库本身的id值.)

注意:虽然这不能在单个查询中完成工作,但有时一个更简单,可以完成的解决方案是最有效的.


Qua*_*noi 8

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
Run Code Online (Sandbox Code Playgroud)


Jus*_*all 5

如果您的id是增量的,那么使用类似的东西

delete from table where id < (select max(id) from table)-N
Run Code Online (Sandbox Code Playgroud)

  • 这个好技巧中的一个大问题是:连续出版物并不总是连续的(例如,当有回滚时). (2认同)

Pao*_*olo 5

要删除除最后N个记录以外的所有记录,您可以使用下面报告的查询。

这是一个查询,但是有很多语句,因此实际上并不是单个查询的原始查询方式。

另外,由于MySQL中的错误,您还需要一个变量和一个内置的(在查询中)准备好的语句。

希望它仍然有用...

NNN是行保持theTable是你的工作表。

我假设您有一个名为id的自动递增记录

SELECT @ROWS_TO_DELETE := COUNT(*) - nnn FROM `theTable`;
SELECT @ROWS_TO_DELETE := IF(@ROWS_TO_DELETE<0,0,@ROWS_TO_DELETE);
PREPARE STMT FROM "DELETE FROM `theTable` ORDER BY `id` ASC LIMIT ?";
EXECUTE STMT USING @ROWS_TO_DELETE;
Run Code Online (Sandbox Code Playgroud)

这种方法的好处是性能:我已经在具有大约13,000条记录的本地数据库上测试了查询,保留了最后1000条记录。运行时间为0.08秒。

来自已接受答案的脚本...

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
);
Run Code Online (Sandbox Code Playgroud)

需要0.55秒。大约7倍。

测试环境:2011年末配备SSD的i7 MacBookPro上的mySQL 5.5.25