Wie genau ist SELECT DISTINCT in der PostGIS-Geometriespalte?

19

Ich frage mich, wie hoch die Präzision des SELECT DISTINCTOperators in einer PostGIS-Geometrie ist. Auf meinem System gibt die folgende Abfrage eine Anzahl von 5 an, was bedeutet, dass die eingefügten Punkte als gleich angesehen werden, wenn sie sich um weniger als 1e-5 unterscheiden, und ich bin nicht sicher, ob dies eine Funktion von PostGIS ist, ein Problem meiner Installation oder ein Bug.

Weiß jemand, ob das das erwartete Verhalten ist?

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)

Ich benutze:

$ psql --version
psql (PostgreSQL) 9.3.1

und

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

unter OSX 10.9

Yellowcap
quelle

Antworten:

18

Ich bin überrascht, dass es so grob ist, aber da ist es. Es ist nicht DISTINCT per se, es ist der Operator '=', der für Geometrie als 'Gleichheit der Indexschlüssel' definiert wird, was praktisch 'Gleichheit der 32-Bit-Begrenzungsrahmen' bedeutet.

Sie können den gleichen Effekt sehen, indem Sie einfach '=' direkt verwenden.

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;

Ein "intuitives" Verhalten von "=" würde leider entweder einen enormen Rechenverlust (Ausführen einer expliziten ST_Equals () - Auswertung für den Operatoraufruf) oder einen wesentlichen neuen, komplizierten Code (Speichern von Hashwerten für größere Geometrien, Ausführen exakter Tests im laufenden Betrieb für kleinere bedeuten den richtigen Codepfad auswählen, usw.)

Und natürlich haben jetzt viele Anwendungen / Benutzer das vorhandene Verhalten verinnerlicht, so wie es ist. "Verbessern" wäre für viele ein Downgrade. Sie können eine "exakte" Unterscheidung durchführen, indem Sie stattdessen Ihre Menge auf ST_AsBinary (geom) berechnen. Dadurch werden die bytea-Ausgaben genau auf Gleichheit geprüft.

Paul Ramsey
quelle
Und können wir annehmen, dass ST_AsBinary (geom) eine relativ sehr schnelle Operation ist?
Martin F
Vielen Dank für Ihre Antwort, dies erklärt das Verhalten gut. Ich arbeite gerade an einem Geodjango-Projekt, daher verwende ich dort den __equalsFilter, der meiner Meinung nach in die Funktion ST_Equals übersetzt wird.
Yellowcap
1
Ja, ST_AsBinary ist schnell. Gleichheitstests auf bytea beinhalten wahrscheinlich memcmp, was eine sehr schnelle Operation ist, sollte also nicht zu schrecklich sein.
Paul Ramsey
Was schlagen Sie hier vor, @PaulRamsey? SELECT DISTINCT ST_AsBinary(geom)? Das ergibt eine binäre Darstellung von geomals Ergebnis. Das könnten Sie tun SELECT MAX(geom) FROM the_table GROUP BY ST_AsBinary(geom);(ich denke, eine Aggregatfunktion wie MAX()in der ist erforderlich, SELECTweil die GROUP BYKlausel die ST_AsBinary()Funktion return verwendet, nicht das Feld selbst.) Sieht das gut aus?
Martin Burch
7

Angesichts der hervorragenden Erklärung von Paul Ramsey, warum die nächste Frage lautet, was dagegen getan werden kann. Wie gehen Sie SELECT DISTINCTmit Geometriefeldern um und lassen Sie diese wie erwartet ausführen?

In der Antwort von Paul schlug ich vor, zu verwenden SELECT MAX(geom) FROM the_table GROUP BY ST_AsBinary(geom);, MAX()ist aber langsam und erfordert anscheinend einen Tabellenscan.

Stattdessen fand ich das schneller:

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

Nur ein Update für PostGIS 2.4 SELECT DISTINCTfunktioniert korrekt für die Punktedaten im 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)

Und

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
quelle