97

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

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

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

谢谢。

4

16 回答 16

151

您不能以这种方式删除记录,主要问题是您不能使用子查询来指定 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 指出这个查询可以针对某些用例(例如这个)进行显着优化。我建议您也阅读该答案,看看它是否适合您的答案。

于 2009-02-23T19:13:34.907 回答
117

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

实际有效的解决方案是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
  );

它用于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 |
+-----------+------------------------+----------------------+

有趣的是,该<=方法在整体上看到了更好的性能,但实际上你保留的越多,性能就会越好,而不是越差。

于 2011-11-28T22:41:29.353 回答
11

不幸的是,对于其他人给出的所有答案,您不能在同一个查询中使用给定的表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 注入风险,但在这种情况下,这些值不是来自不受信任的来源,它们被认为是来自数据库本身的整数值。)

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

于 2009-02-23T19:08:00.193 回答
9
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
于 2009-02-23T19:03:08.113 回答
8

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

delete from table where id < (select max(id) from table)-N
于 2009-02-23T19:03:39.970 回答
5

要删除除 te last 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倍。

测试环境:2011 年末 i7 MacBookPro 上的 mySQL 5.5.25,带 SSD

于 2013-10-02T08:12:08.353 回答
2
DELETE FROM table WHERE ID NOT IN
(SELECT MAX(ID) ID FROM table)
于 2009-02-23T19:02:46.867 回答
1

试试下面的查询:

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

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

于 2015-05-14T06:44:00.793 回答
0
DELETE FROM table WHERE id NOT IN (
    SELECT id FROM table ORDER BY id, desc LIMIT 0, 10
)
于 2009-02-23T19:05:02.290 回答
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]
于 2009-02-23T20:18:47.200 回答
0

关于什么 :

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

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

于 2011-08-03T11:28:35.107 回答
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    
)
于 2012-02-14T10:28:53.373 回答
0

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

于 2015-04-02T13:00:56.227 回答
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
于 2016-06-23T12:40:52.430 回答
-1

为什么不

DELETE FROM table ORDER BY id DESC LIMIT 1, 123456789

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

于 2009-02-23T19:40:46.480 回答
-1

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

DELETE FROM table_name order by ID limit 10

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

于 2013-02-19T05:45:42.783 回答