你可以使用
球面余弦定理来得到以
地球半径为单位表示的距离。
有些人更喜欢
Haversine公式,因为它给出的精度更高,但是考虑到MySql的浮点精度已经足够高,两者之间的差异可以忽略不计。第一个公式实现起来更简单:
select c1.cafe_id,
substring_index (
group_concat( c2.cafe_id order by
acos( sin(radians(c1.gps_latitude)) * sin(radians(c2.gps_latitude))
+ cos(radians(c1.gps_latitude)) * cos(radians(c2.gps_latitude))
* cos(radians(c2.gps_longitude-c1.gps_longitude)) ) ),
',', 1) nearest
from cafe c1
inner join cafe c2 on c1.cafe_id <> c2.cafe_id
group by c1.cafe_id
样例数据的输出结果为:
| cafe_id | nearest |
|----------|----------|
| 011-1003 | 037-0233 |
| 037-0233 | 011-1003 |
| 121-934 | 192-143 |
| 192-143 | 121-934 |
这里有一个MySql fiddle。
解释
距离计算用于group_concat
聚合函数的order by
子句中,其结果是按照到所分组咖啡店的距离排序的逗号分隔的cafe_id值列表。 substring_index
函数从该列表中提取第一项。
联接条件很重要,因为如果没有它,您将得到咖啡店本身作为最近的邻居(它的距离显然为0)。
设置距离限制
在评论中,您要求只在特定半径内包括邻居的可能性。
在这种情况下,您可以输出转换为公里的“距离”(英里将是不同的因素):
select c1.cafe_id,
substring_index (
group_concat( c2.cafe_id order by
acos( sin(radians(c1.gps_latitude)) * sin(radians(c2.gps_latitude))
+ cos(radians(c1.gps_latitude)) * cos(radians(c2.gps_latitude))
* cos(radians(c2.gps_longitude-c1.gps_longitude)) ) ),
',', 1) nearest,
min(
acos( sin(radians(c1.gps_latitude)) * sin(radians(c2.gps_latitude))
+ cos(radians(c1.gps_latitude)) * cos(radians(c2.gps_latitude))
* cos(radians(c2.gps_longitude-c1.gps_longitude)) ) )
* 6371 km
from cafe c1
inner join cafe c2 on c1.cafe_id <> c2.cafe_id
group by c1.cafe_id
现在,您可以根据距离决定是否要忽略邻居。如果您真的想排除最近邻居太远的咖啡馆,则在末尾添加一个having
子句:
having km < 5
如果您更喜欢使用英里作为单位,那么在SQL中使用3959作为乘法因子,而不是6371。
WHERE
中添加“边界框”。如果这还不够好,请参阅我的博客。 - Rick James