Tenho que adicionar uma restrição única a uma tabela existente. Isso é bom, exceto que a tabela já tem milhões de linhas e muitas das linhas violam a restrição exclusiva que preciso adicionar.
Qual é a abordagem mais rápida para remover as linhas problemáticas? Eu tenho uma instrução SQL que encontra as duplicatas e as exclui, mas está demorando muito para ser executada. Existe outra maneira de resolver este problema? Talvez fazendo backup da tabela e restaurando após a adição da restrição?
CREATE TABLE tmp AS SELECT ...;
. Então você não precisa nem mesmo descobrir qual é o layouttmp
. :)Algumas dessas abordagens parecem um pouco complicadas e geralmente faço isso como:
Dada a tabela
table
, deseja-se exclusivo em (campo1, campo2) mantendo a linha com o campo máximo3:Por exemplo, tenho uma tabela,
user_accounts
e quero adicionar uma restrição exclusiva para e-mail, mas tenho algumas duplicatas. Diga também que desejo manter o criado mais recentemente (id máximo entre duplicatas).USING
não é SQL padrão, é uma extensão do PostgreSQL (mas muito útil), mas a pergunta original menciona especificamente o PostgreSQL.fonte
USING
faz no postgresql?WHERE table1.ctid<table2.ctid
- não há necessidade de adicionar coluna serialEm vez de criar uma nova tabela, você também pode inserir novamente linhas exclusivas na mesma tabela depois de truncá-la. Faça tudo em uma transação . Opcionalmente, você pode descartar a tabela temporária no final da transação automaticamente com
ON COMMIT DROP
. Ver abaixo.Essa abordagem só é útil quando há muitas linhas para excluir de toda a tabela. Para apenas algumas duplicatas, use um plano
DELETE
.Você mencionou milhões de linhas. Para tornar a operação rápida, você deseja alocar buffers temporários suficientes para a sessão. A configuração deve ser ajustada antes que qualquer buffer temporário seja usado na sessão atual. Descubra o tamanho da sua mesa:
Defina de
temp_buffers
acordo. Arredonde generosamente porque a representação na memória precisa de um pouco mais de RAM.Este método pode ser superior à criação de uma nova tabela se existirem objetos dependentes. Exibições, índices, chaves estrangeiras ou outros objetos que fazem referência à tabela.
TRUNCATE
faz com que você comece do zero de qualquer maneira (novo arquivo em segundo plano) e é muito mais rápido do queDELETE FROM tbl
com tabelas grandes (DELETE
na verdade, pode ser mais rápido com tabelas pequenas).Para tabelas grandes, é regularmente mais rápido descartar índices e chaves estrangeiras, recarregar a tabela e recriar esses objetos. No que diz respeito às restrições fk, você deve ter certeza de que os novos dados são válidos, ou você encontrará uma exceção ao tentar criar o fk.
Observe que
TRUNCATE
requer um travamento mais agressivo do queDELETE
. Isso pode ser um problema para tabelas com carga simultânea pesada.Se
TRUNCATE
não for uma opção ou geralmente para tabelas pequenas e médias, há uma técnica semelhante com um CTE de modificação de dados (Postgres 9.1 +):Mais lento para mesas grandes, porque
TRUNCATE
lá é mais rápido. Mas pode ser mais rápido (e mais simples!) Para tabelas pequenas.Se você não tiver nenhum objeto dependente, poderá criar uma nova tabela e excluir a antiga, mas dificilmente ganhará algo com essa abordagem universal.
Para tabelas muito grandes que não cabem na RAM disponível , criar uma nova tabela será consideravelmente mais rápido. Você terá que pesar isso contra possíveis problemas / sobrecarga com objetos dependentes.
fonte
TRUNCATE
. Como disse Erwin, certifique-se de que ele existe antes de truncar sua mesa. Ver a resposta de @codebykatON COMMIT DROP
, para que as pessoas que perderem a parte onde escrevi "em uma transação" não percam dados. E eu adicionei BEGIN / COMMIT para esclarecer "uma transação".Você pode usar oid ou ctid, que normalmente são colunas "não visíveis" na tabela:
fonte
NOT EXISTS
deve ser consideravelmente mais rápido :DELETE FROM tbl t WHERE EXISTS (SELECT 1 FROM tbl t1 WHERE t1.dist_col = t.dist_col AND t1.ctid > t.ctid)
- ou use qualquer outra coluna ou conjunto de colunas para classificar para escolher um sobrevivente.NOT EXISTS
?EXISTS
aqui. Leia assim: "Exclua todas as linhas onde existe qualquer outra linha com o mesmo valor,dist_col
mas maiorctid
" O único sobrevivente por grupo de idiotas será aquele com o maiorctid
.LIMIT
se você souber o número de duplicatas.A função de janela do PostgreSQL é útil para esse problema.
Consulte Excluindo duplicatas .
fonte
De uma lista de e-mails antiga do postgresql.org :
Valores únicos
Valores duplicados
Mais uma duplicata dupla
Selecione as linhas duplicadas
Excluir linhas duplicadas
Nota: PostgreSQL não suporta apelidos na tabela mencionada na
from
cláusula de exclusão.fonte
Consulta generalizada para excluir duplicatas:
A coluna
ctid
é uma coluna especial disponível para cada tabela, mas não visível, a menos que seja especificamente mencionada. Octid
valor da coluna é considerado único para cada linha de uma tabela.fonte
GROUP BY
cláusula corretamente - este deve ser o 'critério de exclusividade' que é violado agora ou se você quiser que a chave detecte duplicatas. Se especificado incorretamente, não funcionará corretamenteAcabei de usar a resposta de Erwin Brandstetter com sucesso para remover duplicatas em uma tabela de junção (uma tabela sem seus próprios IDs primários), mas descobri que há uma advertência importante.
Incluir
ON COMMIT DROP
significa que a tabela temporária será eliminada no final da transação. Para mim, isso significava que a tabela temporária não estava mais disponível no momento em que fui inseri-la!Eu apenas fiz
CREATE TEMPORARY TABLE t_tmp AS SELECT DISTINCT * FROM tbl;
e tudo funcionou bem.A tabela temporária é eliminada no final da sessão.
fonte
Esta função remove duplicatas sem remover índices e faz isso em qualquer tabela.
Uso:
select remove_duplicates('mytable');
fonte
fonte
Se você tem apenas uma ou algumas entradas duplicadas, e elas estão realmente duplicadas (ou seja, aparecem duas vezes), você pode usar a
ctid
coluna "oculta" , conforme proposto acima, junto comLIMIT
:Isso excluirá apenas a primeira das linhas selecionadas.
fonte
Primeiro, você precisa decidir quais de suas "duplicatas" você manterá. Se todas as colunas forem iguais, OK, você pode excluir qualquer uma delas ... Mas talvez você queira manter apenas a mais recente, ou algum outro critério?
O caminho mais rápido depende da sua resposta à pergunta acima, e também da% de duplicatas na tabela. Se você descartar 50% de suas linhas, é melhor fazer isso
CREATE TABLE ... AS SELECT DISTINCT ... FROM ... ;
, e se você excluir 1% das linhas, usar DELETE é melhor.Também para operações de manutenção como essa, geralmente é bom definir
work_mem
uma boa parte de sua RAM: execute EXPLAIN, verifique o número N de tipos / hashes e defina work_mem para sua RAM / 2 / N. Use muita RAM; é bom para velocidade. Contanto que você tenha apenas uma conexão simultânea ...fonte
Estou trabalhando com PostgreSQL 8.4. Quando executei o código proposto, descobri que ele não estava realmente removendo as duplicatas. Ao executar alguns testes, descobri que adicionar "DISTINCT ON (duplicate_column_name)" e "ORDER BY duplicate_column_name" funcionou. Não sou um guru de SQL, encontrei isso no documento PostgreSQL 8.4 SELECT ... DISTINCT.
fonte
Isso funciona muito bem e é muito rápido:
fonte
Exclua duplicatas por coluna (s) e mantenha a linha com o id mais baixo. O padrão é retirado do wiki postgres
Usando CTEs, você pode obter uma versão mais legível do acima por meio deste
fonte
fonte