是否有可能构建一个单一的MySQL查询语句(不使用变量),从表中删除除最新的N个记录之外的所有记录(按id降序排序)?
类似于这样的东西,只是它不起作用 :)
delete from table order by id ASC limit ((select count(*) from table ) - N)
谢谢。
是否有可能构建一个单一的MySQL查询语句(不使用变量),从表中删除除最新的N个记录之外的所有记录(按id降序排序)?
类似于这样的东西,只是它不起作用 :)
delete from table order by id ASC limit ((select count(*) from table ) - N)
谢谢。
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
);
我知道我在挖掘一个相当古老的问题,但最近我遇到了这个问题,但需要一些适用于大量数据的解决方案。 没有现有的性能数据,并且由于这个问题已经引起了相当多的关注,所以我想我会发布我发现的内容。
实际起作用的解决方案是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 |
+-----------+------------------------+----------------------+
有趣的是,<=
方法在各方面都表现更好,但实际上它会随着保留的数量增多而变得更好,而不是更差。
ROW_NUMBER()
来查找特定行的偏移量:https://dev59.com/dHRB5IYBdhLWcg3wgHar - Nicole很遗憾,对于其他人给出的所有答案,您无法在同一查询中使用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;
DELETE FROM mytable WHERE id NOT IN ( ...comma-separated string... );
通常将逗号分隔的列表插入SQL语句中会引入一些SQL注入的风险,但在这种情况下,这些值不是来自不受信任的来源,而是来自数据库本身的整数值。
注意:尽管这不能在单个查询中完成工作,但有时更简单、实用的解决方案是最有效的。
DELETE FROM mytable WHERE id NOT IN (SELECT id FROM mytable ORDER BY id DESC LIMIT 3);
可以正常工作。 - bortzmeyerDELETE 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
delete from table where id < (select max(id) from table)-N
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;
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在一台装有SSD的晚期2011年i7 MacBookPro上运行。
DELETE FROM table WHERE ID NOT IN
(SELECT MAX(ID) ID FROM table)
DELETE FROM tablename WHERE id < (SELECT * FROM (SELECT (MAX(id)-10) FROM tablename ) AS a)
关于什么:
SELECT * FROM table del
LEFT JOIN table keep
ON del.id < keep.id
GROUP BY del.* HAVING count(*) > N;
它返回具有 N 行以上的行。可能会有用吗?
对于使用 Microsoft SQL Server 而不是 MySQL 的任何人,我想把这个内容加入到混合中。MSSQL 不支持关键字“Limit”,因此您需要使用替代方法。这段代码适用于 SQL 2008,并基于此 SO 帖子。https://dev59.com/nXVD5IYBdhLWcg3wU5-H#1104447
-- 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进行优化,请分享您的解决方案。谢谢!