1

私はこのクエリを持っています

SELECT zip, 
( 3959 * acos( cos( radians(34.12520) ) * cos( radians( zip_info.latitude ) ) * cos(radians( zip_info.longitude ) - radians(-118.29200) ) + sin( radians(34.12520) ) * sin( radians( zip_info.latitude ) ) ) ) AS distance, 
user_info.*, office_locations.* 

FROM zip_info 

RIGHT JOIN office_locations ON office_locations.zipcode = zip_info.zip 

RIGHT JOIN user_info ON office_locations.doctor_id = user_info.id 

WHERE user_info.status='yes' 

HAVING distance < 50 ORDER BY distance ASC

出力します

距離| doctor_id | 等

7 ---------------5-------など

8 ---------------4-------など

34 ---------------4-------など

49 ---------------5-------など

30以下の距離を選択すると、上位2つの結果も表示されます。これは良いことです。

問題:doctor_idごとに複数の結果を表示したくないので、GROUP BY user_info.doctor_idを実行します。これは、距離が50未満の場合は結果を表示しません。何らかの理由で、それ以外の場合はすべての結果をグループ化する必要があります。動作しません。任意のヒント?他に何か私を助ける必要がありますか?

だから私が欲しいのは

距離| doctor_id | 等

7 ---------------5-------など

8 ---------------4-------など

結果として4行すべてを表示したいのですが、それらをグループ化して、一意のuser_info.doctor_idあたりの距離が最小の行のみが表示されるようにします。距離は仮想的に存在しないテーブルであることに注意してください。


llionのクエリに基づいて、次の結果が得られます。

 (concat(user_info.id))     zip     distance    id
          1                 NULL    6.6643992   1 

結果は1つだけで、それを機能させるには、ANDをHAVINGの距離に再度変更する必要がありました。

4

2 に答える 2

1

GROUPBYがあなたが望む結果をもたらすとは思わない。残念ながら、MySQLは分析関数をサポートしていません(これが、OracleまたはSQL Serverでこの問題を解決する方法です)。

ユーザー定義変数を利用することで、いくつかの基本的な分析関数をエミュレートすることができます。

この場合、エミュレートする必要があります。

ROW_NUMBER() OVER(PARTITION BY doctor_id ORDER BY distance ASC) AS seq

doctor_idそのため、元のクエリから始めて、最初にソートされ、次に計算されたでソートされるようにORDERBYを変更しましたdistance。(これらの距離がわかるまで、どれが「最も近い」かはわかりません。)

このソートされた結果を使用して、基本的に各doctor_idの行に「番号を付け」、最も近い行を1、2番目に近い行を2というように続けます。新しいdoctor_idを取得すると、最も近いものを1として再開します。

これを実現するために、ユーザー定義変数を利用します。行番号の割り当てに1つ使用します(変数名は@iで、返される列のエイリアスはseqです)。前の行のdoctor_idを「記憶」するために使用するもう1つの変数。これにより、doctor_idの「中断」を検出できるため、行番号を1から再開するタイミングを知ることができます。

クエリは次のとおりです。


SELECT z.*
, @i := CASE WHEN z.doctor_id = @prev_doctor_id THEN @i + 1 ELSE 1 END AS seq
, @prev_doctor_id := z.doctor_id AS prev_doctor_id
FROM
(

  /* original query, ordered by doctor_id and then by distance */
  SELECT zip, 
  ( 3959 * acos( cos( radians(34.12520) ) * cos( radians( zip_info.latitude ) ) * cos(radians( zip_info.longitude ) - radians(-118.29200) ) + sin( radians(34.12520) ) * sin( radians( zip_info.latitude ) ) ) ) AS distance, 
  user_info.*, office_locations.* 
  FROM zip_info 
  RIGHT JOIN office_locations ON office_locations.zipcode = zip_info.zip 
  RIGHT JOIN user_info ON office_locations.doctor_id = user_info.id 
  WHERE user_info.status='yes' 
  ORDER BY user_info.doctor_id ASC, distance ASC

) z JOIN (SELECT @i := 0, @prev_doctor_id := NULL) i
HAVING seq = 1 ORDER BY z.distance

元のクエリが必要な結果セットを返していると仮定しています。行が多すぎるため、各doctor_idの「最も近い」(距離の値が最小の行)以外のすべてを削除する必要があります。

元のクエリを別のクエリでラップしました。元のクエリに加えた唯一の変更は、結果をdoctor_idで並べ替えてから距離で並べ替え、HAVING distance < 50句を削除することでした。(50未満の距離のみを返したい場合は、先に進んでその句をそのままにしておきます。それが意図したものなのか、それとも、doctor_idごとに行を1つに制限するために指定されたものなのかは明確ではありませんでした。)

注意すべきいくつかの問題:

置換クエリは2つの追加の列を返します。これらは、結果セットを生成する手段を除いて、結果セットでは実際には必要ありません。(このSELECT全体を別のSELECTで再度ラップして、これらの列を省略することは可能ですが、それは価値があるよりも実際には厄介です。列を取得するだけで、無視できることがわかります。)

