Excluir geometria duplicada em tabelas postgis

11

Depois - não sei o que aconteceu - todas as minhas entradas nas minhas tabelas PostGIS são duplicadas! Eu tentei isso para excluí-los, mas ele não exclui nenhuma / todas as duplicatas:

DELETE FROM planet_osm_point
       WHERE osm_id NOT IN (SELECT min(osm_id)
                        FROM planet_osm_point
                        GROUP BY osm_id)

ou isto:

DELETE FROM planet_osm_point
WHERE osm_id NOT IN (
    select max(dup.osm_id)
    from planet_osm_point as dup
    group by way);

EDITAR:

Finalmente encontrei uma maneira fácil, que está funcionando no meu caso:

DELETE FROM planet_osm_point WHERE ctid NOT IN
(SELECT max(ctid) FROM planet_osm_point GROUP BY osm_id);

encontrado nesta página: http://technobytz.com/most-useful-postgresql-commands.html

MAPA
fonte
1
Você poderia fornecer a planet_osm_pointestrutura atual da tabela? significa tipo de colunas. Você pode escrever um código Python básico para coletar as colunas selecionadas, se tiver dificuldades com as funções SQL.
Zia
Sim, isso funcionará se você tiver outro ID (ctid) que não seja duplicado. Eu estava assumindo que tudo era idêntico e duplicado duas vezes.
John Powell
Desculpe, mas eu não entendi essa ctidabordagem. Esta coluna foi adicionada manualmente após o evento de duplicação?
Zia
1
"A coluna 'ctid' é uma coluna especial disponível para todas as tabelas, mas não visível, a menos que mencionado especificamente. O valor da coluna ctid é considerado único para todas as linhas de uma tabela. -" technobytz.com/most-useful-postgresql-commands.html
MAP

Respostas:

20

Uma maneira de fazer isso é usar uma função de janela e particionar por geometria, para que cada geometria repetida obtenha um ID: 1, 2, 3, etc (ou 1, 2) no seu caso e, em seguida, basta selecionar no tabela onde o id = 1, para obter um conjunto exclusivo de valores (atributos e geometria) de volta, por exemplo,

WITH unique_geoms (id, geom) as 
 (SELECT row_number() OVER (PARTITION BY ST_AsBinary(geom)) AS id, geom FROM some_table)
SELECT geom 
FROM unique_geoms 
WHERE id=1;

Obviamente, você precisaria adicionar as outras colunas osm também no select, isso é apenas para ilustração, mas isso é basicamente como agrupar por geometria e apenas selecionar a primeira instância de cada uma. Observe que você precisa usar ST_AsBinary na partição. Caso contrário, a comparação é feita na caixa delimitadora, não na geometria real.

Como todos os outros atributos são presumivelmente os mesmos para cada par de geometrias, você faria algo assim para todos os outros campos, incluindo osm_id, e para realmente criar uma nova tabela exclusiva:

CREATE TABLE osm_unique AS
 WITH unique_geoms (id, osm_id, attr1, attr2,... attrn, geom) AS 
  (SELECT row_number() OVER (PARTITION BY ST_AsBinary(geom)) AS id, osm_id, attr1, attr2,... attrn, geom 
    FROM osm_planet_point)
 SELECT osm_id, attr1, attr2,... attrn, geom 
 FROM unique_geoms 
 WHERE id=1;

Isso pode ser mais rápido do que excluir de uma tabela existente, especialmente se houver muitos índices em vigor.

EDIT . Reescrito para facilitar a leitura, mas, deixando o crédito para o dbaston por chamar minha atenção para ST_AsBinary (geom)

John Powell
fonte
Obrigado. Eu fiz uma anotação. Mas, por exemplo, vamos considerar este cenário em que existe um ponto geom que é um ponto de ônibus e uma travessia (não considere os dados OSM). Então teremos dois geom idênticos representando esses dois recursos. Usar sua abordagem removerá um dos recursos. O que estou dizendo é que como resolver esse problema quando não existe uma coluna específica Partition By?
Zia
1
Oi Zia, então você particiona por (geom, attribute), para que ambos tenham que ser iguais para obter o mesmo ID. No seu exemplo, o geom seria o mesmo, o atributo não; portanto, row_number () retornaria 1 para ambos.
John Powell
1
Atualmente, ele identifica geometrias distintas com uma caixa delimitadora compartilhada como duplicadas (já que PARTITION BYusa o =operador, que trabalha com a igualdade da caixa delimitadora). Eu sugiro alterar o acima para PARTITION BY ST_AsBinary(geom)como uma correção.
Dbaston 13/04
Eu acho que você deve aceitar esta resposta ou declarar como ela não responde à pergunta.
John John Powell
1
@AndreSilva. Feito. Estou sempre nervoso em alterar as respostas sem deixar claro que houve uma edição. Mas, você está certo, isso é muito mais legível.
John Powell
2

