PostgreSQL'de sıralama ile sabit sayıda satırı nasıl silerim?


107

Bazı eski MySQL sorgularını PostgreSQL'e aktarmaya çalışıyorum, ancak bununla ilgili sorun yaşıyorum:

DELETE FROM logtable ORDER BY timestamp LIMIT 10;

PostgreSQL, silme sözdiziminde sıralamaya veya sınırlamalara izin vermez ve tablonun birincil anahtarı olmadığı için alt sorgu kullanamıyorum. Ek olarak, sorgunun tam olarak verilen sayıyı veya kayıtları sildiği davranışı korumak istiyorum - örneğin, tablo 30 satır içeriyorsa ancak hepsi aynı zaman damgasına sahipse, önemli olmasa da yine de 10'u silmek istiyorum hangi 10.

Yani; PostgreSQL'de sıralama ile sabit sayıda satırı nasıl silerim?

Düzenle: Birincil anahtar olmaması, log_idsütun veya benzeri olmadığı anlamına gelir . Ah, eski sistemlerin eğlenceleri!


1
Neden birincil anahtarı eklemiyorsunuz? Adet o' PostgreSQL kek: alter table foo add column id serial primary key.
Wayne Conrad

Bu benim ilk yaklaşımımdı, ancak diğer gereksinimler bunu engelliyor.
Whatsit

Yanıtlar:


159

Şunları kullanmayı deneyebilirsiniz ctid:

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

Bu ctid:

Satır sürümünün tablosu içindeki fiziksel konumu. ctidSatır sürümünü çok hızlı bir şekilde bulmak için kullanılabilmesine rağmen , bir satır ctidgüncellenir veya taşınırsa değişecektir VACUUM FULL. Bu nedenle ctid, uzun vadeli bir satır tanımlayıcı olarak faydasızdır.

Ayrıca, oidancak bu yalnızca masayı oluştururken özellikle sorarsanız vardır.


Bu işe yarıyor, ancak ne kadar güvenilir? Dikkat etmem gereken herhangi bir 'sorun' var mı? Sorgu çalışırken tablodaki değerleri VACUUM FULLdeğiştirirlerse otomatik vakumun sorun yaratması mümkün müdür ctid?
Whatsit

2
Artımlı VACUUM'lar ctids'i değiştirmeyecek, sanmıyorum. Bu sadece her sayfada sıkıştırıldığı ve ctid sadece satır numarası olduğu için sayfa ofseti değil. Bir VAKUM TAM veya KÜME operasyonu olurdu ctid değiştirmek, ancak bu işlemler ilk masaya bir erişim özel bir kilit alın.
araqnid

@Whatsit: Benim izlenim ctidbelgelerinde yani ctidbir gettoda-FK başka bir masaya koymak örneğin bu SİL iş tamam yapmaya kararlı yeterli ama değil kararlı yeterli vardır. Muhtemelen GÜNCELLEŞTİRMEZsiniz, logtableböylece değişen ctide- postalar hakkında endişelenmenize gerek VACUUM FULLkalmaz ve tabloyu kilitler ( postgresql.org/docs/current/static/routine-vacuuming.html ), böylece endişelenmenize gerek kalmaz. diğer yol ctiddeğişebilir. @ araqnid'in PostgreSQL-Fu'su oldukça güçlü ve dokümanlar önyükleme konusunda onunla hemfikir.
mu çok kısa

Açıklama için ikinize de teşekkürler. Dokümanlara baktım ama onları doğru yorumladığımdan emin değildim. Bundan önce ctids ile hiç karşılaşmamıştım.
Whatsit

Postgres birleşimlerde TID taramasını kullanamadığı için bu aslında oldukça kötü bir çözümdür (IN özel bir durumdur). Plana bakarsanız, oldukça korkunç olmalı. Bu nedenle, "çok hızlı" yalnızca CTID'yi açıkça belirttiğinizde geçerlidir. Bahsedilen, sürüm 10'dan itibaren.
greatvovan

53

Postgres belgeleri, IN ve alt sorgu yerine dizi kullanılmasını önerir. Bu çok daha hızlı çalışmalı

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

Bu ve diğer bazı numaralar burada bulunabilir


@Konrad Garus İşte bağlantı , 'Hızlı ilk n satır kaldırma'
criticus

1
@BlakeRegalia Hayır, çünkü belirtilen tabloda birincil anahtar yok. Bu, ilk 10'da "ID" bulunan tüm satırları siler. Tüm satırlar aynı ID'ye sahipse, tüm satırlar silinecektir.
Philip Whitehouse

6
Bundan any (array( ... ));daha in ( ... )hızlıysa, sorgu iyileştiricisinde bir hata gibi görünüyorsa - bu dönüşümü tespit edebilmeli ve aynı şeyi verinin kendisiyle yapabilmelidir.
rjmunro

1
Ben göre daha düşük hatırı sayılır için bu yöntemi bulunan INbir on UPDATE(olabilir fark).
jmervine

1
12 GB tablodaki ölçüm: ilk sorgu 450..1000 ms, ikincisi 5..7 saniye: Hızlı bir: cs_logging'den silin, burada id = herhangi bir (dizi (date_created <now () - aralık '1 gün olan cs_logging'den kimliği seçin '* 30 ve partition_key, id sınırına göre'% I 'siparişi gibi 500)) Yavaş bir: cs_logging'den silin (burada cs_logging'den kimliği seçin, burada date_created <now () - aralık' 1 gün '* 30 ve partition_key'% gibi Kimlik sınırına göre sipariş veriyorum 500). Ctid'ı kullanmak çok daha yavaştı (dakikalar).
Guido Leenders

14
delete from logtable where log_id in (
    select log_id from logtable order by timestamp limit 10);

2

HERHANGİ 10 kaydı silmek istediğinizi varsayarsak (sıralama olmadan) şunu yapabilirsiniz:

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

Benim kullanım durumum için, 10 milyon kayıtları silmek, bu daha hızlı oldu.


1

Tek tek satırlar için silme işleminin üzerinden geçen bir prosedür yazabilirsiniz, prosedür, silmek istediğiniz öğe sayısını belirtmek için bir parametre alabilir. Ancak bu, MySQL ile karşılaştırıldığında biraz fazla.


0

Birincil anahtarınız yoksa, birleşik anahtarla Where IN sözdizimini kullanabilirsiniz.

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

Bu benim için çalıştı.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.