Apa ketepatan SELECT DISTINCT pada kolom geometri PostGIS?

19

Saya ingin tahu apa ketepatan SELECT DISTINCToperator pada geometri PostGIS. Pada sistem saya, permintaan berikut memberi saya hitungan 5, yang berarti bahwa poin yang dimasukkan dianggap sama jika mereka berbeda kurang dari 1e-5 dan saya tidak yakin apakah itu fitur PostGIS, masalah instalasi saya atau bug.

Adakah yang tahu kalau itu perilaku yang diharapkan?

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)

Saya menggunakan:

$ psql --version
psql (PostgreSQL) 9.3.1

dan

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

pada OSX 10.9

yellowcap
sumber

Jawaban:

18

Saya terkejut itu sangat kasar, tapi itu dia. Ini bukan DISTINCT, per se, itu adalah operator '=', yang didefinisikan untuk geometri sebagai 'kesetaraan kunci indeks' yang berarti 'kesetaraan dari kotak pembatas 32-bit'.

Anda dapat melihat efek yang sama hanya menggunakan '=' secara langsung,

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;

Membuat '=' berperilaku "secara intuitif" sayangnya akan melibatkan kerugian komputasi yang sangat besar (melakukan ST_Equals eksplisit () evaluasi untuk panggilan operator) atau beberapa kode rumit baru yang substansial (menyimpan nilai hash untuk geometri yang lebih besar, melakukan tes tepat dengan cepat untuk yang lebih kecil) yang, memilih jalur kode yang tepat dengan cepat, dll)

Dan tentu saja sekarang banyak aplikasi / pengguna telah menginternalisasi perilaku yang ada, seperti itu, jadi "meningkatkan" itu akan menjadi downgrade bagi banyak orang. Anda dapat melakukan perbedaan "tepat" dengan menghitung set Anda pada ST_AsBinary (geom) sebagai gantinya, yang akan melakukan pengujian kesetaraan yang tepat pada output bytea.

Paul Ramsey
sumber
Dan dapatkah kita menganggap ST_AsBinary (geom) adalah operasi yang relatif sangat cepat?
Martin F
Terima kasih atas jawaban Anda, ini menjelaskan perilakunya dengan baik. Saya sebenarnya sedang mengerjakan proyek geodjango, jadi saya akan menggunakan __equalsfilter di sana, yang diterjemahkan ke dalam fungsi ST_Equals yang saya pikir.
yellowcap
1
Ya ST_AsBinary cepat. Tes kesetaraan pada bytea mungkin melibatkan memcmp, yang merupakan operasi yang sangat cepat, jadi seharusnya tidak terlalu mengerikan.
Paul Ramsey
Apa yang kamu usulkan di sini, @PaulRamsey? SELECT DISTINCT ST_AsBinary(geom)? Itu memberikan representasi biner geomsebagai hasilnya. Anda bisa melakukannya SELECT MAX(geom) FROM the_table GROUP BY ST_AsBinary(geom);(saya pikir fungsi agregat seperti MAX()diperlukan di dalam SELECTkarena GROUP BYklausa menggunakan ST_AsBinary()fungsi return, bukan bidang itu sendiri.) Apakah itu terlihat bagus?
Martin Burch
7

Mengingat penjelasan Paul Ramsey yang luar biasa tentang mengapa pertanyaan selanjutnya adalah apa yang dapat dilakukan tentang itu. Bagaimana Anda SELECT DISTINCTdi bidang geometri dan menjalankannya seperti yang diharapkan?

Dalam jawaban Paul, saya mengusulkan menggunakan SELECT MAX(geom) FROM the_table GROUP BY ST_AsBinary(geom);tetapi MAX()lambat, tampaknya membutuhkan pemindaian tabel.

Sebaliknya, saya menemukan ini lebih cepat:

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

Hanya pembaruan, untuk PostGIS 2.4, SELECT DISTINCTbekerja dengan benar untuk data poin di 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)

Dan

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
sumber