2011-02-07 4 views
0

厄介なタイトルを許してください。私は質問を1つのフレーズに詰め込むのに苦労しました。誰かがより良いものを考え出すことができるなら、自由に感じてください。"many"のいくつかの基準による1対多の関連付けに基づいてクエリから結果をグループ化するにはどうすればよいですか?

私は、次の単純化されたスキーマがあります。

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 

I:

vendors 
    INT id 

locations 
    INT id 
    INT vendor_id 
    FLOAT latitude 
    FLOAT longitude 

は私が復帰半径の近似によって制限距離によってソート最寄りのベンダーのリストの完全対応していますがこの瞬間、注文/制限期間の繰り返しの周りに自分の道を見つけることはできません。私は最初にSELECTフィールドの間に "距離"としてエイリアシングを試みましたが、psqlはWHERE節でこのエイリアスが利用できないと教えてくれました。ファイン。私の主な質問には:

私がしたいのは、それぞれが最も近い位置につながっているベンダーのリストを返すことですこのリストを近接順に並べ、半径で限定します。

私は2つのベンダーがあり、それぞれ2つのロケーションがあるとします。私は4つの場所のうちの1つだけがその場所の関連ベンダーをベンダー自身と一緒に返すように半径を制限するクエリが必要です。半径がすべての場所を網羅していれば、ベンダー1はその場所とベンダー2の間に最も近いものを提示し、そのベンダー2は最も近い場所に近接してベンダー1と2を注文することが望ましいでしょう。

MySQLでは、GROUP BY、次にMIN(distance)を使用して、各ベンダーの行に最も近い場所を取得できました。しかし、PostgreSQLはGROUP BYの使用法が厳しくなっているようです。

可能であれば、SELECT句で干渉を避けたいと思います。可能ならば上記のクエリのWHEREORDER部分を再利用したいと思います。しかし、これらは決して絶対的な要件ではありません。

私はDISTINCT ONGROUP BYでハックした試行をしましたが、これは私にかなりの詳細を詳しく説明しませんが、他の場所でのミラーステートメントが欠けているという点でかなり問題になりました。


ソリューション

は私がOMG Ponies' excellent answerオフベースのソリューションを採用することになりました。 OMGポニーのソリューションから

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; 

いくつかの偏差:

  • 場所は現在、多形_typeを経由して関連しています。少しの前提が変わります。
  • サブクエリの外側で結合を移動しました。パフォーマンスの影響があるのか​​どうかはわかりませんが、サブクエリを場所と分割ランキングで表示し、大きなクエリをすべてまとめて表示するという意味になりました。
  • minorテーブル名エイリアシングを取り除きました。私はエイリアシングに慣れていましたが、それに追随するのが難しくなりました。私はPostgreSQLを経験してからその才能を発揮するまで待つつもりです。
+1

テーブル名の別名(主に)スタイルのポイントです:一部の人々は常にそれらを使用し、一部の人々はそれらを避けます。計算を内側のクエリに移動し、その後に追加のデータと結合して「後で」は完全にわかります。 Explain出力を見ることは、パフォーマンスに影響があるかどうかを確認する方法です。この場合、ランキング機能が暗示しているソート中にデータを保持する必要がないため、マイナーな改善が得られるかもしれません。 – araqnid

答えて

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()'が好きな理由は何ですか?確かに、私はどちらも理解しませんが、後者は同じ結果を出すように見え、便利なことにトップレベルのクエリで別名を必要としません。 –

+0

私はそれらを比較する[ドキュメントの一部](http://www.postgresql.org/docs/8.4/interactive/functions-window.html)を見つけましたが、違いを見つけ出すのは苦労しています。 –

+0

私自身の質問には申し訳ありませんが、私の目的には 'ROW_NUMBER'が望ましい理由が発見されました。 2つの「場所」は決して 'ROW_NUMBER'には結びつきません。実際には、距離の関係は非常に稀ですが、エッジケースがテストケースで非常に簡単にポップアップしてしまいます。追加するものがあれば教えてください。 –

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 
+0

私はあなたの創造的な「ABS(緯度-30)+ ABS(経度-30)=遠方」参加に感心しています。あなたの解決策は私には大いに意味があります。しかし、サブクエリ「min_locations」のMIN(...)を担当する行の残りの部分を「ドラッグ」する方法は他にありませんか?私はそれが非常に便利な機能になるようだと言わなければならない。グループ化されていない、集約されていないフィールドを表示する際のpsqlの難解さを理解していますが(「平均」集約には当然意味があります)、「分」と見なすのはいいでしょう。私はそれだけで線をぼかしていると思う。 –

+0

そうでなければ、あなたがそうでなければ非合法な結合ソリューションの残念なことに、2つの場所(同じベンダーの下で、結合条件にvendor_id制約を含めると)が同じ距離にあると問題が発生するようです。助言がありますか? –

+0

@Steven Xu:PostgresqlはGROUP BYを標準にしています。 MySQLは拡張であり、グループ化されていない行は、どのように選択されたのか(docに従って)「不確定」です。 1つのベンダーにつき1つの行しか選択されないようにするには、min(id)以外のすべての列がグループ化されている外部クエリでグループを使用できます。比類のない進歩としては、特にオープンソースのために、窓関数はかなり新しくなっています。 SQLは70年代以来ずっと続いています。テーブルは何らかの形で結合されていなければなりません。:-) –

関連する問題