Aqui está outro método que eu usei para remover duplicatas de um download de dados do solo SSURGO. Os shapefiles baixados não tinham uma chave única; portanto, uma coluna pk serial foi gerada quando eu importei para o PostGIS. Houve algumas sobreposições nos conjuntos de dados e, inadvertidamente, importei alguns registros mais de uma vez ao desenvolver o script de importação.

O grupo por instrução inclui todas as colunas da tabela, excluindo a chave primária.

Ele excluirá apenas um conjunto de linhas duplicadas toda vez que for executado, portanto, se uma linha for repetida 4 vezes, você precisará executá-lo no mínimo 3 vezes. Provavelmente não é tão rápido quanto a solução de John, mas funciona dentro de uma tabela existente. Também funciona quando você não possui um ID exclusivo para cada geometria exclusiva (como o osm_id na pergunta original).

Usei um script python para repetir até que as duplicatas desaparecessem e, em seguida, executei um vácuo completo. Eu acho que o script e o vácuo levaram cerca de 30 minutos para algumas centenas de milhares de cópias de cerca de 1,5 milhão de registros em 6 tabelas. Muito bom para uma única vez. Atravessou as mesas pequenas muito rapidamente.

DELETE FROM schema.table 
  WHERE primary_key IN
    (SELECT MAX(primary_key)
     FROM schema.table 
     GROUP BY ST_AsBinary(geom), col_1, col_2, col_etc
     HAVING COUNT(primary_key) > 1);

EDIT: SQL modificado para evitar a execução várias vezes com base na sugestão @dbaston (abaixo). Tentei esse método de consulta em uma tabela grande (~ 1,5 milhão de registros, ~ 25.000 linhas de pontos duplicados) e, depois de executado por 45 minutos, cancelei sua execução. A execução com o SQL acima (usando uma subconsulta menor da HAVING COUNT) reduziu cada execução para menos de 30 segundos. Depois de executar 3 vezes, foi feito com todas as duplicatas. O SQL abaixo deve estar OK para tabelas pequenas.

DELETE FROM schema.table 
  WHERE primary_key NOT IN
    (SELECT MAX(primary_key)
     FROM schema.table 
     GROUP BY ST_AsBinary(geom), col_1, col_2, col_etc);
Nate Wanner
fonte
1
Se você não possui uma chave primária, pode usar a ctidcoluna sempre disponível (consulte a documentação ).
dbaston
1
Você pode evitar executar isso várias vezes verificandoprimary_key NOT IN (SELECT max(primary_key) ....
dbaston 1/16
@dbaston Fiz anotações na resposta acima. A remoção de HAVING COUNT aumenta muito o tamanho dos resultados da subconsulta e, portanto, o número de comparações que a instrução de exclusão precisa fazer. Fiquei surpreso com quanto tempo de execução havia em uma mesa grande.
Nate Wanner #
@NateWanner NOT EXISTS pode dar-lhe alguma velocidade adicional neste caso.
Michal Zimmermann
@MichalZimmermann Não tenho certeza de segui-lo - as duas versões esperam que a subconsulta retorne um resultado.
Nate Wanner #
1

Uma resposta mais geral para excluir facilmente duplicatas de geometria na tabela PostGIS. O comando a seguir exclui todos os recursos com geometria duplicada em "table_name" com base na chave primária (coluna "gid") e na igualdade de geometria (coluna "geom"). Esteja ciente de que realmente exclui todas as duplicatas da geometria, elas desaparecerão para sempre! Talvez voltar primeiro?

DELETE FROM schema_name.table_name a
    USING schema_name.table_name b 
WHERE a.gid > b.gid AND st_equals(a.geom, b.geom);
Miro
fonte