2011-12-08 15 views
1

これを構築する方法がわからないため、エキスパートに公開してください。距離のクエリ

私はその場所の半径内のイベントを探している顧客のリストをデータベースに持っています。私は彼らの郵便番号(または緯度/経度)と彼らがイベントのために行く最大距離を保存することができます。したがって、列lat、lng、distance(例:lat = '22 .7447858 '、lng =' -82.1398589 '、distance = 25)。

イベントは一日中投稿され、郵便番号/緯度/経度が格納されます。

イベントの顧客を取得するクエリ(1日1回)を実行します。私はCyber​​Junkies投稿Mysql within distance queryを見ていたが、問題は私が反対方向にクエリを実行しているということです。私は、「円距離」が現在のイベントをカバーする顧客を見つける必要があります。サークルの距離を格納する方法がわからない(3列以上で十分ですか、このタイプのクエリのデータを格納する良い方法はありますか?)イベントごとに顧客を照会する方法がわからない

ありがとうございます!

答えて

2

をやっていると思います私はこれを行うには主に2つの方法があると思う:その場で距離を計算し、事前に計算した距離を一度し、それらを保存しますルックアップテーブルに格納する。

オプション1はオンザフライで計算します。 Tom van der Woerdtの答えは、あなたがこれをどうやって行うのかを説明するうまい仕事です。

SELECT * FROM customer, event WHERE (<calc distance>) < customer.distance 

オプション2、事前に計算し、すべての距離:擬似コードのクエリは、このようなものです。すべての顧客とすべてのイベント間の距離を格納するテーブル(この例ではdistanceと呼ぶ)を作成します。それには、customerid,eventid、およびmiles(または任意の距離メトリック)の3つの列があります。すべての顧客をループして各イベントまでの距離を計算し、それぞれをdistanceに格納します。新しい顧客またはイベントを追加するたびに、対応するレコードをdistanceテーブルに追加します。

SELECT * FROM distance WHERE miles < [[some number you pick]] 

だから、どちらが優れている。この構造は配置されると、イベントを見つけることのような単純なものでしょうか? CPU時間とディスク容量の間にはトレードオフがあるため、答えはリソースによって異なります。オプション1(オンザフライ計算)では、DBMSにより多くの作業が必要になります(より多くのCPU時間)。人とイベントの量が増えると、そのクエリは実行に時間がかかります。オプション2(距離をあらかじめ計算する)では、検索が非常に高速になりますが、事前に計算されたすべての距離をディスクに保存する必要があります。ルックアップテーブルが最新であることを確認することについても勤勉にする必要があります。顧客やイベントが追加、削除、または緯度/経度が変更されるたびに、対応するルックアップテーブルを更新する必要があります。 Triggersはこのプロセスを自動化するのに役立ちます。ルックアップテーブルが想定どおりに更新されるように、すべてのシナリオ(追加、削除、移動)をテストしてみてください。

短い答え:あなたのデータベースに非常に小さい負荷がある場合、またはディスク容量が限られている場合は、オプション1(オンザフライで計算)を選択してください。負荷が大きくてもディスク容量が豊富な場合は、オプション2を選択します。オプション2はシナリオの可能性が高く、スケーラビリティがはるかに優れています。

+0

2つ目の欠点は、新しいイベント/顧客が追加されるたびにそのリストを更新または更新する必要があることです。 – xQbert

+0

@xQbertあなたが正しいです、私は十分に明確にしていませんでした。私はもう少し追加します。 – ean5533

0

何がしたいことは次のとおりです。

SELECT * FROM customer, event WHERE (<calc distance>) < customer.distance 

これは単に、すべての顧客とのすべてのイベントを取得可能なすべての組み合わせ(100人の顧客と10件のイベントが1000の組み合わせを提供します)を取得するためにそれらを一緒に結合し、その後、彼らは「かどうかをチェックします範囲内に戻る。 *

私は個人的にそれを計算するDISTANCE(customer,event)関数を作ることをお勧めしたいと思います。そのようにクエリを管理する方が簡単で、再利用することができます。

* 必ずしもこの順序でA点からB点への距離を指すように点Bからの距離と同じになるだろう

0

(あなたが道路の方向と異なるパスを扱っている場合を除き) 。

基本的には、(SQL擬似コードで)

SELECT distance(event_loc, user_loc) <= user_max_distance 
0

あなたの距離の計算はthis solutionでのものと類似している場合、あなたはこのような何か行うことができます:あなたはデカルト座標(x、yおよびz)を使用して考えるかもしれません他の回答に加えて

select id1 from Distances 
    join EventTable on id2=EventTable.eventid 
    join UserTable on id1=UserTable.userid 
