Использование предложения WHERE для нахождения POI в диапазоне расстояний от долготы и широты
Я использую следующий sql-код, чтобы узнать " все " poi, наиболее близкие к заданным координатам, но я хотел бы узнать конкретный poi, а не все из них. Когда я пытаюсь использовать предложение where, я получаю ошибку, и она не работает, и это то, где я в настоящее время застрял, так как я использую только одну таблицу для всех координат всех poi.
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 ответа:
Проблема заключается в том, что вы не можете ссылаться на столбец с псевдонимами (
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;
Edit: Как указано ниже, это приведет к полному сканированию таблицы. В зависимости от объема данных, которые вы будете обрабатывать, вы можете избежать этого и перейти к первому варианту, который должен работать быстрее.
Причина, по которой вы не можете использовать свой псевдоним в предложении
WHERE
, - это порядок, в котором MySQL выполняет вещи:
FROM
WHERE
GROUP BY
HAVING
SELECT
ORDER BY
При выполнении предложения
WHERE
значение псевдонима столбца еще не вычислено. Это хорошая вещь, потому что это будет тратить много производительности. Представьте себе много (1,000,000) строк-чтобы использовать ваши вычисления в предложенииWHERE
, Каждый из них 1 000 000 сначала нужно будет получить и вычислить, чтобы условиеWHERE
могло сравнить результаты вычисления с вашим ожиданием.Вы можете сделать это явно либо
- используя
HAVING
(Вот почемуHAVING
имеет другое имя, какWHERE
- это другое дело)- использование подзапроса, как показано на примере @MostyMostacho (эффективно сделает то же самое с некоторыми накладными расходами)
- поместите сложное вычисление в Предложение
WHERE
(фактически даст тот же результат производительности, что иHAVING
)Все они будут работать почти одинаково плохо: каждая строка выбирается первой, расстояние вычисляется и, наконец, фильтруется по расстоянию перед отправкой результата клиенту.
Вы можете получить много (!) повышение производительности путем смешивания простого предложения
WHERE
для аппроксимации расстояния (фильтрация строк для извлечения первой) с более точной евклидовой формулой в предложенииHAVING
.
- найти строки, которые могут совпадать условие
@distance = 10
с использованием предложенияWHERE
, основанного на простом расстоянии X и Y (ограничивающем поле) - это дешевая операция .- фильтруйте эти результаты, используя формулу для евклидова расстояния в предложении
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-это радиус Земли в километрах, поэтому используйте эту константу для измерения расстояния в километрах
Дополнительные сведения см. в Вики про формула Гаверсинуса