Come posso eliminare un numero fisso di righe con l'ordinamento in PostgreSQL?


107

Sto cercando di portare alcune vecchie query MySQL su PostgreSQL, ma ho problemi con questo:

DELETE FROM logtable ORDER BY timestamp LIMIT 10;

PostgreSQL non consente ordini o limiti nella sintassi di eliminazione e la tabella non ha una chiave primaria, quindi non posso utilizzare una sottoquery. Inoltre, voglio preservare il comportamento in cui la query elimina esattamente il numero o i record forniti, ad esempio, se la tabella contiene 30 righe ma hanno tutte lo stesso timestamp, voglio comunque eliminare 10, anche se non importa quale 10.

Così; come faccio a eliminare un numero fisso di righe con l'ordinamento in PostgreSQL?

Modifica: nessuna chiave primaria significa che non ci sono log_idcolonne o simili. Ah, le gioie dei sistemi legacy!


1
Perché non aggiungere la chiave primaria? Pezzo o' torta in PostgreSQL: alter table foo add column id serial primary key.
Wayne Conrad

Questo era il mio approccio iniziale, ma altri requisiti lo impediscono.
Che cosa è il

Risposte:


159

Potresti provare a utilizzare ctid:

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

Il ctidè:

La posizione fisica della versione riga all'interno della relativa tabella. Si noti che sebbene ctidpossa essere utilizzato per individuare la versione della riga molto rapidamente, quella di una riga ctidcambierà se viene aggiornata o spostata da VACUUM FULL. Pertanto ctidè inutile come identificatore di riga a lungo termine.

C'è anche oidma esiste solo se lo chiedi specificatamente quando crei la tabella.


Funziona, ma quanto è affidabile? Ci sono dei "trucchi" a cui devo prestare attenzione? È possibile VACUUM FULLche l'autovacuum causi problemi se modificano i ctidvalori nella tabella mentre la query è in esecuzione?
Che cosa è il

2
I VACUUM incrementali non cambieranno i ctid, non credo. Dal momento che si compatta solo all'interno di ogni pagina, e il ctid è solo il numero di riga, non un offset di pagina. Un VACUUM FULL o un'operazione CLUSTER sarebbero cambiare la ctid, ma quelle operazioni prendere un blocco esclusivo di accesso sulla prima tabella.
araqnid

@ Whatsit: La mia impressione della ctiddocumentazione è che ctidsia abbastanza stabile da far funzionare bene questo DELETE ma non abbastanza stabile da, ad esempio, mettere in un'altra tabella come ghetto-FK. Presumibilmente non si aggiorna logtablequindi non devi preoccuparti di cambiare ctids e VACUUM FULLblocca la tabella ( postgresql.org/docs/current/static/routine-vacuuming.html ) quindi non devi preoccuparti di l'altro modo che ctidpuò cambiare. PostgreSQL-Fu di @araqnid è abbastanza forte e i documenti sono d'accordo con lui per l'avvio.
mu è troppo breve

Grazie ad entrambi per il chiarimento. Ho esaminato i documenti ma non ero sicuro di interpretarli correttamente. Non avevo mai incontrato ctid prima di questo.
Che cosa è

Questa è in realtà una soluzione piuttosto scadente poiché Postgres non è in grado di utilizzare la scansione TID nei join (IN è un caso particolare). Se guardi il piano, dovrebbe essere piuttosto terribile. Quindi "molto rapidamente" si applica solo quando si specifica esplicitamente CTID. Detto è come dalla versione 10.
greatvovan

53

La documentazione di Postgres consiglia di utilizzare array invece di IN e subquery. Questo dovrebbe funzionare molto più velocemente

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

Questo e altri trucchi possono essere trovati qui


@Konrad Garus Here you go link , 'Fast first n rows removal'
criticus

1
@BlakeRegalia No, perché non c'è una chiave primaria nella tabella specificata. Questo eliminerà tutte le righe con un "ID" trovato nelle prime 10. Se tutte le righe hanno lo stesso ID, tutte le righe verranno eliminate.
Philip Whitehouse

6
Se any (array( ... ));è più veloce di in ( ... )così suona come un bug nell'ottimizzatore di query, dovrebbe essere in grado di individuare quella trasformazione e fare la stessa cosa con i dati stessi.
rjmunro

1
Ho trovato questo metodo molto più lento rispetto all'utilizzo INsu un UPDATE(che potrebbe essere la differenza).
jmervine

1
Misura sulla tabella da 12 GB: prima query 450..1000 ms, seconda 5..7 secondi: veloce: elimina da cs_logging dove id = any (array (seleziona id da cs_logging dove date_created <now () - intervallo '1 giorni '* 30 e partition_key come'% I 'order by id limit 500)) Slow one: elimina da cs_logging dove id in (seleziona id da cs_logging dove date_created <now () - intervallo' 1 giorni '* 30 e partition_key come'% Ordino per ID limite 500). L'uso di ctid è stato molto più lento (minuti).
Guido Leenders

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

2

Supponendo che tu voglia eliminare QUALSIASI 10 record (senza l'ordinamento) potresti farlo:

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

Per il mio caso d'uso, l'eliminazione di 10 milioni di record, si è rivelato più veloce.


1

È possibile scrivere una procedura che esegue un ciclo sulla cancellazione per singole righe, la procedura potrebbe richiedere un parametro per specificare il numero di elementi che si desidera eliminare. Ma questo è un po 'eccessivo rispetto a MySQL.


0

Se non si dispone di una chiave primaria, è possibile utilizzare la sintassi dell'array Where IN con una chiave composta.

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

Questo ha funzionato per me.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.