where type2=<EVENT_TYPE> and type1=<USER_TYPE> 
    and geodistance_km_by_obj(id1,<USER_TYPE>,id2,<EVENT_TYPE>) < UserTable.max_distance 
0

をDBストレージのlat/lngの代わりに、結果のクエリ式が、lat/lng距離の可能なクエリよりもdbサーバのロード/タイムに対してより単純であるためです。

PHP実装の例は、下に見出すことができる:

http://headers-already-sent.com/geodistance/

方法「getCartesian」デカルト座標に緯度/経度に変換し、メソッド「getDistanceByCartesian」は実際の距離を計算する方法を示しています。あなたがする必要があるのは、この距離計算をPHPからSQLクエリに転送することです(複雑であってはなりません)。

編集、私はあなたが私は私の会社の場所と、すべてのマクドナルドのレストランのための2デモ・テーブルを設定上記のリンクの下に見つけることができますクラスに基づいて、より実用的な例

を与えるために時間を見つけて私たちの近くにあるとデカルトX、Y、ZにGoogleマップから緯度/経度に変換:で、一定の距離(2000内のすべての場所(レストラン)を見つけるためのSQL-クエリこの2つのテーブルに基づいて

CREATE TABLE `locations` (
    `id` int(11) unsigned NOT NULL AUTO_INCREMENT, 
    `title` varchar(255) NOT NULL DEFAULT '', 
    `lat` double NOT NULL, 
    `lng` double NOT NULL, 
    `x` double NOT NULL, 
    `y` double NOT NULL, 
    `z` double NOT NULL, 
    PRIMARY KEY (`id`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8; 

INSERT INTO `locations` (`id`, `title`, `lat`, `lng`, `x`, `y`, `z`) 
VALUES 
    (1,'Ida-Ehre-Platz 10, 20095 Hamburg',53.55053,9.99949,3727600.05477,657242.251356,5124712.81705), 
    (2,'Kieler Straße 191-193, 22525 Hamburg',53.57731,9.93686,3725956.4981,652753.812254,5126481.40905), 
    (3,'Reeperbahn 42, 20359 Hamburg',53.549951,9.964937,3728046.74189,655003.113578,5124674.56664), 
    (4,'Theodor-Heuss-Platz 3, 20354 Hamburg',53.56083,9.99038,3726797.15378,656489.722425,5125393.17725), 
    (5,'Mundsburger Damm 67, 22087 Hamburg',53.57028,10.02642,3725550.98379,658686.623655,5126017.24553), 
    (6,'Paul-Nevermann-Platz 1, 22765 Hamburg',53.552602,9.936678,3728135.78521,653123.397726,5124849.69505), 
    (7,'Friedrich-Ebert-Damm 101, 22047 Hamburg',53.58753,10.08958,3723303.02881,662522.688778,5127156.05819), 
    (8,'Amsinckstraße 73, 20097 Hamburg',53.54271,10.02654,3727978.07563,659123.791421,5124196.16112), 
    (9,'Eiffestraße 440, 20537 Hamburg',53.55214,10.04638,3726919.13256,660267.521487,5124819.17553); 


CREATE TABLE `user` (
    `id` int(11) unsigned NOT NULL AUTO_INCREMENT, 
    `name` varchar(255) NOT NULL DEFAULT '', 
    `lat` double NOT NULL, 
    `lng` double NOT NULL, 
    `x` double NOT NULL, 
    `y` double NOT NULL, 
    `z` double NOT NULL, 
    PRIMARY KEY (`id`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8; 

INSERT INTO `user` (`id`, `name`, `lat`, `lng`, `x`, `y`, `z`) 
VALUES 
    (1,'Ministry.BBS, Cremon 36, 20457 Hamburg',53.545943,9.988761,3728127.10678,656615.385203,5124409.77226), 
    (2,'BBS, Dorotheenstraße 60, 22301 Hamburg',53.583231,10.008315,3724617.80169,657307.963226,5126872.28974); 

メートル)は、すべてのユーザー(私たちの会社のオフィス)になります:

SELECT locations.*, 
    2 * 6371000.785 * 
     asin(
      sqrt(
       pow(locations.x - user.x, 2) 
       + pow(locations.y - user.y, 2) 
       + pow(locations.z - user.z, 2) 
      )/(2 * 6371000.785) 
     ) AS distance 
    FROM locations, user 
    HAVING distance < 2000 
    ORDER BY distance ASC 

「メートル」以外のものが必要な場合は、地球の半径を約3分の1に変更する必要があります。 6371000.785(メートル単位)を必要なものに変更し、2000年の希望の距離を好みのものに変更するか、ユーザーごとにユーザーテーブルに保存します。