2011-02-07 32 views
0

請原諒尷尬的標題。我很難將我的問題提煉成一個短語。如果任何人都可以拿出更好的一個,那就隨意吧。如何根據「許多」中的某個標準基於一對多關聯查詢對結果進行分組?

我有以下簡單的模式:

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之間距離最近的供應商2最近,最終根據其最近位置的距離排序供應商1和供應商2。

在MySQL中,我設法通過使用GROUP BY然後MIN(distance)來獲得每個供應商行中最近的位置。但PostgreSQL似乎更嚴格的使用GROUP BY

如果可能,我希望避免插手SELECT條款。我還想,如果可能的話,重新使用上述查詢的WHEREORDER部分。但這絕不是絕對的要求。

我對DISTINCT ONGROUP BY做了陳腐的嘗試,但是這些給我帶來了一些麻煩,主要是因爲我在其他地方缺少鏡像語句,我現在不會詳細說明。


我結束了採用基於截止OMG Ponies' excellent answer的溶液。

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小馬的解決方案有些不同之處:

  • 位置,現在通過多態相關_type。一點前提變化。
  • 我將連接移到了子查詢之外。我不知道是否存在性能影響,但我認爲將子查詢視爲獲取位置和分區排名,然後將更大的查詢視爲將它們集合在一起的行爲是有意義的。
  • 未成年人帶走表名別名。雖然我很多時候都習慣於鋸齒,但它讓我更難以跟隨。我會等到我對PostgreSQL有更多的經驗之後纔開始工作。
+1

表名走樣(大部分)的風格一點:有些人總是使用它們,有些人避免它們。將計算移動到內部查詢中,然後再與其他數據「後來」結合起來對我來說是完全有意義的。查看解釋輸出是查看是否存在性能影響的方法;我覺得在這種情況下,它可能會提供一個小的改進,因爲在排序功能所隱含的排序期間不得不保持較少的數據。 – araqnid 2011-02-07 11:42:10

回答

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

+0

任何理由打電話比`RANK()`更喜歡`ROW_NUMBER()`?當然,我既不理解,但後者似乎產生相同的結果,並且方便地在給定頂級查詢的情況下不需要別名。 – 2011-02-07 06:57:47

+0

我發現[文檔的一部分](http://www.postgresql.org/docs/8.4/interactive/functions-window.html)對它們進行了比較,但我很難區分差異。 – 2011-02-07 07:23:55

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 
相關問題