Как сгруппировать результаты запроса, основанного на связи «один ко многим», по некоторому критерию «многие»? - PullRequest
0 голосов
/ 07 февраля 2011

Пожалуйста, прости неловкий заголовок.Мне было трудно вынести мой вопрос в одну фразу.Если кто-то может придумать лучший вариант, не стесняйтесь.

У меня есть следующая упрощенная схема:

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, прежде чем работать в этом духе.

Ответы [ 2 ]

2 голосов
/ 07 февраля 2011

Для PostgreSQL 8.4+ вы можете использовать аналитику, например, ROW_NUMBER :

SELECT x.*
  FROM (SELECT v.*,
               t.*,
               ABS(t.latitude - 30) + ABS(t.longitude - 30) AS distance,
               ROW_NUMBER() OVER(PARTITION BY v.id
                                     ORDER BY ABS(t.latitude - 30) + ABS(t.longitude - 30)) AS rank
          FROM VENDORS v
          JOIN LOCATIONS t ON t.vendor_id = v.id
         WHERE t.latitude IS NOT NULL 
           AND t.longitude IS NOT NULL) x
  WHERE x.rank = 1
    AND x.distance < 50
ORDER BY x.distance

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

ROW_NUMBER вернет отдельное последовательное значение, которое сбрасывается для каждого поставщика в этом примере.Если вам нужны дубликаты, вам нужно использовать DENSE_RANK.

См. эту статью для эмуляции ROW_NUMBER в PostgreSQL до 8.4 .

1 голос
/ 07 февраля 2011

MySQL расширяет GROUP BY, и не все столбцы должны быть агрегатами. http://dev.mysql.com/doc/refman/5.0/en/group-by-hidden-columns.html

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

create temp table locations (id int, vender_id int, latitude int, longitude int);
CREATE TABLE
insert into locations values
        (1, 1, 50, 50),
        (2, 1, 35, 30),
        (3, 2, 5, 30)
;
SELECT
     locations.*, distance
     FROM
     (
          SELECT 
              vender_id,
              MIN(ABS(latitude - 30) + ABS(longitude - 30)) as distance
              FROM locations
              WHERE latitude IS NOT NULL AND longitude IS NOT NULL
                  GROUP BY vender_id
      ) AS min_locations
      JOIN locations ON
           ABS(latitude - 30) + ABS(longitude - 30) = distance
           AND min_locations.vender_id = locations.vender_id
       WHERE distance < 50
       ORDER BY distance
;
 id | vender_id | latitude | longitude | distance 
----+-----------+----------+-----------+----------
  2 |         1 |       35 |        30 |        5
  3 |         2 |        5 |        30 |       25
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...