2016-01-14 12 views
8

Ich möchte alle Punkte innerhalb einer bestimmten Bereich einer anderen Reihe von Punkten abrufen. Angenommen, Sie finden alle Geschäfte im Umkreis von 500m von jeder U-Bahnstation.PostGis nächste Nachbarn Abfrage

Ich schrieb diese Abfrage, die ziemlich langsam ist, und möchte es optimieren:

SELECT DISCTINCT ON(locations.id) locations.id FROM locations, pois 
WHERE pois.poi_kind = 'subway' 
AND ST_DWithin(locations.coordinates, pois.coordinates, 500, false); 

Ich bin auf neuesten Versionen von Postgres und PostGIS (Postgres 9.5, PostGIS 2.2.1) läuft

Hier ist die Tabellenmetadaten:

          Table "public.locations" 
     Column  |   Type    |      Modifiers 
--------------------+-----------------------------+-------------------------------------------------------- 
id     | integer      | not null default nextval('locations_id_seq'::regclass) 
coordinates  | geometry     | 
Indexes: 
    "locations_coordinates_index" gist (coordinates) 


             Table "public.pois" 
    Column |   Type    |      Modifiers 
-------------+-----------------------------+--------------------------------------------------- 
id   | integer      | not null default nextval('pois_id_seq'::regclass) 
coordinates | geometry     | 
poi_kind_id | integer      | 
Indexes: 
    "pois_pkey" PRIMARY KEY, btree (id) 
    "pois_coordinates_index" gist (coordinates) 
    "pois_poi_kind_id_index" btree (poi_kind_id) 
Foreign-key constraints: 
    "pois_poi_kind_id_fkey" FOREIGN KEY (poi_kind_id) REFERENCES poi_kinds(id) 

Hier ist das Ergebnis der EXPLAIN (ANALYZE, BUFFERS):

Unique (cost=2407390.71..2407390.72 rows=2 width=4) (actual time=3338.080..3338.252 rows=918 loops=1) 
Buffers: shared hit=559 
-> Sort (cost=2407390.71..2407390.72 rows=2 width=4) (actual time=3338.079..3338.145 rows=963 loops=1) 
     Sort Key: locations.id 
     Sort Method: quicksort Memory: 70kB 
     Buffers: shared hit=559 
     -> Nested Loop (cost=0.00..2407390.71 rows=2 width=4) (actual time=2.466..3337.835 rows=963 loops=1) 
      Join Filter: (((pois.coordinates)::geography && _st_expand((locations.coordinates)::geography, 500::double precision)) AND ((locations.coordinates)::geography && _st_expand((pois.coordinates)::geography, 500::double precision)) AND _st_dwithin((pois.coordinates)::geography, (locations.coordinates)::geography, 500::double precision, false)) 
      Rows Removed by Join Filter: 4531356 
      Buffers: shared hit=559 
      -> Seq Scan on locations (cost=0.00..791.68 rows=24168 width=36) (actual time=0.005..3.100 rows=24237 loops=1) 
        Buffers: shared hit=550 
      -> Materialize (cost=0.00..10.47 rows=187 width=32) (actual time=0.000..0.009 rows=187 loops=24237) 
        Buffers: shared hit=6 
        -> Seq Scan on pois (cost=0.00..9.54 rows=187 width=32) (actual time=0.015..0.053 rows=187 loops=1) 
         Filter: (poi_kind_id = 3) 
         Rows Removed by Filter: 96 
         Buffers: shared hit=6 
Planning time: 0.184 ms 
Execution time: 3338.304 ms 
(20 rows) 
+0

Sind sie Geometrie oder Geographie? – fradal83

+0

https://wiki.postgresql.org/wiki/Slow_Query_Questions –

+0

@ FrancescoD'Alesio Geometrie – Chris

Antwort

0

Ich kam schließlich zu dem Ergebnis, dass ich nicht leben konnte - berechnen Sie die Entfernung zwischen Tausenden von interessanten Orten und Tausenden von Orten innerhalb einer realistischen Zeit (< 1sec).

