如何根据“多”中某些标准基于一对多关联的查询对结果进行分组?

时间:2011-02-07 04:38:49

标签: sql postgresql

请原谅这个尴尬的头衔。我很难将我的问题提炼成一个短语。如果有人能想出更好的,请随意。

我有以下简化架构:

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

我现在无法找到重复订单/限制条款的方法。我最初尝试将它作为“{1}}字段中的”距离“别名,但是psql告诉我这个别名在SELECT子句中不可用。精细。如果在这周围有一些花哨的裤子,我会全力以赴,但在我的主要问题上:

我想要做的是返回供应商列表,每个供应商都与最接近的位置相关联,并按照邻近度排列此列表并受半径限制。

假设我有2个供应商,每个供应商有两个位置。我想要一个限制半径的查询,使得四个位置中只有一个位于其中,以便将该位置的相关供应商与供应商本身一起返回。如果半径包含所有位置,我希望供应商1在其位置与供应商2之间最接近,其位置最接近,最终根据最近位置的距离对供应商1和2进行排序。

在MySQL中,我设法使用WHERE然后GROUP BY获取每个供应商行中最近的位置。但PostgreSQL似乎对MIN(distance)的使用更为严格。

如果可能的话,我希望避免插入GROUP BY条款。如果可能的话,我还想重复使用上述查询的SELECTWHERE部分。但这绝不是绝对的要求。

我曾在ORDERDISTINCT ON做过陈词滥调,但这些给我带来了一些麻烦,主要是因为我在其他地方遗漏了镜像陈述,我不会详细详细说明现在。


解决方案

我最终采用了基于OMG Ponies' excellent answer的解决方案。

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; 进行多态关联。一点前提改变。
  • 我将连接移到了子查询之外。我不知道是否存在性能影响,但在我看来,将子查询视为获取位置和分区排名,然后将更大的查询视为将所有这些组合在一起的行为。
  • minor 收起表名别名。虽然我已经习惯了混淆,但这让我更难跟上。我会等到我对PostgreSQL更有经验之后再开始这种天赋。

2 个答案:

答案 0 :(得分:2)

对于PostgreSQL 8.4+,您可以使用analytics like 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。

请参阅this article for emulating ROW_NUMBER on PostgreSQL pre-8.4

答案 1 :(得分:1)

MySQL扩展了GROUP BY,并非所有列都需要聚合。 http://dev.mysql.com/doc/refman/5.0/en/group-by-hidden-columns.html

我在这里遇到过很多同样问题的问题。诀窍是在子查询中获取nececssary列,然后在外部查询中自己加入它:

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