5

次のSQLコードを使用して、設定された座標に最も近い「すべての」poiを見つけていますが、すべてではなく特定のpoiを見つけたいと思います。where句を使用しようとすると、エラーが発生し、機能しません。これは、すべてのpoiのすべての座標に1つのテーブルしか使用しないため、現在スタックしている場所です。

SET @orig_lat=55.4058;  
SET @orig_lon=13.7907; 
SET @dist=10;
SELECT 
    *, 
    3956 * 2 * ASIN(SQRT(POWER(SIN((@orig_lat -abs(latitude)) * pi()/180 / 2), 2) 
    + COS(@orig_lat * pi()/180 ) * COS(abs(latitude) * pi()/180) 
    * POWER(SIN((@orig_lon - longitude) * pi()/180 / 2), 2) )) as distance 
FROM geo_kulplex.sweden_bobo
HAVING distance < @dist 
ORDER BY distance limit 10;
4

2 に答える 2

6

問題は、 or句で別名列 (distanceこの場合) を参照できないことです。たとえば、次のことはできません。selectwhere

select a, b, a + b as NewCol, NewCol + 1 as AnotherCol from table
where NewCol = 2

これは両方で失敗します:処理しようとしたときのステートメントselectと、処理しようとしたときのステートメントの両方です。NewCol + 1whereNewCol = 2

これを解決するには、次の 2 つの方法があります。

1) 参照を計算値自体に置き換えます。例:

select a, b, a + b as NewCol, a + b + 1 as AnotherCol from table
where  a + b = 2

2) 外部selectステートメントを使用します。

select a, b, NewCol, NewCol + 1 as AnotherCol from (
    select a, b, a + b as NewCol from table
) as S
where NewCol = 2

さて、あなたの巨大で人間に優しくない計算列を考えると:)読みやすさを改善するには、最後のオプションを選択する必要があると思います。

SET @orig_lat=55.4058;  
SET @orig_lon=13.7907; 
SET @dist=10;

SELECT * FROM (
  SELECT 
    *, 
    3956 * 2 * ASIN(SQRT(POWER(SIN((@orig_lat -abs(latitude)) * pi()/180 / 2), 2) 
    + COS(@orig_lat * pi()/180 ) * COS(abs(latitude) * pi()/180) 
    * POWER(SIN((@orig_lon - longitude) * pi()/180 / 2), 2) )) as distance 
  FROM geo_kulplex.sweden_bobo
) AS S
WHERE distance < @dist
ORDER BY distance limit 10;

編集: @Kaii が以下で述べたように、これにより完全なテーブル スキャンが行われます。処理するデータの量によっては、それを避けて、より高速に実行される最初のオプションを使用することをお勧めします。

于 2012-02-26T14:05:05.783 に答える
4

WHERE句でエイリアスを使用できない理由は、MySQL が実行する順序です。

  1. FROM
  2. WHERE
  3. GROUP BY
  4. HAVING
  5. SELECT
  6. ORDER BY

句を実行するときWHERE、列エイリアスの値はまだ計算されていません。これは、多くのパフォーマンスを浪費するため、良いことです。多くの (1,000,000) 行を想像してみてください。句で計算を使用するには、条件が計算結果を期待値と比較できるWHEREように、最初にそれらの 1,000,000 行をそれぞれフェッチして計算する必要があります。WHERE

これは、次のいずれかで明示的に行うことができます

  • 使用(これが別の名前を持つHAVING理由です-それは別のものです)HAVINGWHERE
  • @MostyMostacho で示されているようにサブクエリを使用する (いくらかのオーバーヘッドで効果的に同じことを行います)
  • 複雑な計算をWHERE句に入れます(事実上、 と同じパフォーマンス結果が得られますHAVING

これらはすべて、ほぼ同じようにパフォーマンスが低下します。各行が最初にフェッチされ、距離が計算され、最終的に距離によってフィルタリングされてから、結果がクライアントに送信されます。

WHERE距離近似の単純な節 (行をフィルタリングして最初にフェッチする) と、節内のより正確なユークリッド式を組み合わせることで、はるかに (!) 優れたパフォーマンスを得ることができますHAVING

  1. 単純な X と Y の距離 (バウンディング ボックス) に基づく句を使用して、条件に一致する可能性のある行を検索します。これは安価な操作です。@distance = 10WHERE
  2. 句でユークリッド距離の式を使用してこれらの結果をフィルタリングします。HAVINGこれはコストのかかる操作です。

このクエリを見て、私の意味を理解してください。

SET @orig_lat=55.4058;
SET @orig_lon=13.7907; 
SET @dist=10;
SELECT 
    *, 
    3956 * 2 * ASIN(SQRT(POWER(SIN((@orig_lat -abs(latitude)) * pi()/180 / 2), 2)
    + COS(@orig_lat * pi()/180 ) * COS(abs(latitude) * pi()/180) 
    * POWER(SIN((@orig_lon - longitude) * pi()/180 / 2), 2) )) as distance 
FROM geo_kulplex.sweden_bobo
/* WHERE clause to pre-filter by distance approximation .. filter results 
   later with precise euclidian calculation. can use indexes. */
WHERE 
    /* i'm unsure about geo stuff ... i dont think you want a 
       distance of 10° here, please adjust this properly!! */
    latitude BETWEEN (@orig_lat - @dist) AND (@orig_lat + @dist)
    AND longitude BETWEEN (@orig_lon - @dist) AND (@orig_lon + @dist)
/* HAVING clause to filter result using the more precise euclidian distance */
HAVING distance < @dist 
ORDER BY distance limit 10;

定数に興味がある人のために:

  • 3956 は地球のマイル単位の半径なので、結果の距離はマイル単位で測定されます。
  • 6371 はキロメートル単位の地球の半径です。この定数を使用して距離をキロメートル単位で測定します。

Haversine 式の詳細については、 Wiki を参照してください。

于 2012-02-27T10:13:48.570 に答える