29. Пошук найближчого сусіда

29.2. З’єднання найближчих сусідів

Оператор сортування з підтримкою індексу має один суттєвий недолік: він працює лише з одним геометричним літералом з одного боку оператора. Це підходить для пошуку об’єктів, найближчих до одного запитного об’єкта, але не допомагає для просторового з’єднання, де мета — знайти найближчого сусіда для кожного об’єкта з повного набору кандидатів.

На щастя, у мові SQL є можливість виконувати запит багаторазово в циклі — це LATERAL join <https://medium.com/kkempin/postgresqls-lateral-join-bfd6bd0199df>_.

Тут ми знайдемо найближчу вулицю до кожної станції метро:

SELECT subways.gid AS subway_gid,
       subways.name AS subway,
       streets.name AS street,
       streets.gid AS street_gid,
       streets.geom::geometry(MultiLinestring, 26918) AS street_geom,
       streets.dist
FROM nyc_subway_stations subways
CROSS JOIN LATERAL (
  SELECT streets.name, streets.geom, streets.gid, streets.geom <-> subways.geom AS dist
  FROM nyc_streets AS streets
  ORDER BY dist
  LIMIT 1
) streets;

Зверніть увагу, що CROSS JOIN LATERAL працює як внутрішня частина циклу, керованого таблицею метро. Кожен запис із таблиці метро по черзі передається в латеральний підзапит, тож ви отримуєте найближчий результат для кожного запису метро.

_images/knn4.png

План виконання показує цикл по станціях метро та сортування з підтримкою індексу всередині циклу, саме там, де це потрібно:

                           QUERY PLAN
-------------------------------------------------------------------------
 Nested Loop  (cost=0.28..13140.71 rows=491 width=37)
   ->  Seq Scan on nyc_subway_stations subways
       (cost=0.00..15.91 rows=491 width=46)
   ->  Limit
       (cost=0.28..1.71 rows=1 width=170)
         ->  Index Scan using nyc_streets_geom_idx on nyc_streets streets
             (cost=0.28..27410.12 rows=19091 width=170)
                Order By: (geom <-> subways.geom)