SQLクエリ:最新のNを除くすべてのレコードをテーブルから削除しますか?


91

最新のN(id descでソート)を除くすべてのレコードをテーブルから削除する単一のmysqlクエリ(変数なし)を構築することは可能ですか?

このようなもの、それだけは機能しません:)

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

ありがとう。

回答:


141

この方法でレコードを削除することはできません。主な問題は、サブクエリを使用して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つのエラーが発生します。

  1. SQLエラー(1093):FROM句で更新するターゲットテーブル「テーブル」を指定できません-MySQLでは、直接サブクエリ内から削除するテーブルを参照できません。
  2. SQLエラー(1235):このバージョンのMySQLは、「LIMIT&IN / ALL / ANY / SOMEサブクエリ」をまだサポートしていません-NOTIN演算子の直接サブクエリ内でLIMIT句を使用することはできません。

幸い、中間サブクエリを使用すると、これらの制限の両方を回避できます。


ニコールは、このクエリは特定のユースケース(このような)向けに大幅に最適化できると指摘しています。その答えも読んで自分に合っているかどうかを確認することをお勧めします。


4
それはうまくいきますが、私にとっては、そのような不可解なトリックに頼らなければならないのはエレガントで満足のいくものではありません。それにもかかわらず、答えは+1です。
ビルカーウィン

1
それは私が求めたことをするので、私はそれを受け入れられた答えとしてマークします。しかし、私は個人的にそれを単純にするためにおそらく2つのクエリでそれを行います:)私は多分いくつかの迅速で簡単な方法があると思いました。
serg 2009

1
アレックスに感謝します、あなたの答えは私を助けました。中間サブクエリが必要なのはわかりますが、その理由がわかりません。その説明はありますか?
sv1 2012年

9
質問:「foo」は何のためにあるのですか?
セバスチャンブライト2013

9
Perroloco、fooなしで試したところ、次のエラーが発生しました。エラー1248(42000):すべての派生テーブルには独自のエイリアスが必要です。したがって、すべての派生テーブルには独自のエイリアスが必要です。
codygman 2013年

109

私はかなり古い質問を復活させていることを知っています、しかし私は最近この問題に遭遇しました、しかし私は大きな数にうまくスケーリングする何かを必要としていました。既存のパフォーマンスデータはなく、この質問はかなり注目されていたので、見つけたものを投稿したいと思いました。

実際に機能したソリューションは、AlexBarrettのdoublesubquery /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
  )

N番目のレコードのOFFSETIDを取得するために使用し、そのレコードと以前のすべてのレコードを削除します。

順序付けはすでにこの問題(ORDER BY id DESC)の前提で<=あるため、完全に適合します。

サブクエリによって生成された一時テーブルには、Nレコードではなく1つのレコードしか含まれていないため、はるかに高速です。

テストケース

上記の3つの作業方法と新しい方法を2つのテストケースでテストしました。

どちらのテストケースも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 |
+-----------+------------------------+----------------------+

興味深いのは、この<=方法では全体的にパフォーマンスが向上することですが、実際には、維持するほどパフォーマンスが低下するのではなく、向上することです。


11
4。5年後にこのスレッドをもう一度読んでいます。素敵な追加!
Alex Barrett

うわー、この偉大なルックスが、マイクロソフトSQLでの作業をしない2008年には、私はこのメッセージを得る:。。「『リミット』の近くに不正な構文を、それは、MySQLで動作しますが、私は別の解決策を見つける必要があるだろうということのnice
ケン・パーマー

1

3
@KenPalmerは、SQLとmySQLを切り替えるときにLIMITではなくSELECT TOPを使用します
Alpha G33k 2015年

1
乾杯。これにより、(非常に大きな)データセットに対するクエリが12分から3.64秒に短縮されました。
Lieuwe 2016

10

残念ながら、することはできません、他の人々によって与えられたすべての答えのため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

また、MySQLLIMITはサブクエリでサポートできません。これらは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つのクエリで作業を完了できませんが、より単純なget-it-doneソリューションが最も効果的な場合があります。


ただし、削除と選択の間で内部結合を行うことができます。以下で私がしたことはうまくいくはずです。
achinda99 2009

サブクエリでLIMITを機能させるには、中間サブクエリを使用する必要があります。
Alex Barrett

@ achinda99:このスレッドであなたからの回答が表示されません...?
ビルカーウィン

私は会議に引っ張られました。私の悪い。現在、作成したSQLをテストするためのテスト環境はありませんが、Alex Barretが行ったことと、内部結合で機能するようになったことの両方を実行しました。
achinda99 2009

これはMySQLの愚かな制限です。PostgreSQLでは、DELETE FROM mytable WHERE id NOT IN (SELECT id FROM mytable ORDER BY id DESC LIMIT 3);正常に動作します。
bortzmeyer 2009

8
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

2
この素晴らしいトリックの1つの大きな問題は、シリアルが常に連続しているとは限らないことです(たとえば、ロールバックがあった場合)。
bortzmeyer 2009

5

最後の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;

このアプローチの良いところはパフォーマンスです。ローカルDBで約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倍。

テスト環境:SSDを搭載した2011年後半のi7MacBookPro上のmySQL5.5.25



1

以下のクエリを試してください:

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

内側のサブクエリは上位10個の値を返し、外側のクエリは上位10個を除くすべてのレコードを削除します。


1
これがどのように機能するかについてのいくつかの説明は、この答えに出くわす人々にとって有益でしょう。通常、コードダンプはお勧めしません。
rayryeng 2015年

0

どうですか:

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

以前にN行を超える行を返します。役に立つでしょうか?


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    
)

0

MySQLの代わりにMicrosoftSQL Serverを使用している人のために、これをミックスに入れたかっただけです。キーワード「制限」はMSSQLでサポートされていないため、別のキーワードを使用する必要があります。このコードはSQL2008で機能し、この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)

確かに、これはエレガントではありません。これをMicrosoftSQL用に最適化できる場合は、ソリューションを共有してください。ありがとう!


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

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]



-1

久しぶりにこれに答える…同じ状況に出くわし、上記の答えを使う代わりに、以下のものを用意しました-

DELETE FROM table_name order by ID limit 10

これにより、最初の10レコードが削除され、最新のレコードが保持されます。


質問は、「最後のNレコードを除くすべて」と「1回のクエリで」という質問でした。ただし、テーブル内のすべてのレコードをカウントしてから合計に制限するには、最初のクエリが必要なようです-N
Paolo

@Paolo上記のクエリは最後の10レコードを除くすべてを削除するため、すべてのレコードをカウントするクエリは必要ありません。
Nitesh 2013年

1
いいえ、そのクエリは最も古い10個のレコードを削除します。OPは、最新のn個のレコードを除くすべてを削除したいと考えています。OPがすべてを単一のクエリに結合する方法があるかどうかを尋ねている間、あなたはカウントクエリとペアになる基本的なソリューションです。
ChrisMoll 2013年

@ChrisMoll同意します。この回答を今すぐ編集/削除して、ユーザーが私に反対票を投じたり、そのままにしたりしないようにしますか?
Nitesh 2014年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.