最新のN(id descでソート)を除くすべてのレコードをテーブルから削除する単一のmysqlクエリ(変数なし)を構築することは可能ですか?
このようなもの、それだけは機能しません:)
delete from table order by id ASC limit ((select count(*) from table ) - N)
ありがとう。
最新のN(id descでソート)を除くすべてのレコードをテーブルから削除する単一のmysqlクエリ(変数なし)を構築することは可能ですか?
このようなもの、それだけは機能しません:)
delete from table order by id ASC limit ((select count(*) from table ) - N)
ありがとう。
そのようにレコードを削除することはできません。主な問題は、サブクエリを使用して 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
);
中間サブクエリが必要です。これがないと、次の 2 つのエラーが発生します。
幸いなことに、中間サブクエリを使用すると、これらの両方の制限を回避できます。
Nicole は、このクエリは特定のユース ケース (このようなもの) に対して大幅に最適化できると指摘しています。その回答も読んで、自分に合っているかどうかを確認することをお勧めします。
かなり古い質問を復活させていることは知っていますが、最近この問題に遭遇しましたが、大きな数にうまくスケーリングできるものが必要でした。既存のパフォーマンス データはありませんでした。この質問はかなりの注目を集めていたので、見つけたものを投稿しようと思いました。
実際に機能したソリューションは、Alex Barrett の double sub-query/メソッド ( 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
);
を使用してNOFFSET
番目のレコードの ID を取得し、そのレコードとそれ以前のすべてのレコードを削除します。
順序付けは既にこの問題の前提であるため ( ORDER BY id DESC
)、<=
完全に適合します。
サブクエリによって生成される一時テーブルには、 N 個のレコードではなく 1 つのレコードしか含まれないため、はるかに高速です。
2 つのテスト ケースで、3 つの作業方法と上記の新しい方法をテストしました。
どちらのテスト ケースも既存の 10000 行を使用しますが、最初のテストでは 9000 行を保持し (最も古い 1000 行を削除)、2 番目のテストでは 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 |
+-----------+------------------------+----------------------+
興味深いのは、この<=
方法は全体的にパフォーマンスが向上していることですが、実際には維持するほどパフォーマンスが向上し、悪化することはありません。
残念ながら、他の人々からのすべての回答に対して、同じクエリで特定のテーブルから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
LIMIT
また、サブクエリでMySQL をサポートすることもできません。これらは 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'
私が思いつく最善の答えは、これを 2 段階で行うことです。
SELECT id FROM mytable ORDER BY id DESC LIMIT n;
ID を収集し、カンマ区切りの文字列にします。
DELETE FROM mytable WHERE id NOT IN ( ...comma-separated string... );
(通常、コンマ区切りのリストを SQL ステートメントに補間すると、SQL インジェクションのリスクが発生しますが、この場合、値は信頼できないソースからのものではなく、データベース自体からの整数値であることがわかっています。)
注:これは1 回のクエリで作業を完了するわけではありませんが、より単純ですぐに実行できるソリューションが最も効果的な場合があります。
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
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 レコードのローカル DB でクエリをテストし、最後の 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倍以上。
テスト環境: SSD を搭載した 2011 年後半の 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 件を除くすべてのレコードを削除します。
どうですか:
SELECT * FROM table del
LEFT JOIN table keep
ON del.id < keep.id
GROUP BY del.* HAVING count(*) > N;
以前にN行を超える行を返します。役に立つでしょうか?
これもうまくいくはずです:
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]
DELETE FROM table WHERE id NOT IN (
SELECT id FROM table ORDER BY id, desc LIMIT 0, 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
)
なぜだめですか
DELETE FROM table ORDER BY id DESC LIMIT 1, 123456789
2 番目の LIMIT 引数として非常に大きな数を使用して、最初の行以外をすべて削除します (順序は DESC です!)。こちらをご覧ください
久しぶりにこれに答える...同じ状況に遭遇し、言及された答えを使用する代わりに、以下を使用しました-
DELETE FROM table_name order by ID limit 10
これにより、最初の 10 件のレコードが削除され、最新のレコードが保持されます。