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

Posted

技术标签:

【中文标题】SQL查询:从表中删除除最新N之外的所有记录?【英文标题】:SQL query: Delete all records from the table except latest N? 【发布时间】:2010-10-09 09:30:30 【问题描述】:

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

类似的东西,只是它不起作用:)

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

谢谢。

【问题讨论】:

【参考方案1】:

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

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

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

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


Nicole 指出这个查询可以针对某些用例(例如这个)进行显着优化。我建议您也阅读that answer,看看它是否适合您。

【讨论】:

好吧,这行得通——但对我来说,不得不求助于这样的神秘技巧是不雅和不令人满意的。为答案+1。 我将其标记为已接受的答案,因为它符合我的要求。但为了简单起见,我个人可能会在两个查询中执行此操作:) 我想也许有一些快速简便的方法。 谢谢亚历克斯,你的回答帮助了我。我看到中间子查询是必需的,但我不明白为什么。你对此有什么解释吗? 一个问题:“foo”是干什么用的? Perroloco,我在没有 foo 的情况下尝试过,得到了这个错误:错误 1248 (42000):每个派生表都必须有自己的别名所以我们的答案是,每个派生表都必须有自己的别名!【参考方案2】:

我知道我正在重新提出一个相当老的问题,但我最近遇到了这个问题,但需要一些可以很好地扩展到大数字的东西。没有任何现有的性能数据,由于这个问题引起了相当多的关注,我想我会发布我发现的内容。

实际可行的解决方案是Alex Barrett's double sub-query/NOT IN 方法(类似于Bill Karwin's)和Quassnoi's 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
  );

它使用OFFSET 获取第N条记录的ID,并删除该记录和之前的所有记录。

由于排序已经是这个问题的一个假设 (ORDER BY id DESC),&lt;= 是一个完美的选择。

速度要快得多,因为子查询生成的临时表只包含一条记录,而不是 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 |
+-----------+------------------------+----------------------+

有趣的是,&lt;= 方法看到了整体上更好的性能,但实际上你保留的越多越好,而不是越差。

【讨论】:

我在 4.5 年后再次阅读此主题。不错的补充! 哇,这看起来很棒,但在 Microsoft SQL 2008 中不起作用。我收到以下消息:“'Limit' 附近的语法不正确。它在 MySQL 中工作很好,但我需要寻找替代解决方案。 @KenPalmer 您应该仍然可以使用 ROW_NUMBER(): ***.com/questions/603724/… 找到特定的行偏移量 @KenPalmer 在 SQL 和 mySQL 之间切换时使用 SELECT TOP 而不是 LIMIT 为此干杯。它将对我的(非常大的)数据集的查询从 12 分钟减少到 3.64 秒!【参考方案3】:

不幸的是,对于其他人给出的所有答案,您不能在同一查询中的给定表中 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; 

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

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

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

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

【讨论】:

但是您可以在删除和选择之间进行内部连接。我在下面所做的应该可以工作。 您需要使用中间子查询来让 LIMIT 在子查询中工作。 @achinda99:我在这个线程上没有看到你的答案...? 我被拉去开会。我的错。我现在没有测试环境来测试我编写的 sql,但我已经完成了 Alex Barret 所做的工作,并且我已经让它与内部连接一起工作。 这是 MySQL 的一个愚蠢的限制。使用 PostgreSQL,DELETE FROM mytable WHERE id NOT IN (SELECT id FROM mytable ORDER BY id DESC LIMIT 3); 工作正常。【参考方案4】:
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】:

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

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

【讨论】:

这个好技巧的一个大问题:连续剧并不总是连续的(例如当有回滚时)。【参考方案6】:

要删除除最后一个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 条记录的查询,保留了最后的 1,000 条。它在 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 倍。

测试环境:mySQL 5.5.25,2011 年末 i7 MacBookPro 和 SSD

【讨论】:

【参考方案7】:
DELETE FROM table WHERE ID NOT IN
(SELECT MAX(ID) ID FROM table)

【讨论】:

这只会留下一个最新的行 这是我认为最好的解决方案!【参考方案8】:

试试下面的查询:

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

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

【讨论】:

有关其工作原理的一些解释将对遇到此答案的人有益。通常不建议转储代码。【参考方案9】:

怎么样:

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

它返回之前超过 N 行的行。 有用吗?

【讨论】:

【参考方案10】:

在许多情况下,为该任务使用 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    
)

【讨论】:

【参考方案11】:

只是想为使用 Microsoft SQL Server 而不是 MySQL 的任何人加入这个组合。 MSSQL 不支持关键字“限制”,因此您需要使用替代方法。此代码在 SQL 2008 中有效,并且基于此 SO 帖子。 https://***.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 进行优化,请分享您的解决方案。谢谢!

【讨论】:

【参考方案12】:

如果您还需要删除基于其他列的记录,那么这里有一个解决方案:

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

【讨论】:

【参考方案13】:

这应该也可以:

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]

【讨论】:

【参考方案14】:
DELETE FROM table WHERE id NOT IN (
    SELECT id FROM table ORDER BY id, desc LIMIT 0, 10
)

【讨论】:

【参考方案15】:

为什么不

DELETE FROM table ORDER BY id DESC LIMIT 1, 123456789

只删除除第一行之外的所有行(顺序为 DESC!),使用一个非常非常大的数字作为第二个 LIMIT 参数。 See here

【讨论】:

DELETE 不支持[offset],OFFSET: dev.mysql.com/doc/refman/5.0/en/delete.html【参考方案16】:

很长一段时间后回答这个问题......遇到了同样的情况,我没有使用提到的答案,而是在下面给出 -

DELETE FROM table_name order by ID limit 10

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

【讨论】:

问题询问“所有除最后 N 条记录”和“在单个查询中”。但似乎您仍然需要第一个查询来计算表中的所有记录,然后限制为总数 - N @Paolo 我们不需要查询来计算所有记录,因为上述查询会删除除最后 10 条记录之外的所有记录。 否,该查询会删除 10 条最旧的记录。 OP 想要删除除 n 个最近记录之外的所有内容。您的解决方案是与计数查询配对的基本解决方案,而 OP 则询问是否有办法将所有内容组合成一个查询。 @ChrisMoll 我同意。我现在应该编辑/删除这个答案,让用户不要投票给我或保持原样吗?

以上是关于SQL查询:从表中删除除最新N之外的所有记录?的主要内容,如果未能解决你的问题,请参考以下文章

如何从表中删除除前两个和最后一个之外的所有行?

如何使用 MySQL 查询从表中选择除一列之外的所有内容? [复制]

SQL Server:删除除最新的“n”个结果之外的所有结果

删除除给定查询获取的所有记录之外的所有记录

从表中删除 WHERE NOT MAX

查询效率 - 从表中选择 2 个最新的“组/批次”记录