Supprimer la géométrie en double dans les tableaux de postgis

11

Après - je ne sais pas ce qui s'est passé - toutes mes entrées dans mes tables PostGIS sont doublées! J'ai essayé cela pour les supprimer mais cela ne supprime aucun / tous les doublons:

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

ou ca:

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

ÉDITER:

J'ai finalement trouvé un moyen simple, qui fonctionne dans mon cas:

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

trouvé sur cette page: http://technobytz.com/most-useful-postgresql-commands.html

CARTE
la source
1
Pourriez-vous s'il vous plaît fournir la planet_osm_pointstructure actuelle du tableau? signifie le type de colonnes. Vous pouvez écrire un code Python de base pour collecter les colonnes sélectionnées, si vous rencontrez des difficultés avec les fonctions SQL.
Zia
Oui, cela fonctionnera si vous avez un autre identifiant (ctid) qui n'est pas dupliqué. Je supposais que tout était identique et dupliqué deux fois.
John Powell
Désolé, mais je n'ai pas eu cette ctidapproche. Cette colonne a été ajoutée manuellement après l'événement de duplication?
Zia
1
"La colonne 'ctid' est une colonne spéciale disponible pour toutes les tables mais non visible sauf mention contraire. La valeur de la colonne ctid est considérée comme unique pour chaque ligne d'une table. -" technobytz.com/most-useful-postgresql-commands.html
MAP

Réponses:

20

Pour ce faire, vous pouvez utiliser une fonction de fenêtre et une partition par géométrie, de sorte que chaque géométrie répétée reçoive un identifiant: 1, 2, 3, etc. (ou 1, 2) dans votre cas, puis sélectionnez simplement table où l'id = 1, pour récupérer un ensemble unique de valeurs (attributs et géométrie), par exemple,

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;

Évidemment, vous devrez également ajouter les autres colonnes osm dans la sélection, c'est juste à titre d'illustration, mais cela revient essentiellement à grouper par géométrie et à sélectionner simplement la première instance de chacune. Remarque, vous devez utiliser ST_AsBinary dans la partition par sinon la comparaison est effectuée sur le cadre de sélection, pas sur la géométrie réelle.

Comme tous les autres attributs sont probablement les mêmes pour chaque paire de géométries, vous devriez donc quelque chose comme ça pour tous les autres champs, y compris osm_id, et pour réellement créer une nouvelle table unique:

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;

Cela peut être plus rapide que la suppression d'une table existante, surtout s'il y a beaucoup d'index en place.

MODIFIER . Réécrit pour la lisibilité, mais, laissant le crédit à dbaston pour avoir attiré mon attention sur ST_AsBinary (geom)

John Powell
la source
Merci. J'ai pris une note. Mais par exemple, considérons ce scénario où il y a un point géom qui est à la fois un arrêt de bus et un croisement (ne prenez pas en compte les données OSM). Ensuite, nous aurons deux geom identiques représentant ces deux entités. L'utilisation de votre approche supprimera l'une des fonctionnalités. Ce que je dis, c'est que comment résoudre ce problème lorsqu'il n'y a pas de colonne spécifique Partition By?
Zia
1
Salut Zia, alors vous partitionnez par (geom, attribut), de sorte qu'ils doivent tous deux être les mêmes pour obtenir le même identifiant. Dans votre exemple, le geom serait le même, l'attribut non, donc row_number () retournerait 1 pour les deux.
John Powell
1
Cela identifie actuellement des géométries distinctes avec une boîte englobante partagée comme doublons (car PARTITION BYutilise l' =opérateur, qui fonctionne sur l'égalité de la boîte englobante). Je suggère de remplacer ce qui précède PARTITION BY ST_AsBinary(geom)par un correctif.
dbaston
Je pense que vous devriez accepter cette réponse, ou indiquer comment elle ne répond pas à la question.
John Powell
1
@AndreSilva. Terminé. Je suis toujours nerveux à l'idée de changer les réponses sans préciser qu'il y a eu une modification. Mais, vous avez raison, c'est beaucoup plus lisible.
John Powell
2

Voici une autre méthode que j'ai utilisée pour supprimer les doublons d'un téléchargement de données de sol SSURGO. Les fichiers de formes téléchargés n'avaient pas de clé unique, donc une colonne série pk a été générée lorsque j'ai importé dans PostGIS. Il y avait quelques chevauchements dans les ensembles de données et j'ai importé par inadvertance plusieurs enregistrements plusieurs fois lors du développement du script d'importation.

L'instruction group by inclut toutes les colonnes du tableau, à l'exception de la clé primaire.

Il ne supprimera qu'un seul ensemble de lignes en double à chaque exécution, donc si une ligne est répétée 4 fois, vous devrez l'exécuter au moins 3 fois. Ce n'est probablement pas aussi rapide que la solution de John, mais fonctionne dans une table existante. Cela fonctionne également lorsque vous n'avez pas d'ID unique pour chaque géométrie unique (comme l'osm_id dans la question d'origine).

J'ai utilisé un script python pour répéter jusqu'à ce qu'il n'y ait plus de doublons, puis j'ai exécuté un vide complet. Je pense que le script et le vide ont pris chacun environ 30 minutes pour quelques centaines de milliers de doublons provenant d'environ 1,5 million d'enregistrements dans 6 tableaux. Beaucoup de bien pour une seule fois. Il a parcouru les petites tables très rapidement.

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 modifié pour éviter de s'exécuter plusieurs fois sur la base de la suggestion de @dbaston (ci-dessous). J'ai essayé cette méthode de requête sur une grande table (~ 1,5 million d'enregistrements, ~ 25 000 lignes de points en double), et après qu'elle ait fonctionné pendant 45 minutes, j'ai annulé son exécution. L'exécution avec le SQL ci-dessus (en utilisant une sous-requête plus petite de HAVING COUNT) a réduit chaque exécution à moins de 30 secondes. Après avoir exécuté 3 fois, cela a été fait avec tous les doublons. Le SQL ci-dessous devrait être OK pour les petites tables.

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
la source
1
Si vous n'avez pas de clé primaire, vous pouvez utiliser la ctidcolonne toujours disponible (voir les documents ).
dbaston
1
Vous pouvez éviter de l'exécuter plusieurs fois en vérifiantprimary_key NOT IN (SELECT max(primary_key) ....
dbaston
@dbaston J'ai pris des notes dans la réponse ci-dessus. La suppression du HAVING COUNT augmente considérablement la taille des résultats de la sous-requête et, par conséquent, le nombre de comparaisons que l'instruction delete doit effectuer. J'ai été surpris du temps d'exécution sur une grande table.
Nate Wanner
@NateWanner NOT EXISTS peut vous donner une vitesse supplémentaire dans ce cas.
Michal Zimmermann
@MichalZimmermann Je ne suis pas sûr de vous suivre - les deux versions s'attendent à ce que la sous-requête renvoie un résultat.
Nate Wanner
1

Une réponse plus générale pour supprimer facilement les doublons de géométrie dans la table PostGIS. La commande suivante supprime toutes les entités avec une géométrie en double dans "nom_table" en fonction de la clé primaire (colonne "gid") et de l'égalité de la géométrie (colonne "geom"). Sachez que cela supprime vraiment tous les doublons de géométrie, ils seront partis pour toujours! Peut-être sauvegarder en premier?

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
la source