0Pricing
SQL Academy · Урок

Расстояние и ближайшие соседи

Находите объекты поблизости

«Расстояние и ближайшие соседи» — бесплатный урок SQL Academy на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения SQL Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс SQL Academy содержит 4 уроков всего.

Что такое пространственное расстояние

В географических базах данных расстояние — это измеряемый промежуток между двумя географическими местоположениями. PostGIS предоставляет мощные функции для вычисления расстояний между точками, линиями, полигонами и другими типами геометрии.

Понимание запросов расстояния позволяет отвечать на такие вопросы, как: Какой ресторан находится ко мне ближе всего? или Какие клиенты находятся в пределах 5 км от нашего склада?

Функция ST_Distance

ST_Distance(geom_a, geom_b) возвращает минимальное расстояние между двумя объектами геометрии. По умолчанию при использовании обычных типов геометрии результат выражается в единицах системы координат, обычно в градусах для EPSG:4326.

Чтобы получить осмысленные результаты в метрах, следует использовать тип географии или перепроецировать данные.

SELECT ST_Distance(
  ST_MakePoint(28.9784, 41.0082)::geography,
  ST_MakePoint(29.0100, 41.0200)::geography
) AS distance_meters;

Создание учебной таблицы

Создадим простую таблицу мест с географическими координатами. Мы будем использовать тип географии, чтобы все вычисления расстояний автоматически возвращали результат в метрах — без необходимости выполнять проецирование вручную.

В этой таблице будут храниться кафе города: для каждого кафе указаны название и местоположение, сохранённое как точка.

CREATE TABLE cafes (
  id SERIAL PRIMARY KEY,
  name TEXT NOT NULL,
  location GEOGRAPHY(Point, 4326)
);

INSERT INTO cafes (name, location) VALUES
  ('Cafe Alpha',   ST_MakePoint(28.9784, 41.0082)::geography),
  ('Cafe Beta',    ST_MakePoint(28.9900, 41.0150)::geography),
  ('Cafe Gamma',   ST_MakePoint(29.0100, 41.0200)::geography),
  ('Cafe Delta',   ST_MakePoint(28.9650, 40.9980)::geography),
  ('Cafe Epsilon', ST_MakePoint(29.0250, 41.0350)::geography);

Поиск расстояния от фиксированной точки

Имея данные о местоположении, можно вычислить расстояние от каждой строки до опорной точки. Здесь мы рассчитываем расстояние от каждого кафе до пользователя, находящегося в координатах (28.9800, 41.0100).

Результат выражается в метрах, поскольку мы использовали тип географии.

SELECT
  name,
  ROUND(
    ST_Distance(
      location,
      ST_MakePoint(28.9800, 41.0100)::geography
    )::NUMERIC
  ) AS distance_m
FROM cafes
ORDER BY distance_m;

Поиск ближайшего соседа

Чтобы найти единственное ближайшее к заданной точке местоположение, отсортируйте результаты по расстоянию и возьмите только одну строку с помощью LIMIT 1. Это классический запрос поиска ближайшего соседа.

Такой шаблон очень часто используется в приложениях, учитывающих местоположение: для поиска ближайшего магазина, врача или остановки общественного транспорта.

SELECT
  name,
  ROUND(
    ST_Distance(
      location,
      ST_MakePoint(28.9800, 41.0100)::geography
    )::NUMERIC
  ) AS distance_m
FROM cafes
ORDER BY location <-> ST_MakePoint(28.9800, 41.0100)::geography
LIMIT 1;

Оператор &lt;-&gt; для поиска ближайшего соседа

PostGIS предоставляет оператор <-> (KNN — K ближайших соседей), который учитывает индексы. В отличие от ST_Distance в предложении ORDER BY, оператор <-> может использовать пространственный индекс GiST, чтобы не просматривать каждую строку.

Всегда отдавайте предпочтение <-> в ORDER BY, когда необходимо эффективно найти ближайших соседей. Для его поддержки создайте индекс следующим образом:

CREATE INDEX ON cafes USING GIST (location);

SELECT
  name,
  ROUND(
    ST_Distance(
      location,
      ST_MakePoint(28.9800, 41.0100)::geography
    )::NUMERIC
  ) AS distance_m
FROM cafes
ORDER BY location <-> ST_MakePoint(28.9800, 41.0100)::geography
LIMIT 5;

Фильтрация по расстоянию (ST_DWithin)

ST_DWithin(geom_a, geom_b, distance) возвращает TRUE, если две геометрии находятся друг от друга в пределах заданного расстояния. Для типов географии расстояние выражается в метрах.

Это эффективнее, чем ST_Distance(...) < radius в предложении WHERE, поскольку ST_DWithin учитывает индекс и прекращает поиск сразу после выполнения условия.

SELECT
  name,
  ROUND(
    ST_Distance(
      location,
      ST_MakePoint(28.9800, 41.0100)::geography
    )::NUMERIC
  ) AS distance_m
FROM cafes
WHERE ST_DWithin(
  location,
  ST_MakePoint(28.9800, 41.0100)::geography,
  2000
)
ORDER BY distance_m;

Поиск ближайших N объектов с ограничением по радиусу

