J'ai besoin d'écrire une requête qui me permette de trouver tous les lieux situés dans un intervalle (miles) à partir d'un lieu donné.
Le tableau est le suivant :
id | name | lat | lng
J'ai donc fait des recherches et j'ai trouvé : voici ma présentation sql
Je l'ai testé sur un tableau d'environ 100 lignes et j'en aurai bien d'autres ! - Il doit être évolutif.
J'ai d'abord essayé quelque chose de plus simple comme ça :
//just some test data this would be required by user input
set @orig_lat=55.857807; set @orig_lng=-4.242511; set @dist=10;
SELECT *, 3956 * 2 * ASIN(
SQRT( POWER(SIN((orig.lat - abs(dest.lat)) * pi()/180 / 2), 2)
+ COS(orig.lat * pi()/180 ) * COS(abs(dest.lat) * pi()/180)
* POWER(SIN((orig.lng - dest.lng) * pi()/180 / 2), 2) ))
AS distance
FROM locations dest, locations orig
WHERE orig.id = '1'
HAVING distance < 1
ORDER BY distance;
Cela a retourné des rangs dans environ 50ms ce qui est plutôt bien ! Toutefois, ce processus ralentirait considérablement à mesure que le nombre de rangs augmente.
EXPLAIN
montre qu'il n'utilise que la clé PRIMAIRE, ce qui est évident.
Puis, après avoir lu l'article lié ci-dessus . J'ai essayé quelque chose comme ça :
// defining variables - this when made into a stored procedure will call
// the values with a SELECT query.
set @mylon = -4.242511;
set @mylat = 55.857807;
set @dist = 0.5;
-- calculate lon and lat for the rectangle:
set @lon1 = @mylon-@dist/abs(cos(radians(@mylat))*69);
set @lon2 = @mylon+@dist/abs(cos(radians(@mylat))*69);
set @lat1 = @mylat-(@dist/69);
set @lat2 = @mylat+(@dist/69);
-- run the query:
SELECT *, 3956 * 2 * ASIN(
SQRT( POWER(SIN((@mylat - abs(dest.lat)) * pi()/180 / 2) ,2)
+ COS(@mylat * pi()/180 ) * COS(abs(dest.lat) * pi()/180)
* POWER(SIN((@mylon - dest.lng) * pi()/180 / 2), 2) ))
AS distance
FROM locations dest
WHERE dest.lng BETWEEN @lon1 AND @lon2
AND dest.lat BETWEEN @lat1 AND @lat2
HAVING distance < @dist
ORDER BY distance;
L'heure de cette requête est environ 240 ms Ce n'est pas trop mal, mais c'est plus lent que le précédent. Mais je peux imaginer qu'avec un nombre de rangs beaucoup plus élevé, cela irait plus vite. Cependant, une EXPLAIN
montre les clés possibles comme lat
, lng
o PRIMARY
et utilisé PRIMARY
.
Comment puis-je faire mieux ???
Je sais que je pourrais stocker la lat lng comme un POINT() ; mais je n'ai pas trouvé beaucoup de documentation à ce sujet qui montre si c'est plus rapide ou précis ?
Toute autre idée serait acceptée avec plaisir !
Merci beaucoup !
-Stefan
UPDATE :
Comme Jonathan Leffler l'a souligné, j'avais fait quelques erreurs que je n'avais pas remarquées :
J'avais seulement mis abs() sur une des valeurs de lat. Dans la deuxième requête, j'ai également utilisé une recherche par identifiant dans la clause WHERE, alors que ce n'était pas nécessaire. La première requête était purement expérimentale, la seconde est plus susceptible d'être utilisée en production.
Après ces changements EXPLAIN
montre que la clé utilise maintenant lng
et le temps moyen de réponse autour de 180ms maintenant, ce qui est une amélioration.