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

是否有可能build立一个单一的mysql查询(无variables)从表中删除所有logging,除了最新的N(按ID descsorting)?

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

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

谢谢。

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

这工作(在MySQL 5.0.67testing):

 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 ); 

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

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

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


NickC指出,这个查询可以针对某些用例(比如这个)显着优化。 我build议阅读这个答案 ,看看它是否适合你。

我知道我正在复活一个相当古老的问题,但是我最近遇到了这个问题,但是需要一些能够很好地扩展到大量的东西。 没有任何现有的性能数据,因为这个问题已经有相当多的关注,我想我会张贴我发现的。

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

不幸的是,上述两种方法都会创build非常大的中间临时表,并且随着未被删除的logging数量变大,性能会迅速下降。

我决定使用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 ) 

它使用OFFSET获取第N条logging的ID并删除该logging和所有以前的logging。

由于sorting已经是这个问题的一个假设( ORDER BY id DESC ),所以<=是一个完美的select。

由于子查询生成的临时表只包含一条logging,而不是N条logging,所以速度要快得多。

testing用例

我在两个testing用例中testing了上面三种工作方法和新方法。

两个testing用例使用10000个现有行,而第一个testing保持9000(删除最老的1000),第二个testing保持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 | +-----------+------------------------+----------------------+ 

有趣的是, <=方法在整体上performance出更好的performance,但实际上越多越好,而不是更糟糕。

不幸的是,对于其他人给出的所有答案,您不能在同一个查询中从给定的表中DELETESELECT

 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 

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' 

我能想到的最好的答案是分两步进行:

 SELECT id FROM mytable ORDER BY id DESC LIMIT n; 

收集编号并将它们变成逗号分隔的string:

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

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

注意:虽然这不能在单个查询中完成工作,但有时候一个更简单,完全实现的解决scheme是最有效的。

 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 

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

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

要删除除最后一个N之外的所有logging,可以使用稍后报告的查询。

这是一个单一的查询,但有很多的陈述,所以它实际上不是一个单一的查询原来的问题的方式。

你也需要一个variables和一个内置的(在查询中)准备好的声明,因为MySQL中有一个bug。

希望它可能是有用的无论如何…

nnn是要保留的行,表是您正在处理的表。

我假设你有一个名为id的autoincrementinglogging

 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; 

这种方法的好处是性能 :我已经在本地数据库上testing了大约13,000条logging的查询,保留了最近的1000条logging。 它运行在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 ); 

花费0.55秒。 大约7倍多。

我正在使用SSD的i7 MacBookPro上运行mySQL 5.5.25

 DELETE FROM table WHERE ID NOT IN (SELECT MAX(ID) ID FROM table) 

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

这也应该工作:

 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] 

关于什么 :

 SELECT * FROM table del LEFT JOIN table keep ON del.id < keep.id GROUP BY del.* HAVING count(*) > N; 

它返回的行数超过N行。 可能有用吗?

在许多情况下,使用id来执行此任务不是一个选项。 例如 – 表与twitter状态。 这是一个具有指定时间戳字段的变体。

 delete from table where access_time >= ( select access_time from ( select access_time from table order by access_time limit 150000,1 ) foo ) 

只是想把这个混合到任何使用Microsoft SQL Server而不是MySQL的人。 MSSQL不支持关键字“限制”,因此您需要使用替代方法。 此代码在SQL 2008中工作,并基于此SOpost。 https://stackoverflow.com/a/1104447/993856

 -- Keep the last 10 most recent passwords for this user. DECLARE @UserID int; SET @UserID = 1004 DECLARE @ThresholdID int -- Position of 10th password. SELECT @ThresholdID = UserPasswordHistoryID FROM ( SELECT ROW_NUMBER() OVER (ORDER BY UserPasswordHistoryID DESC) AS RowNum, UserPasswordHistoryID FROM UserPasswordHistory WHERE UserID = @UserID ) sub WHERE (RowNum = 10) -- Keep this many records. DELETE UserPasswordHistory WHERE (UserID = @UserID) AND (UserPasswordHistoryID < @ThresholdID) 

诚然,这不是优雅的。 如果您能够针对Microsoft SQL优化此function,请分享您的解决scheme。 谢谢!

试试下面的查询:

 DELETE FROM tablename WHERE id < (SELECT * FROM (SELECT (MAX(id)-10) FROM tablename ) AS a) 

内部子查询将返回前10个值,外部查询将删除除前10个之外的所有logging。

如果您还需要删除基于其他列的logging,那么这里是一个解决scheme:

 DELETE FROM articles WHERE id IN (SELECT id FROM (SELECT id FROM articles WHERE user_id = :userId ORDER BY created_at DESC LIMIT 500, 10000000) abc) AND user_id = :userId 

为什么不

 DELETE FROM table ORDER BY id DESC LIMIT 1, 123456789 

只要删除第一行(顺序是DESC!),使用一个非常大的数字作为第二个LIMIT参数。 看这里

经过很长一段时间的回答…遇到同样的情况,而不是使用提到的答案,我带着下面 –

 DELETE FROM table_name order by ID limit 10 

这将删除前10条logging并保留最新的logging。