Распространённый рабочий шаблон сочетает ST_DWithin в качестве предварительного фильтра с сортировкой с помощью <->. Проверка ST_DWithin использует пространственный индекс, чтобы быстро отбрасывать удалённые строки, после чего оставшиеся кандидаты сортируются по точному расстоянию.

Так можно очень эффективно получить N ближайших местоположений в пределах максимального радиуса.

SELECT
  name,
  ROUND(
    ST_Distance(
      location,
      ST_MakePoint(28.9800, 41.0100)::geography
    )::NUMERIC
  ) AS distance_m
FROM cafes
WHERE ST_DWithin(
  location,
  ST_MakePoint(28.9800, 41.0100)::geography,
  5000
)
ORDER BY location <-> ST_MakePoint(28.9800, 41.0100)::geography
LIMIT 3;

Расстояние между двумя таблицами (перекрёстное расстояние)

Можно вычислять расстояния между строками двух разных таблиц, используя JOIN вместе с ST_Distance. Это полезно, например, для сопоставления каждого клиента с ближайшим складом или каждого происшествия с ближайшей больницей.

Ниже мы находим ближайшее кафе для каждого пользователя из таблицы users с помощью латерального соединения — мощного шаблона PostgreSQL для подзапросов, выполняемых отдельно для каждой строки.

SELECT
  u.username,
  c.name AS nearest_cafe,
  ROUND(ST_Distance(u.location, c.location)::NUMERIC) AS distance_m
FROM (
  VALUES
    ('alice', ST_MakePoint(28.9810, 41.0095)::geography),
    ('bob',   ST_MakePoint(29.0200, 41.0300)::geography)
) AS u(username, location)
CROSS JOIN LATERAL (
  SELECT name, location
  FROM cafes
  ORDER BY location <-> u.location
  LIMIT 1
) c;

Форматирование результата расстояния

Необработанные расстояния в метрах бывает трудно читать. Их можно форматировать в километрах или добавлять понятные человеку обозначения с помощью выражений CASE и форматирования строк. Ниже приведён запрос, который выводит расстояние в метрах для небольших расстояний и в километрах для больших.

SELECT
  name,
  CASE
    WHEN ST_Distance(location, ST_MakePoint(28.9800, 41.0100)::geography) < 1000
    THEN ROUND(ST_Distance(location, ST_MakePoint(28.9800, 41.0100)::geography)::NUMERIC)
         || ' m'
    ELSE ROUND((ST_Distance(location, ST_MakePoint(28.9800, 41.0100)::geography) / 1000.0)::NUMERIC, 2)
         || ' km'
  END AS formatted_distance
FROM cafes
ORDER BY location <-> ST_MakePoint(28.9800, 41.0100)::geography;

Присвоение ранга по расстоянию

Оконные функции, такие как RANK() и ROW_NUMBER(), идеально подходят для запросов расстояния. Можно присвоить каждому местоположению ранг близости относительно пользователя, что полезно при создании упорядоченных по близости списков рекомендаций или результатов поиска.

SELECT
  name,
  ROUND(
    ST_Distance(
      location,
      ST_MakePoint(28.9800, 41.0100)::geography
    )::NUMERIC
  ) AS distance_m,
  ROW_NUMBER() OVER (
    ORDER BY location <-> ST_MakePoint(28.9800, 41.0100)::geography
  ) AS proximity_rank
FROM cafes;

Проверка знаний

Проверьте, насколько хорошо Вы понимаете запросы расстояния и поиск ближайших соседей в PostGIS.

Итоги урока

В этом уроке Вы узнали, как работать с пространственными расстояниями и запросами поиска ближайших соседей в PostGIS:

  • ST_Distance вычисляет точное расстояние между двумя геометриями; используйте тип географии, чтобы автоматически получать результат в метрах.
  • ST_DWithin эффективно фильтрует строки в пределах радиуса с помощью пространственного индекса — предпочитайте его конструкции ST_Distance < radius в предложениях WHERE.
  • Оператор <-> (KNN) в ORDER BY учитывает индекс и является самым быстрым способом получить N ближайших соседей.
  • Сочетайте ST_DWithin в качестве предварительного фильтра с сортировкой по <->, чтобы наиболее эффективно выполнять запросы на получение первых N объектов в пределах радиуса.
  • CROSS JOIN LATERAL позволяет находить ближайшее соответствие из другой таблицы отдельно для каждой строки.
  • Оконечные функции, такие как ROW_NUMBER(), позволяют присваивать ранги близости результатам, упорядоченным по расстоянию.

Эти шаблоны лежат в основе функций, учитывающих местоположение, в реальных приложениях.

Часто задаваемые вопросы

Урок «Расстояние и ближайшие соседи» бесплатный?

Да — полный текст урока «Расстояние и ближайшие соседи» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс SQL Academy, подпишись на CoddyKit PRO. Курс SQL Academy содержит 4 уроков всего.

Чему я научусь в уроке «Расстояние и ближайшие соседи»?

Находите объекты поблизости Ты практикуешь SQL Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать SQL Academy?

Предыдущий опыт не требуется. SQL Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.

Сколько времени занимает урок «Расстояние и ближайшие соседи»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке SQL Academy?

Да. Каждый урок SQL Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Пространственные типы данных
  2. Расстояние и ближайшие соседи
  3. Пространственные объединения и вхождение
  4. Пространственные индексы (GiST)
← Назад к SQL Academy