2016-01-14 21 views
8

Vorrei recuperare tutti i punti all'interno di un determinato intervallo di un altro insieme di punti. Supponiamo di trovare tutti i negozi entro 500 metri da qualsiasi stazione della metropolitana.PostGis vicini più vicini query

Ho scritto questa domanda, che è abbastanza lento, e vorrebbe ottimizzarlo:

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

sto correndo sulle più recenti versioni di Postgres e PostGIS (Postgres 9.5, PostGIS 2.2.1)

Ecco i metadati della tabella:

          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) 

Ecco il risultato di spiegare (ANALYZE, i buffer):

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

Sono geometria o geografia? – fradal83

+0

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

+0

@ FrancescoD'Alesio geometry – Chris

risposta

0

Alla fine sono arrivato alla conclusione che non potevo calcolare la distanza tra migliaia di punti di interesse e migliaia di posizioni in un lasso di tempo realistico (< 1sec).

Quindi, invece, ho precomputo tutto: ogni volta che viene creata/aggiornata una posizione o un POI, memorizzo la distanza minima tra ogni posizione e ogni tipo di POI per poter rispondere alla domanda "quali posizioni sono più vicine di X metri da questo tipo di POI ".

Ecco il modulo ho codificato per questo scopo (è in Elixir, ma la parte principale è SQL prime)

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

Penso che dovresti cambiare una soluzione, postgis è ancora in esecuzione una query in Database strutturato, è potente, ma non veloce nel requisito speciale, potrebbe essere necessario elasticsearch.

elasticsearch è valido per la ricerca di località geografiche, ma non è valido per il processo dei dati geografici, penso che siano necessari entrambi.

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

+0

Grazie per la tua risposta, ma mi piacerebbe davvero attenermi ai postgres. Se non c'è modo di coprire la mia necessità, prenderò in considerazione l'utilizzo di un ulteriore motore di archiviazione – Chris

0

Penso che si sta utilizzando la versione geografia di st_dwithin, a causa del quarto parametro.

provare a cambiare i vostri dati di questo:

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

Se non si risolve, si prega di inviare l'Spiegare analizzare di nuovo.

+0

La rimozione del parametro FALSE ha reso la query eseguita 8.6sec (anziché 3sec). Ecco la nuova spiegazione analizzare: https://gist.github.com/cblavier/726139eda4cd574340bd – Chris