Пожалуйста, прости неловкий заголовок.Мне было трудно вынести мой вопрос в одну фразу.Если кто-то может придумать лучший вариант, не стесняйтесь.
У меня есть следующая упрощенная схема:
vendors
INT id
locations
INT id
INT vendor_id
FLOAT latitude
FLOAT longitude
Я вполне способен вернуть список ближайших поставщиков, отсортированный поблизость, ограниченная приближением радиуса:
SELECT * FROM locations
WHERE latitude IS NOT NULL AND longitude IS NOT NULL
AND ABS(latitude - 30) + ABS(longitude - 30) < 50
ORDER BY ABS(latitude - 30) + ABS(longitude - 30) ASC
В данный момент я не могу найти способ повторения порядка / предельного члена.Первоначально я пытался присвоить ему псевдоним как «расстояние» среди полей SELECT
, но psql сказал мне, что этот псевдоним не был доступен в предложении WHERE
.Хорошо.Если есть какие-то причудливые штаны, то у меня все уши, но на мой главный вопрос:
Что я хотел бы сделать, так это вернуть список поставщиков, каждый из которых соединен сближайший из его местоположений, и этот список упорядочен по близости и ограничен радиусом.
Итак, предположим, у меня есть 2 продавца, у каждого из которых есть два местоположения.Я хочу запрос, который ограничивает радиус так, чтобы в нем находилось только одно из четырех местоположений, чтобы вернуть связанного поставщика этого местоположения вместе с самим поставщиком.Если бы радиус охватывал все местоположения, я бы хотел, чтобы продавец 1 был представлен ближе всех к своим местоположениям, а продавец 2 - ближе всех к своим местоположениям, в конечном итоге заказывая продавцов 1 и 2 на основе близости их ближайшего местоположения.
В MySQL мне удалось получить ближайшее местоположение в строке каждого поставщика, используя GROUP BY
, а затем MIN(distance)
.Но PostgreSQL кажется более строгим в использовании GROUP BY
.
. Я бы хотел, если возможно, избегать вмешательства в предложение SELECT
.Я также хотел бы, если возможно, повторно использовать части WHERE
и ORDER
вышеуказанного запроса.Но это ни в коем случае не абсолютные требования.
Я предпринял избитые попытки DISTINCT ON
и GROUP BY
, но это доставило мне немало хлопот, в основном из-за того, что я пропустил зеркальные заявления в других местах, чтоЯ не буду подробно останавливаться на этом подробнее.
Решение
В итоге я принял решение, основанное на превосходном ответе OMG Ponies .
SELECT vendors.* FROM (
SELECT locations.*,
ABS(locations.latitude - 2.1) + ABS(locations.longitude - 2.1) AS distance,
ROW_NUMBER() OVER(PARTITION BY locations.locatable_id, locations.locatable_type
ORDER BY ABS(locations.latitude - 2.1) + ABS(locations.longitude - 2.1) ASC) AS rank
FROM locations
WHERE locations.latitude IS NOT NULL
AND locations.longitude IS NOT NULL
AND locations.locatable_type = 'Vendor'
) ranked_locations
INNER JOIN vendors ON vendors.id = ranked_locations.locatable_id
WHERE (ranked_locations.rank = 1)
AND (ranked_locations.distance <= 0.5)
ORDER BY ranked_locations.distance;
Некоторые отклонения от решения OMG Ponies:
- Места теперь полиморфно связаны через
_type
.Небольшое изменение предпосылки. - Я переместил объединение за пределы подзапроса.Я не знаю, влияют ли это на производительность, но в моем сознании имел смысл рассматривать подзапрос как получение местоположений и разделенных рейтингов, а затем более крупный запрос как акт объединения всего этого.
- несовершеннолетний Убрал псевдоним имени таблицы.Хотя я достаточно привык к псевдонимам, мне стало труднее следовать за ними.Я подожду, пока у меня не появится больше опыта работы с PostgreSQL, прежде чем работать в этом духе.