Bagaimana cara saya menghapus sejumlah baris dengan pengurutan di PostgreSQL?

107

Saya mencoba mem-port beberapa kueri MySQL lama ke PostgreSQL, tetapi saya mengalami masalah dengan yang ini:

DELETE FROM logtable ORDER BY timestamp LIMIT 10;

PostgreSQL tidak mengizinkan pengurutan atau batasan dalam sintaks hapus, dan tabel tidak memiliki kunci utama sehingga saya tidak dapat menggunakan subkueri. Selain itu, saya ingin mempertahankan perilaku di mana kueri menghapus persis nomor atau catatan yang diberikan - misalnya, jika tabel berisi 30 baris tetapi semuanya memiliki stempel waktu yang sama, saya masih ingin menghapus 10, meskipun tidak masalah yang 10.

Begitu; bagaimana cara menghapus sejumlah baris dengan pengurutan di PostgreSQL?

Edit: Tidak ada kunci utama berarti tidak ada log_idkolom atau sejenisnya. Ah, nikmatnya sistem warisan!

Apa
sumber
1
Mengapa tidak menambahkan kunci utama? Sepotong o' kue dalam postgresql: alter table foo add column id serial primary key.
Wayne Conrad
Itu adalah pendekatan awal saya, tetapi persyaratan lain mencegahnya.
Whatsit

Jawaban:

159

Anda dapat mencoba menggunakan ctid:

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

The ctidadalah:

Lokasi fisik versi baris dalam tabelnya. Perhatikan bahwa meskipun ctiddapat digunakan untuk menemukan versi baris dengan sangat cepat, baris ctidakan berubah jika diperbarui atau dipindahkan VACUUM FULL. Oleh karena ctiditu tidak berguna sebagai pengenal baris jangka panjang.

Ada juga oidtetapi itu hanya ada jika Anda secara khusus memintanya saat Anda membuat tabel.

mu terlalu pendek
sumber
Ini berfungsi, tetapi seberapa andal itu? Apakah ada 'gotcha' yang perlu saya perhatikan? Apakah mungkin VACUUM FULLatau autovacuum menyebabkan masalah jika mereka mengubah ctidnilai dalam tabel saat kueri sedang berjalan?
Whatsit
2
VAKUM inkremental tidak akan mengubah ctids, saya rasa. Karena itu hanya memadatkan dalam setiap halaman, dan ctid hanyalah nomor baris, bukan offset halaman. Operasi VACUUM FULL atau CLUSTER akan mengubah ctid, tetapi operasi tersebut mengambil kunci eksklusif akses di atas meja terlebih dahulu.
araqnid
@Whatsit: Kesan saya tentang ctiddokumentasinya adalah ctidcukup stabil untuk membuat DELETE ini berfungsi dengan baik tetapi tidak cukup stabil untuk, misalnya, diletakkan di tabel lain sebagai ghetto-FK. Agaknya Anda tidak MEMPERBARUI logtablesehingga Anda tidak perlu khawatir tentang perubahan ctiditu dan VACUUM FULLmengunci tabel ( postgresql.org/docs/current/static/routine-vacuuming.html ) sehingga Anda tidak perlu khawatir tentang cara lain yang ctidbisa berubah. PostgreSQL-Fu @ araqnid cukup kuat dan dokumen setuju dengannya untuk boot.
mu terlalu pendek
Terima kasih untuk Anda berdua atas klarifikasinya. Saya memang melihat ke dalam dokumen tetapi saya tidak yakin saya menafsirkannya dengan benar. Saya belum pernah menemukan ctids sebelum ini.
Whatsit
Ini sebenarnya adalah solusi yang sangat buruk karena Postgres tidak dapat menggunakan pemindaian TID dalam gabungan (IN adalah kasus tertentu). Jika Anda melihat rencananya, itu pasti sangat mengerikan. Jadi "sangat cepat" hanya berlaku jika Anda menentukan CTID secara eksplisit. Yang dikatakan adalah pada versi 10.
greatvovan
53

Dokumen Postgres merekomendasikan untuk menggunakan array daripada IN dan subquery. Ini akan bekerja lebih cepat

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

Ini dan beberapa trik lainnya dapat ditemukan di sini

kritikus
sumber
@Konrad Garus Ini dia tautan , 'Cepat menghapus n baris pertama'
kritikus
1
@BlakeRegalia Tidak, karena tidak ada kunci utama dalam tabel yang ditentukan. Ini akan menghapus semua baris dengan "ID" yang ditemukan di 10. Jika semua baris memiliki ID yang sama, semua baris akan dihapus.
Philip Whitehouse
6
Jika any (array( ... ));lebih cepat dari in ( ... )itu terdengar seperti bug dalam pengoptimal kueri - ia harus dapat melihat transformasi itu dan melakukan hal yang sama dengan datanya sendiri.
rjmunro
1
Saya menemukan metode ini menjadi lebih lambat daripada menggunakan INpada UPDATE(yang mungkin menjadi perbedaannya).
jmervine
1
Pengukuran pada tabel 12 GB: query pertama 450..1000 ms, yang kedua 5..7 detik: Fast one: hapus dari cs_logging dimana id = any (array (pilih id dari cs_logging dimana date_created <now () - interval '1 hari '* 30 dan partition_key seperti'% I 'memesan dengan batas id 500)) Yang lambat: hapus dari cs_logging di mana id masuk (pilih id dari cs_logging di mana date_created <sekarang () - interval' 1 hari '* 30 dan partition_key suka'% Saya memesan berdasarkan batas id 500). Menggunakan ctid jauh lebih lambat (menit).
Guido Leenders
14
delete from logtable where log_id in (
    select log_id from logtable order by timestamp limit 10);
Konrad Garus
sumber
2

Dengan asumsi Anda ingin menghapus SETIAP 10 catatan (tanpa pemesanan) Anda dapat melakukan ini:

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);

Untuk kasus penggunaan saya, menghapus 10 juta data, ternyata ini lebih cepat.

Patrick Hüsler
sumber
1

Anda bisa menulis prosedur yang mengulang penghapusan untuk baris individu, prosedur dapat mengambil parameter untuk menentukan jumlah item yang ingin Anda hapus. Tapi itu sedikit berlebihan dibandingkan dengan MySQL.

Bernhard
sumber
0

Jika Anda tidak memiliki kunci utama, Anda dapat menggunakan sintaks array Where IN dengan kunci komposit.

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

Ini berhasil untuk saya.

pengguna2449151
sumber