PostgreSQLのソートで固定数の行を削除するにはどうすればよいですか?


107

私はいくつかの古いMySQLクエリをPostgreSQLに移植しようとしていますが、これに問題があります。

DELETE FROM logtable ORDER BY timestamp LIMIT 10;

PostgreSQLでは、削除構文での順序付けや制限が許可されていません。また、テーブルに主キーがないため、サブクエリを使用できません。さらに、クエリが指定された数またはレコードを正確に削除するという動作を維持したいと思います。たとえば、テーブルに30行が含まれているが、それらすべてに同じタイムスタンプがある場合、重要ではありませんが、10を削除します。その10。

そう; PostgreSQLのソートで固定数の行を削除するにはどうすればよいですか?

編集:主キーがないということは、log_id列などがないことを意味します。ああ、レガシーシステムの喜び!


1
主キーを追加しないのはなぜですか?postgresqlのピースo 'ケーキ:alter table foo add column id serial primary key
ウェインコンラッド

それが私の最初のアプローチでしたが、他の要件がそれを妨げています。
Whatsit

回答:


159

あなたは使用することができますctid

DELETE FROM logtable
WHERE ctid IN (
    SELECT ctid
    FROM logtable
    ORDER BY timestamp
    LIMIT 10
)

ctid次のとおりです。

テーブル内の行バージョンの物理的な場所。をctid使用して行のバージョンをすばやく見つけることができますが、ctidによって更新または移動されると、行のバージョンが変更されることに注意してくださいVACUUM FULL。したがってctid、長期的な行識別子としては役に立ちません。

またoid、テーブルを作成するときに具体的に要求した場合にのみ存在します。


これは機能しますが、どの程度信頼できますか?私が注意しなければならない「落とし穴」はありますか?VACUUM FULLまたは自動バキュームctidがクエリの実行中にテーブルの値を変更した場合、問題が発生する可能性はありますか?
Whatsit

2
インクリメンタルVACUUMはctidを変更しないと思います。これは、各ページ内で圧縮されるだけなので、ctidはページ番号ではなく、単に行番号です。VACUUM FULLまたはCLUSTER操作 ctid 変更しますが、これらの操作は最初にテーブルのアクセス排他ロックを取得します。
araqnid

@Whatsit:ctidドキュメントの私の印象は、ctidこのDELETEを機能させるのに十分安定しているが、たとえば、別のテーブルにゲットーFKとして置くには十分に安定していないということです。おそらくあなたは更新しないlogtableので、ctidsの変更を心配する必要はなくVACUUM FULL、テーブルをロックします(postgresql.org/docs/current/static/routine-vacuuming.html)ので、心配する必要はありません。ctidsが変更できるもう1つの方法。@araqnidのPostgreSQL-Fuは非常に強力であり、ドキュメントは彼との起動に同意しています。
muが短すぎる

説明をありがとうございました。私はドキュメントを調べましたが、それらを正しく解釈しているかどうか確信がありませんでした。これまでにctidに出会ったことはありません。
Whatsit 2011

Postgresは結合でTIDスキャンを使用できないため、これは実際にはかなり悪いソリューションです(INはその特殊なケースです)。計画を見ると、かなりひどいはずです。したがって、「非常に迅速に」が適用されるのは、CTIDを明示的に指定した場合のみです。バージョン10のようである
greatvovan

53

Postgresのドキュメントでは、INとサブクエリの代わりに配列を使用することを推奨しています。これははるかに速く機能するはずです

DELETE FROM logtable 
WHERE id = any (array(SELECT id FROM logtable ORDER BY timestamp LIMIT 10));

これと他のいくつかのトリックはここにあります


@Konrad Garusここにリンクがあります、「最初のn行が削除されています」
批評家

1
@BlakeRegaliaいいえ、指定されたテーブルに主キーがないためです。これにより、最初の10で見つかった「ID」を持つすべての行が削除されます。すべての行が同じIDを持つ場合、すべての行が削除されます。
フィリップホワイトハウス、2015年

6
それany (array( ... ));よりも高速でin ( ... )ある場合、クエリオプティマイザーのバグのように聞こえます。その変換を特定し、データ自体で同じことを実行できるはずです。
rjmunro 2015年

1
私はこの方法がでの使用よりもかなり遅いことを発見しINましたUPDATE(違いかもしれません)。
jmervine 2016

1
12 GBテーブルでの測定:最初のクエリ450..1000ミリ秒、2番目のクエリ5..7秒:高速1:cs_loggingから削除しますwhere id = any(array(select id from cs_logging where date_created <now()-interval '1 days '* 30および'%I 'のようなpartition_keyによるID制限500の順序))1つ遅い:cs_logging where id inから削除(select ids from cs_logging where date_created <now()-interval' 1 days '* 30 and partition_key like'% ID制限500で注文します)。ctidの使用はかなり遅くなりました(分)。
Guido Leenders、


2

10個のレコードを(順序付けなしで)削除する場合は、次のようにします。

DELETE FROM logtable as t1 WHERE t1.ctid < (select t2.ctid from logtable as t2  where (Select count(*) from logtable t3  where t3.ctid < t2.ctid ) = 10 LIMIT 1);

私の使用例では、1千万のレコードを削除しましたが、これはより高速であることがわかりました。


1

個々の行の削除をループするプロシージャを記述できます。プロシージャは、削除する項目の数を指定するパラメータを取ることができます。しかし、これはMySQLと比較すると少しやりすぎです。


0

主キーがない場合は、配列Where IN構文を複合キーで使用できます。

delete from table1 where (schema,id,lac,cid) in (select schema,id,lac,cid from table1 where lac = 0 limit 1000);

これでうまくいきました。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.