Eliminar geometría duplicada en tablas postgis

11

Después, no sé qué pasó, ¡todas mis entradas en mis tablas de PostGIS se duplican! Intenté esto para eliminarlos, pero no elimina ninguno / todos los duplicados:

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

o esto:

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 encontré una manera fácil, que está funcionando en mi caso:

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

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

MAPA
fuente
1
¿Podría por favor proporcionar la planet_osm_pointestructura de la tabla actual ? significa tipo de columnas. Puede escribir un código básico de Python para recopilar las columnas seleccionadas, si tiene dificultades con las funciones de SQL.
Zia
Sí, eso funcionará si tiene otra identificación (ctid) que no esté duplicada. Supuse que todo era idéntico y duplicado dos veces.
John Powell
Lo siento, pero no entendí este ctidenfoque. ¿Esta columna se ha agregado manualmente después del evento de duplicación?
Zia
1
"La columna 'ctid' es una columna especial disponible para todas las tablas, pero no visible a menos que se mencione específicamente. El valor de la columna ctid se considera único para cada fila de una tabla. -" technobytz.com/most-useful-postgresql-commands.html
MAPA

Respuestas:

20

Una forma de hacerlo es utilizar una función de ventana y una partición por geometría, de modo que cada geometría repetida obtenga una identificación: 1, 2, 3, etc. (o 1, 2) en su caso, y luego simplemente seleccione de tabla donde id = 1, para recuperar un conjunto único de valores (atributos y geometría), por ejemplo,

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, también necesitaría agregar las otras columnas de osm en la selección, esto es solo para ilustración, pero esto es básicamente como agrupar por geometría y simplemente seleccionar la primera instancia de cada una. Tenga en cuenta que debe usar ST_AsBinary en Partition By ya que, de lo contrario, la comparación se realiza en el cuadro delimitador, no en la geometría real.

Como todos los demás atributos son presumiblemente los mismos para cada par de geometría, sería algo así para todos los demás campos, incluido osm_id, y para crear una tabla nueva y única:

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;

Esto podría ser más rápido que eliminar de una tabla existente, especialmente si hay muchos índices en su lugar.

EDITAR . Reescrito para facilitar la lectura, pero dejando el crédito a dbaston por llamar mi atención sobre ST_AsBinary (geom)

John Powell
fuente
Gracias. He hecho una nota. Pero, por ejemplo, consideremos este escenario donde hay un punto geom que es tanto una parada de autobús como un cruce (no considere los datos de OSM). Entonces tendremos dos geom idénticos que representan estas dos características. El uso de su enfoque eliminará una de las características. Lo que estoy diciendo es que ¿cómo resolver este problema cuando no hay una columna específica Partition By?
Zia
1
Hola Zia, entonces particiona por (geom, atributo), de modo que ambos tienen que ser iguales para obtener la misma identificación. En su ejemplo, el geom sería el mismo, el atributo no, por lo que row_number () devolvería 1 para ambos.
John Powell
1
Esto identifica actualmente geometrías distintas con un cuadro delimitador compartido como duplicados (ya que PARTITION BYutiliza el =operador, que funciona en la igualdad del cuadro delimitador). Sugeriría cambiar lo anterior PARTITION BY ST_AsBinary(geom)como una solución.
dbaston
Creo que debería aceptar esta respuesta, o indicar cómo no responde la pregunta.
John Powell
1
@AndreSilva. Hecho. Siempre estoy nervioso por cambiar las respuestas sin dejar en claro que ha habido una edición. Pero, tienes razón, esto es mucho más legible.
John Powell
2

Aquí hay otro método que usé para eliminar duplicados de una descarga de datos de suelo SSURGO. Los archivos de forma descargados no tenían una clave única, por lo que se generó una columna pk en serie cuando importé a PostGIS. Hubo algunas superposiciones en los conjuntos de datos, y sin querer importé algunos registros más de una vez mientras desarrollaba el script de importación.

El grupo por declaración incluye todas las columnas de la tabla, excluyendo la clave primaria.

Solo eliminará un conjunto de filas duplicadas cada vez que se ejecute, por lo que si una fila se repite 4 veces, deberá ejecutar esto un mínimo de 3 veces. Es probable que esto no sea tan rápido como la solución de John, pero funciona dentro de una tabla existente. También funciona cuando no tiene una identificación única para cada geometría única (como el osm_id en la pregunta original).

Usé un script de Python para repetir hasta que desaparecieron los duplicados, y luego ejecuté un vacío completo. Creo que el guión y el vacío tomaron aproximadamente 30 minutos para unos cientos de miles de duplicados de alrededor de 1.5 millones de registros en 6 tablas. Mucho bueno para una sola vez. Atravesó las mesas pequeñas muy rápidamente.

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

EDITAR: SQL modificado para evitar ejecutar varias veces según la sugerencia de @dbaston (a continuación). Probé este método de consulta en una tabla grande (~ 1.5 millones de registros, ~ 25,000 filas de puntos duplicados), y después de que se ejecutó durante 45 minutos, cancelé su ejecución. Ejecutar con el SQL anterior (usando una subconsulta más pequeña de HAVING COUNT) redujo cada ejecución a menos de 30 segundos. Después de correr 3 veces, se hizo con todos los duplicados. El SQL a continuación debería estar bien para tablas pequeñas.

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
fuente
1
Si no tiene una clave principal, puede usar la ctidcolumna siempre disponible (ver documentos ).
dbaston el
1
Puede evitar ejecutar esto varias veces comprobandoprimary_key NOT IN (SELECT max(primary_key) ....
dbaston
@dbaston tomé notas en la respuesta anterior. La eliminación de HAVING COUNT aumenta en gran medida el tamaño de los resultados de la subconsulta y, por lo tanto, el número de comparaciones que debe realizar la declaración de eliminación. Me sorprendió la duración de la ejecución en una mesa grande.
Nate Wanner el
@NateWanner NO EXISTE podría darle algo de velocidad adicional en este caso.
Michal Zimmermann el
@MichalZimmermann No estoy seguro de seguirte: ambas versiones esperan que la subconsulta arroje un resultado.
Nate Wanner el
1

Una respuesta más general para eliminar fácilmente los duplicados de geometría en la tabla PostGIS. El siguiente comando elimina todas las características con geometría duplicada en "nombre_tabla" en función de la clave primaria (columna "gid") y la igualdad de geometría (columna "geom"). Tenga en cuenta que realmente elimina todos los duplicados de geometría, ¡se habrán ido para siempre! Tal vez una copia de seguridad primero?

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);
Miró
fuente