Also vorberechne ich alles: Jedes Mal, wenn ein Ort oder ein POI erstellt/aktualisiert wird, speichere ich den Mindestabstand zwischen jedem Ort und jeder Art von POI, um die Frage "Welche Orte sind näher als X Meter von dieser Art von POI ". Hier

ist das Modul I zu diesem Zweck codiert (es ist in Elixir, aber der Hauptteil ist roh SQL)

defmodule My.POILocationDistanceService do 

    alias Ecto.Adapters.SQL 
    alias My.Repo 

    def delete_distance_for_location(location_id) do 
    run_query!("DELETE FROM poi_location_distance WHERE location_id = $1::integer", [location_id]) 
    end 

    def delete_distance_for_poi_kind(poi_kind_id) do 
    run_query!("DELETE FROM poi_location_distance WHERE poi_kind_id = $1::integer", [poi_kind_id]) 
    end 

    def insert_distance_for_location(location_id) do 
    sql = """ 
    INSERT INTO poi_location_distance(poi_kind_id, location_id, poi_id, distance) 
    SELECT 
     DISTINCT ON (p.poi_kind_id) 
     p.poi_kind_id as poi_kind_id, 
     l.id as location_id, 
     p.id as poi_id, 
     MIN(ST_Distance_Sphere(l.coordinates, p.coordinates)) as distance 
    FROM locations l, pois p 
    WHERE 
     l.id = $1 
     AND ST_DWithin(l.coordinates, p.coordinates, $2, FALSE) 
    GROUP BY p.poi_kind_id, p.id, l.id 
    ORDER BY p.poi_kind_id, distance; 
    """ 

    run_query!(sql, [location_id, max_distance]) 
    end 

    def insert_distance_for_poi_kind(poi_kind_id, offset \\ 0, limit \\ 10_000_000) do 
    sql = """ 
    INSERT INTO poi_location_distance(poi_kind_id, location_id, poi_id, distance) 
    SELECT 
     DISTINCT ON(l.id, p.poi_kind_id) 
     p.poi_kind_id as poi_kind_id, 
     l.id as location_id, 
     p.id as poi_id, 
     MIN(ST_Distance_Sphere(l.coordinates, p.coordinates)) as distance 
    FROM pois p, (SELECT * FROM locations OFFSET $1 LIMIT $2) as l 
    WHERE 
     p.poi_kind_id = $3 
     AND ST_DWithin(l.coordinates, p.coordinates, $4, FALSE) 
    GROUP BY l.id, p.poi_kind_id, p.id; 
    """ 

    run_query!(sql, [offset, limit, poi_kind_id, max_distance]) 
    end 

    defp run_query!(query, params) do 
    SQL.query!(Repo, query, params) 
    end 

    def max_distance, do: 5000 

end 
0

Ich denke, Sie sollten eine Lösung ändern, Postgis führt immer noch eine Abfrage in der strukturierten Datenbank, es ist mächtig, aber nicht schnell in speziellen Anforderungen, möglicherweise brauchen Sie elasticsearch.

elasticsearch ist gut bei Geo-Standort-Suche, aber nicht gut bei Geodaten-Prozess, ich denke, Sie brauchen sie beide.

https://www.elastic.co/blog/geo-location-and-search

+0

Danke für deine Antwort, aber ich würde gerne bei Postgres bleiben. Wenn es keine Möglichkeit gibt, meine Bedürfnisse zu decken, werde ich die Verwendung einer zusätzlichen Speicher-Engine erwägen. – Chris

0

Ich denke, dass Sie die Geographie-Version von st_dwithin verwenden, weil der vierte Parameter.

Versuchen Sie Ihre Suche zu diesem Wechsel:

SELECT DISCTINCT ON(locations.id) locations.id FROM locations, pois 
WHERE pois.poi_kind = 'subway' 
AND ST_DWithin(locations.coordinates, pois.coordinates, 500); 

Wenn es nicht lösen hat, bitte posten die erneut analysieren zu erklären.

+0

Durch das Entfernen des FALSE-Parameters wurde die Abfrage 8,6 Sekunden (statt 3 Sekunden) ausgeführt. Hier ist die neue EXPLAIN-Analyse: https://gist.github.com/cblavier/726139eda4cd574340bd – Chris

Verwandte Themen