Quelle est la précision de SELECT DISTINCT sur la colonne de géométrie PostGIS?

19

Je me demande quelle est la précision de l' SELECT DISTINCTopérateur sur une géométrie PostGIS. Sur mon système, la requête suivante me donne un nombre de 5, ce qui signifie que les points insérés sont considérés comme égaux s'ils diffèrent de moins de 1e-5 et je ne suis pas sûr que ce soit une fonctionnalité de PostGIS, un problème de mon installation ou un bug.

Est-ce que quelqu'un sait si c'est le comportement attendu?

CREATE TEMP TABLE test (geom geometry);
INSERT INTO test
    VALUES 
        (St_GeomFromText('POINT (0.1 0.1)')),
        (St_GeomFromText('POINT (0.001 0.001)')),
        (St_GeomFromText('POINT (0.0001 0.0001)')),
        (St_GeomFromText('POINT (0.00001 0.00001)')),
        (St_GeomFromText('POINT (0.000001 0.000001)')),
        (St_GeomFromText('POINT (0.0000001 0.0000001)')),
        (St_GeomFromText('POINT (0.00000001 0.00000001)')),
        (St_GeomFromText('POINT (0.000000001 0.000000001)'));

SELECT COUNT(*) FROM (SELECT DISTINCT geom FROM test) AS test;

 count 
-------
     5
(1 row)

J'utilise:

$ psql --version
psql (PostgreSQL) 9.3.1

et

SELECT PostGIS_full_version();
----------------------------------------------------------------------------------------------------------------------------------------------------------------------
POSTGIS="2.1.1 r12113" GEOS="3.4.2-CAPI-1.8.2 r3921" PROJ="Rel. 4.8.0, 6 March 2012" GDAL="GDAL 1.10.1, released 2013/08/26" LIBXML="2.7.3" LIBJSON="UNKNOWN" RASTER

sur OSX 10.9

yellowcap
la source

Réponses:

18

Je suis surpris que ce soit assez grossier, mais ça y est. Ce n'est pas DISTINCT, en soi, c'est l'opérateur '=', qui est défini pour la géométrie comme 'l'égalité des clés d'index' qui signifie pratiquement 'l'égalité des boîtes englobantes de 32 bits'.

Vous pouvez voir le même effet en utilisant directement "=",

select 'POINT (0.000000001 0.000000001)'::geometry = 'POINT (0.000000001 0.000001)'::geometry;

select 'POINT (0.000000001 0.000000001)'::geometry = 'POINT (0.000000001 0.00001)'::geometry;

Faire "=" un comportement "intuitif" impliquerait malheureusement soit une énorme perte de calcul (faire une évaluation ST_Equals () explicite pour l'appel de l'opérateur) soit un nouveau code compliqué substantiel (stocker des valeurs de hachage pour des géométries plus grandes, faire des tests exacts à la volée pour des petits ceux, choisir le bon chemin de code à la volée, etc.)

Et bien sûr, maintenant de nombreuses applications / utilisateurs ont internalisé le comportement existant, tel qu'il est, donc "améliorer" ce serait une rétrogradation pour beaucoup de gens. Vous pouvez faire une distinction "exacte" en calculant votre ensemble sur ST_AsBinary (geom) à la place, ce qui fera des tests d'égalité exacts sur les sorties de bytea.

Paul Ramsey
la source
Et peut-on supposer que ST_AsBinary (geom) est une opération relativement très rapide?
Martin F
Merci pour votre réponse, cela explique bien le comportement. Je travaille actuellement sur un projet geodjango, donc je vais utiliser le __equalsfiltre là-bas, ce qui se traduit par la fonction ST_Equals je pense.
yellowcap
1
Oui ST_AsBinary est rapide. Les tests d'égalité sur bytea impliquent probablement memcmp, qui est une opération très rapide, donc ne devrait pas être trop terrible.
Paul Ramsey
Que proposez-vous ici, @PaulRamsey? SELECT DISTINCT ST_AsBinary(geom)? Cela donne une représentation binaire de geomcomme résultat. Vous pouvez le faire SELECT MAX(geom) FROM the_table GROUP BY ST_AsBinary(geom);(je pense qu'une fonction d'agrégation comme celle-ci MAX()est requise dans le SELECTcar la GROUP BYclause utilise la ST_AsBinary()fonction return, pas le champ lui-même.)
Martin Burch,
7

Étant donné l'excellente explication de Paul Ramsey sur la raison pour laquelle la question suivante est de savoir ce qui peut être fait à ce sujet. Comment faites-vous SELECT DISTINCTsur les champs de géométrie et faites-vous fonctionner comme prévu?

Dans la réponse de Paul, j'ai proposé d'utiliser SELECT MAX(geom) FROM the_table GROUP BY ST_AsBinary(geom);mais MAX()est lent, nécessitant apparemment un scan de table.

Au lieu de cela, j'ai trouvé que c'était plus rapide:

SELECT DISTINCT ON (ST_AsBinary(geom)) geom FROM the_table;
Martin Burch
la source
4

Juste une mise à jour, pour PostGIS 2.4, SELECT DISTINCTfonctionne correctement pour les données de points dans l'OP:

CREATE TEMP TABLE test (geom geometry);
CREATE TABLE
user=> INSERT INTO test
user->     VALUES 
user->         (St_GeomFromText('POINT (0.1 0.1)')),
user->         (St_GeomFromText('POINT (0.001 0.001)')),
user->         (St_GeomFromText('POINT (0.0001 0.0001)')),
user->         (St_GeomFromText('POINT (0.00001 0.00001)')),
user->         (St_GeomFromText('POINT (0.000001 0.000001)')),
user->         (St_GeomFromText('POINT (0.0000001 0.0000001)')),
user->         (St_GeomFromText('POINT (0.00000001 0.00000001)')),
user->         (St_GeomFromText('POINT (0.000000001 0.000000001)'));
INSERT 0 8
user=> 
user=> SELECT COUNT(*) FROM (SELECT DISTINCT geom FROM test) AS test;
 count 
-------
     8
(1 row)

Et

user=> select 'POINT (0.000000001 0.000000001)'::geometry = 'POINT (0.000000001 0.000001)'::geometry;
 ?column? 
----------
 f
(1 row)

user=> 
user=> select 'POINT (0.000000001 0.000000001)'::geometry = 'POINT (0.000000001 0.00001)'::geometry;
 ?column? 
----------
 f
(1 row)
tinlyx
la source