Использование предложения WHERE для поиска POI в диапазоне расстояний от долготы и широты - PullRequest
4 голосов
/ 26 февраля 2012

Я использую следующий sql-код, чтобы найти «ВСЕ» пои, наиболее близкие к заданным координатам, но я бы хотел найти конкретные пои вместо всех них. Когда я пытаюсь использовать предложение where, я получаю сообщение об ошибке, и оно не работает, и это то место, где я сейчас застрял, поскольку я использую только одну таблицу для всех координат всех точек.

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;

Ответы [ 2 ]

5 голосов
/ 26 февраля 2012

Проблема в том, что вы не можете ссылаться на столбец с псевдонимом (в данном случае distance) в предложении select или where. Например, вы не можете сделать это:

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

Это будет невозможно как в операторе select при попытке обработать NewCol + 1, так и в операторе where при обработке NewCol = 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, это приведет к полному сканированию таблицы. В зависимости от объема данных, которые вы будете обрабатывать, вы можете избежать этого и перейти к первому варианту, который должен работать быстрее.

3 голосов
/ 27 февраля 2012

Причиной, по которой вы не можете использовать свой псевдоним в предложении 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 (вот почему HAVING имеет другое имя как WHERE - это совсем другое)
  • , используяподзапрос, как показано в @MostyMostacho (эффективно проделает то же самое с некоторыми накладными расходами)
  • поместит комплексное вычисление в предложение WHERE (фактически даст тот же результат производительности, что и HAVING)

Все они будут работать почти одинаково плохо: каждая строка извлекается первой, расстояние вычисляется и, наконец, фильтруется по расстоянию перед отправкой результата клиенту.

Вы можете получитьнамного (!) лучшую производительность, смешивая простое предложение WHERE для приближения расстояния (фильтрация строк для выборки в первую очередь) с более точным euclформула идиана в предложении HAVING.

  1. находит строки, которые могут соответствовать условию @distance = 10, используя предложение WHERE на основе простых расстояний X и Y (ограничивающая рамка) - это дешевая операция.
  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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...