Запрос на расстоянии - PullRequest
       7

Запрос на расстоянии

1 голос
/ 08 декабря 2011

Не уверен, как это сделать, так что открываем для экспертов:

У меня есть список клиентов в дБ, которые ищут события в радиусе своего местоположения.Я могу сохранить их почтовый индекс (или широту / долготу) и максимальное расстояние, которое они пройдут для события.Таким образом, столбцы lat, lng, distance (например: lat = '22 .7447858 ', lng =' -82.1398589 ', distance = 25).

События публикуются весь день, а их почтовый индекс / lat / lng сохраняется.

Я хочу выполнить запрос (один раз в день), который получает клиентов на событие.Я смотрел на сообщение CyberJunkies Mysql в пределах расстояния , но проблема в том, что я выполняю запрос в противоположном направлении.Мне нужно найти клиентов, чье «расстояние по кругу» охватывает текущее событие, а не наоборот.Не знаете, как сохранить расстояние по кругу (достаточно ли хороши 3 вышеупомянутых столбца или есть лучший способ сохранить данные для этого типа запроса)?Не знаете, как запрашивать клиентов по каждому событию.

Заранее спасибо!

Ответы [ 5 ]

2 голосов
/ 08 декабря 2011

Я думаю, что есть два основных способа сделать это: вычислить расстояния на лету и предварительно рассчитать расстояния один раз, а затем сохранить их в справочной таблице.

Вариант 1 , расчет на лету.Ответ Тома ван дер Вёрдта хорошо объясняет, как вы это сделаете.Запрос псевдокода выглядит примерно так:

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

Опция 2 , предварительно рассчитайте все расстояния.Вы должны создать таблицу (назовите ее distance для этого примера), в которой хранится расстояние между каждым клиентом и каждым событием.Он будет иметь три столбца: customerid, eventid и miles (или любую другую метрику расстояния, которую вы хотите).Просмотрите каждого клиента, вычисляя расстояние до каждого события, и сохраните каждое в distance.Каждый раз, когда вы добавляете нового клиента или событие, вы добавляете соответствующие записи в таблицу distance.Как только эта структура будет создана, поиск событий будет таким простым:

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

Так какой из них лучше? Это компромисс между временем процессора и дисковым пространством, поэтому ответ зависитна ваших ресурсах.Вариант 1 (вычисления на лету) потребует от СУБД дополнительной работы (больше процессорного времени).По мере увеличения количества людей и событий этот запрос будет выполняться дольше.Вариант 2 (предварительный расчет расстояний) сделает поиск ОЧЕНЬ быстрым, но компромисс заключается в том, что вы должны хранить все эти предварительно рассчитанные расстояния на диске.Вы также должны быть внимательны, чтобы убедиться, что ваша таблица соответствия обновлена.Каждый раз, когда клиент или событие добавляются, удаляются или меняются их широта / долгота, вам необходимо соответствующим образом обновить таблицу поиска. Триггеры могут помочь вам сделать этот процесс автоматическим;просто попробуйте протестировать каждый сценарий (добавление, удаление, перемещение), чтобы убедиться, что справочная таблица обновляется так, как она должна.

Краткий ответ: выберите вариант 1 (рассчитать пона лету), если у вас очень маленькая нагрузка на базу данных и / или у вас ограничено дисковое пространствоВыберите вариант 2, если у вас большая нагрузка, но места на диске достаточно.Вариант 2 является более вероятным сценарием, и он гораздо более масштабируем.

0 голосов
/ 26 сентября 2012

В дополнение к другим ответам вы можете подумать об использовании декартовых координат (x, y и z) вместо lat / lng для хранения в дБ, поскольку полученные выражения запроса более просты в отношении нагрузки / времени на сервере дб, чем это возможно запросы для широты / долготы.

Пример реализации PHP можно найти по адресу:

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

Метод «getCartesian» преобразует широту / долготу в декартовы координаты, а метод «getDistanceByCartesian» показывает, как рассчитать фактическое расстояние. Что вам нужно сделать, это перенести это вычисление расстояния из PHP в запрос SQL (что не должно быть таким сложным).

Отредактируйте, поскольку я нашел время, чтобы привести более практический пример

Исходя из класса, который вы можете найти по указанной выше ссылке, я установил 2 демо-таблицы для местоположений моих компаний и всех ресторанов MC Donalds в нашей окрестности и преобразовал широту / долготу из Карт Google в декартову x, y, z:

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);

Исходя из этих двух таблиц, SQL-запрос для поиска всех местоположений (ресторанов) на определенном расстоянии (в этом примере 2000 метров в метрах) до каждого пользователя (офисы наших компаний) будет:

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

Если вам нужно что-то еще, кроме «метра», вам придется изменить радиус Земли прибл. 6371000,785 (в метрах) на все, что вам нужно, а также измените желаемое расстояние 2000 на то, что вам нравится или хранится в вашей пользовательской таблице для каждого пользователя.

0 голосов
/ 04 апреля 2012

Если ваши расчеты расстояния похожи на те, что в этом решении , то вы можете сделать что-то вроде этого:

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 голосов
/ 08 декабря 2011

Расстояние от точки A до точки B будет таким же, как расстояние от точки B до точки A (если вы не имеете дело с направлениями дороги и различными путями).

По сути, вы будете делать (в псевдокоде SQL)

SELECT distance(event_loc, user_loc) <= user_max_distance
0 голосов
/ 08 декабря 2011

То, что вы хотите:

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

Это просто соберет всех клиентов и все события, объединит их вместе, чтобы получить все возможные комбинации (100 клиентов и 10 событий дают 1000 комбинаций), а затем проверит,Вы в диапазоне.*

Я лично рекомендую сделать функцию DISTANCE(customer,event), которая рассчитывает ее для вас.Таким способом легче управлять запросом, и вы можете использовать его повторно.

* Не обязательно в таком порядке

...