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


90

是否可以构建单个mysql查询(不带变量)以从表中删除所有记录,但最新的N(按ID desc排序)除外?

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

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

谢谢。

Answers:


140

您不能以这种方式删除记录,主要问题是您不能使用子查询来指定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
);

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

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

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


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


4
可以,但是对我来说,必须诉诸于这种奥秘的技巧,这是不雅且不令人满意的。+1仍然是答案。
比尔·卡温

1
我将其标记为可接受的答案,因为它可以满足我的要求。但是我个人可能会在两个查询中做到这一点,只是为了保持简单:)我认为也许有一些快速简便的方法。
serg

1
谢谢亚历克斯,您的回答帮助了我。我看到中间子查询是必需的,但我不明白为什么。您对此有解释吗?
2012年

8
一个问题:“ foo”的作用是什么?
Sebastian Breit

9
Perroloco,我在没有foo的情况下尝试执行此错误:ERROR 1248(42000):每个派生表都必须具有自己的别名因此,我们的答案是,每个派生表都必须具有自己的别名!
codygman

106

我知道我正在复活一个很老的问题,但是我最近遇到了这个问题,但是需要一些可以很好地扩展到大量的问题。没有任何现有的性能数据,并且由于这个问题引起了很多关注,所以我认为我应该发表发现的内容。

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

不幸的是,上述两种方法都创建了非常大的中间临时表,并且由于删除的记录数量变大,因此性能迅速下降。

我决定使用Alex Barrett的double子查询(谢谢!),但使用<=而不是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条记录的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 |
+-----------+------------------------+----------------------+

有趣的是,该<=方法的整体性能更好,但是保留的越多,效果越好,而不是更糟。


11
4.5年后,我再次阅读此主题。不错的补充!
亚历克斯·巴雷特

哇,这看起来很大,但并没有在Microsoft SQL 2008年的工作,我得到这个消息:“近‘限价’不正确的语法,这是不错的,它的作品在MySQL,但我需要找到一个替代的解决方案。
肯·帕尔默

1
@KenPalmer您应该仍然可以使用ROW_NUMBER()以下方法找到特定的行偏移量:stackoverflow.com/questions/603724/…–
Nicole

3
在SQL和mySQL之间切换时,@ KenPalmer使用SELECT TOP而不是LIMIT
Alpha G33k

1
为此加油。它将对我(非常大)数据集的查询从12分钟减少到3.64秒!
利乌威

10

不幸的是,对于其他人给出的所有答案,您不能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

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; 

收集ID,并将其放入逗号分隔的字符串中:

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

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

注意:尽管这无法在单个查询中完成工作,但有时更简单,“完成”的解决方案是最有效的。


但是您可以在删除和选择之间进行内部联接。我在下面所做的事情应该起作用。
achinda99,2009年

您需要使用中间子查询来使LIMIT在子查询中正常工作。
亚历克斯·巴雷特

@ achinda99:我没有看到您对此线程的回答...?
比尔·卡文

我被拉去开会了。我的错。我现在没有测试环境来测试我编写的sql,但是我已经做了Alex Barret所做的事情,并且已经将它与内部联接一起使用。
achinda99

这是MySQL的愚蠢限制。使用PostgreSQL,DELETE FROM mytable WHERE id NOT IN (SELECT id FROM mytable ORDER BY id DESC LIMIT 3);效果很好。
bortzmeyer

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


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;

这种方法的优点是性能:我已经在具有大约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
);

需要0.55秒。大约7倍。

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



1

请尝试以下查询:

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

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


1
关于此工作原理的一些解释将对那些遇到此答案的人有所帮助。通常不建议代码转储。
rayryeng

这是不是与不一致编号是否正确
斯拉瓦Rozhnev

0

关于什么 :

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

它返回之前有N行以上的行。有用吗?


0

在许多情况下,不能将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    
)

0

只是想让使用Microsoft SQL Server而不是MySQL的任何人都可以使用。MSSQL不支持关键字“ Limit”,因此您需要使用其他方法。此代码在SQL 2008中有效,并且基于此SO帖子。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进行优化,请共享您的解决方案。谢谢!


0

如果您还需要根据其他列删除记录,那么可以采用以下解决方案:

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

0

这也应该工作:

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]



-1

很长一段时间后回答这个问题...遇到相同的情况,而不是使用提到的答案,我来了-

DELETE FROM table_name order by ID limit 10

这将删除前10条记录,并保留最新记录。


该问题询问“最后的N条记录全部”和“单个查询”。但是,看来您仍然需要第一个查询来对表中的所有记录进行计数,然后才对总数进行限制-N
Paolo

@Paolo我们不需要查询来对所有记录进行计数,因为上述查询会删除除最后10条记录以外的所有记录。
尼特什

1
不,该查询将删除10个最旧的记录。OP希望删除除n条最近记录以外的所有内容。您的解决方案是与count查询配对使用的基本解决方案,而OP正在询问是否存在一种将所有内容组合到单个查询中的方法。
ChrisMoll 2013年

@ChrisMoll我同意。我现在是否应该编辑/删除此答案,以使用户不要对我投反对票或保留原样?
尼特什
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.