もう1つの問題は.*、内部クエリでのinの使用は少し危険であり、そのクエリによって返される列名が一意であることを実際に保証する必要があるということです。(現在、列名が異なる場合でも、これらのテーブルの1つに列を追加すると、クエリに「あいまいな」列例外が発生する可能性があります。これを回避するのが最善です。これは、.*を次のリストに置き換えることで簡単に対処できます。返される列、および「重複する」列名のエイリアスの指定(。z.*によって返される列を制御している限り、外部クエリでの使用は問題になりませんz。)


補遺:

GROUP BYでは、必要な結果セットが得られないことに注意しました。GROUP BYを使用したクエリで結果セットを取得することは可能ですが、正しい結果セットを返すステートメントは面倒です。を指定するMIN(distance) ... GROUP BY doctor_idと、最小の距離が得られますが、SELECTリスト内の他の非集計式が、他の行ではなく、最小の距離の行からのものであるという保証はありません。(MySQLは、GROUP BYおよび集計に関して危険なほど自由です。MySQLエンジンをより慎重にするために(そして他のリレーショナルデータベースエンジンと一致させるために)、SET sql_mode = ONLY_FULL_GROUP_BY

補遺2:

Dariousによって報告されたパフォーマンスの問題「一部のクエリには7秒かかります」。

処理を高速化するには、関数の結果をキャッシュすることをお勧めします。基本的に、ルックアップテーブルを作成します。例えば

CREATE TABLE office_location_distance
( office_location_id INT UNSIGNED NOT NULL COMMENT 'PK, FK to office_location.id'
, zipcode_id         INT UNSIGNED NOT NULL COMMENT 'PK, FK to zipcode.id'
, gc_distance        DECIMAL(18,2)         COMMENT 'calculated gc distance, in miles'
, PRIMARY KEY (office_location_id, zipcode_id)
, KEY (zipcode_id, gc_distance, office_location_id)
, CONSTRAINT distance_lookup_office_FK
  FOREIGN KEY (office_location_id) REFERENCES office_location(id)
  ON UPDATE CASCADE ON DELETE CASCADE
, CONSTRAINT distance_lookup_zipcode_FK
  FOREIGN KEY (zipcode_id) REFERENCES zipcode(id)
  ON UPDATE CASCADE ON DELETE CASCADE
) ENGINE=InnoDB

それはただのアイデアです。(特定の郵便番号からoffice_location距離を検索していると思います。したがって、(zipcode、gc_distance、office_location_id)のインデックスは、クエリに必要なカバーインデックスです(計算された距離をFLOATとして保存することは避けます。 FLOATデータ型を使用したクエリパフォーマンス)

INSERT INTO office_location_distance (office_location_id, zipcode_id, gc_distance)
SELECT d.office_location_id
     , d.zipcode_id
     , d.gc_distance
  FROM (
         SELECT l.id AS office_location_id
              , z.id AS zipcode_id
              , ROUND( <glorious_great_circle_calculation> ,2) AS gc_distance
           FROM office_location l
          CROSS
           JOIN zipcode z
          ORDER BY 1,3
       ) d
ON DUPLICATE KEY UPDATE gc_distance = VALUES(gc_distance)

関数の結果がキャッシュされてインデックスが作成されると、クエリははるかに高速になります。

SELECT d.gc_distance, o.*
  FROM office_location o
  JOIN office_location_distance d ON d.office_location_id = o.id
 WHERE d.zipcode_id = 63101
   AND d.gc_distance <= 100.00
 ORDER BY d.zipcode_id, d.gc_distance

INSERT/UPDATEのHAVING述語をキャッシュテーブルに追加することを躊躇しています。(緯度/経度が間違っていて、100マイル未満で誤った距離を計算した場合、緯度/経度が固定されて距離が1000マイルになった後の後続の実行...行がクエリから除外されている場合、その場合、キャッシュテーブルの既存の行は更新されません(キャッシュテーブルをクリアすることはできますが、実際には必要ありません。データベースとログに多くの余分な作業が必要です。メンテナンスクエリの結果セットも大規模な場合は、zipcodeごと、またはoffice_locationごとに繰り返し実行するように分割できます。)

一方、特定の値を超える距離に関心がない場合は、HAVING gc_distance <述語を追加して、キャッシュテーブルのサイズを大幅に削減できます。

于 2012-06-19T22:19:10.457 に答える
1

HAVING句は、集計結果に基づいてフィルタリングします。GROUP BYをステートメントに追加すると、distance列を構成する方程式により、単一のdoctor_idのすべての行の値が追加されます。したがって、距離は次のようになります。

distance | doctor_id | etc
      56 |         5 | etc
      42 |         4 | etc

ご覧のとおり、doctor_id5は>50です。doctor_id4が結果を返さない場合は、表示しなかった行がさらにあると思います。

必要なのは、距離が50未満の個別のdoctor_idです。最小、最大、平均の距離が必要ですか?たぶんこれはあなたが望むものです(私はこれをテストしていません、そしてあなたはzip値の周りのグループ化を解決する必要があると思います):

SELECT distinct(concat(zip,user_info.doctor_id)), zip, min(( 3959 * acos( cos( radians(34.12520) ) * cos( radians( zip_info.latitude ) ) * cos(radians( zip_info.longitude ) - radians(-118.29200) ) + sin( radians(34.12520) ) * sin( radians( zip_info.latitude ) ) ) )) AS distance, 
user_info.doctor_id

FROM zip_info 

RIGHT JOIN office_locations ON office_locations.zipcode = zip_info.zip 

RIGHT JOIN user_info ON office_locations.doctor_id = user_info.id 

WHERE user_info.status='yes' 
AND distance < 50 ORDER BY distance ASC

これにより、他のものを追加することなく、一意のzip/ドクターグループが提供されます。

于 2012-06-19T22:19:41.513 に答える