Como excluo um número fixo de linhas com classificação no PostgreSQL?

107

Estou tentando portar algumas consultas MySQL antigas para PostgreSQL, mas estou tendo problemas com esta:

DELETE FROM logtable ORDER BY timestamp LIMIT 10;

O PostgreSQL não permite ordenação ou limites em sua sintaxe de exclusão, e a tabela não possui uma chave primária, então não posso usar uma subconsulta. Além disso, quero preservar o comportamento em que a consulta exclui exatamente o número ou os registros fornecidos - por exemplo, se a tabela contém 30 linhas, mas todos têm o mesmo carimbo de data / hora, ainda quero excluir 10, embora não importe quais 10.

Assim; como excluo um número fixo de linhas com classificação no PostgreSQL?

Editar: Sem chave primária significa que não há log_idcoluna ou similar. Ah, as alegrias dos sistemas legados!

O que é isso
fonte
1
Por que não adicionar a chave primária? Peça o' bolo no PostgreSQL: alter table foo add column id serial primary key.
Wayne Conrad,
Essa foi minha abordagem inicial, mas outros requisitos o impedem.
Whatsit

Respostas:

159

Você pode tentar usar ctid:

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

O ctidé:

A localização física da versão da linha em sua tabela. Observe que embora o ctidpossa ser usado para localizar a versão da linha muito rapidamente, uma linha ctidserá alterada se for atualizada ou movida por VACUUM FULL. Portanto, ctidé inútil como um identificador de linha de longo prazo.

Há também oidmas isso só existe se você solicitar especificamente quando criar a mesa.

mu é muito curto
fonte
Isso funciona, mas quão confiável é? Há alguma 'pegadinha' que preciso procurar? É possível paraVACUUM FULL ou autovacuum causar problemas se eles alterarem os ctidvalores na tabela durante a execução da consulta?
Whatsit
2
VÁCUOS incrementais não mudam as células tóxicas, não acho. Uma vez que isso apenas compacta em cada página, e o ctid é apenas o número da linha, não um deslocamento da página. A VACUUM FULL ou uma operação CLUSTER iria mudar o ctid, mas essas operações têm um bloqueio exclusivo de acesso na mesa primeiro.
araqnid
@Whatsit: Minha impressão da ctiddocumentação é que ctidé estável o suficiente para fazer esse DELETE funcionar bem, mas não estável o suficiente para, por exemplo, colocar em outra mesa como um gueto-FK. Provavelmente, você não atualizar o logtableque você não precisa se preocupar com que a mudança ctids e VACUUM FULLfaz bloquear a tabela ( postgresql.org/docs/current/static/routine-vacuuming.html ), assim você não precisa se preocupar com a outra maneira que isso ctidpode mudar. O PostgreSQL-Fu de @araqnid é muito forte e os documentos concordam com ele.
mu é muito curto
Obrigado a ambos pelo esclarecimento. Eu olhei para os documentos, mas não tinha certeza se os estava interpretando corretamente. Eu nunca tinha encontrado ctids antes disso.
Whatsit
Na verdade, esta é uma solução muito ruim, pois o Postgres não é capaz de usar a varredura TID em joins (IN é um caso particular disso). Se você olhar para o plano, deve ser terrível. Portanto, "muito rapidamente" se aplica apenas quando você especifica o CTID explicitamente. O dito é a partir da versão 10.
greatvovan
53

A documentação do Postgres recomenda usar array em vez de IN e subconsulta. Isso deve funcionar muito mais rápido

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

Este e alguns outros truques podem ser encontrados aqui

criticus
fonte
@Konrad Garus Aqui está link , ' Remoção rápida das primeiras n linhas'
criticus
1
@BlakeRegalia Não, porque não há chave primária na tabela especificada. Isso excluirá todas as linhas com um "ID" encontrado nos primeiros 10. Se todas as linhas tiverem o mesmo ID, todas as linhas serão excluídas.
Philip Whitehouse
6
Se any (array( ... ));for mais rápido do que in ( ... )isso, parece um bug no otimizador de consulta - ele deve ser capaz de detectar essa transformação e fazer a mesma coisa com os próprios dados.
rjmunro,
1
Achei esse método consideravelmente mais lento do que INem um UPDATE(o que pode ser a diferença).
jmervine
1
Medição na tabela de 12 GB: primeira consulta 450..1000 ms, segunda uma 5..7 segundos: Rápida: excluir de cs_logging onde id = any (array (selecionar id de cs_logging onde date_created <now () - intervalo '1 dias '* 30 e partition_key como'% I 'ordenar por limite de id 500)) Um lento: excluir de cs_logging onde id em (selecionar id de cs_logging onde date_created <now () - intervalo' 1 dias '* 30 e partition_key como'% I 'pedido pelo limite de id 500). Usar o ctid era muito mais lento (minutos).
Guido Leenders de
14
delete from logtable where log_id in (
    select log_id from logtable order by timestamp limit 10);
Konrad Garus
fonte
2

Supondo que você deseja excluir QUALQUER 10 registros (sem o pedido), você pode fazer isso:

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

Para o meu caso de uso, excluir registros de 10 milhões, isso acabou sendo mais rápido.

Patrick Hüsler
fonte
1

Você pode escrever um procedimento que faz um loop sobre a exclusão de linhas individuais, o procedimento pode receber um parâmetro para especificar o número de itens que deseja excluir. Mas isso é um pouco exagero em comparação com o MySQL.

Bernhard
fonte
0

Se você não tem uma chave primária, pode usar a sintaxe Where IN da matriz com uma chave composta.

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

Isso funcionou para mim.

user2449151
fonte