Расстояние и ближайшие соседи
Находите объекты поблизости
«Расстояние и ближайшие соседи» — бесплатный урок 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;Оператор <-> для поиска ближайшего соседа
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 — локальная установка не требуется.
Все уроки этого курса
- Пространственные типы данных
- Расстояние и ближайшие соседи
- Пространственные объединения и вхождение
- Пространственные индексы (GiST)