是否可以构建单个mysql查询(不带变量)以从表中删除所有记录,但最新的N(按ID desc排序)除外?
像这样的东西,只有它不起作用:)
delete from table order by id ASC limit ((select count(*) from table ) - N)
谢谢。
Answers:
您不能以这种方式删除记录,主要问题是您不能使用子查询来指定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
);
中间子查询是必需的。没有它,我们将遇到两个错误:
幸运的是,使用中间子查询使我们能够绕过这两个限制。
Nicole指出,可以针对某些用例(例如此用例)显着优化此查询。我建议您也阅读该答案,以查看它是否适合您。
我知道我正在复活一个很老的问题,但是我最近遇到了这个问题,但是需要一些可以很好地扩展到大量的问题。没有任何现有的性能数据,并且由于这个问题引起了很多关注,所以我认为我应该发表发现的内容。
实际起作用的解决方案是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 |
+-----------+------------------------+----------------------+
有趣的是,该<=
方法的整体性能更好,但是保留的越多,效果越好,而不是更糟。
ROW_NUMBER()
以下方法找到特定的行偏移量:stackoverflow.com/questions/603724/…–
不幸的是,对于其他人给出的所有答案,您不能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注入的风险,但是在这种情况下,这些值不是来自不受信任的来源,因此它们是数据库本身的整数值。)
注意:尽管这无法在单个查询中完成工作,但有时更简单,“完成”的解决方案是最有效的。
DELETE FROM mytable WHERE id NOT IN (SELECT id FROM mytable ORDER BY id DESC LIMIT 3);
效果很好。
如果您的ID是增量ID,请使用类似
delete from table where id < (select max(id) from table)-N
要删除除最后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
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)
内部子查询将返回前10个值,外部查询将删除除前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
)
只是想让使用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进行优化,请共享您的解决方案。谢谢!
如果您还需要根据其他列删除记录,那么可以采用以下解决方案:
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_name order by ID limit 10
这将删除前10条记录,并保留最新记录。