我在JAVA Spring服务器中运行以下SQL查询。该查询对于几乎所有坐标都是完美的,除了一个特定对c =< 23.065079,72.511478> (= to_lat,to_long):
SELECT *
FROM karpool.ride
WHERE Acos(Sin(Radians(23.065079)) * Sin(Radians(to_lat)) +
Cos(Radians(23.065079)) * Cos(Radians(to_lat)) *
Cos(Radians(to_lon) - Radians(72.511478))) * 6371 <= 10;
我的数据库距离c的距离不到10公里。通过上面的查询,我获得了所有这些位置的距离,除了与c完全匹配的位置。在这种情况下返回的距离应为0,但查询失败。
这是一个SQL问题还是公式有问题?
答案 0 :(得分:1)
这很可能是由于floating point accuracy problems。
首先,使用的公式是Great circle distance formula:
设φ 1 ,λ 1 和φ 1 ,λ 2 为地理纬度和经度两点1和2,Δφ,Δλ它们的绝对差值;然后Δσ,它们之间的中心角,由余弦的球面定律给出:
Δσ = arccos ( sin φ1 ∙ sin φ2 + cos φ1 ∙ cos φ2 ∙ cos (Δλ) )
。半径为 r 的球体的距离 d ,即弧长,以弧度给出的Δσ
d = r Δσ
。
现在,如果两个点相同,那么Δλ = 0
,然后是cos(Δλ) = cos(0) = 1
,第一个公式将缩减为:
Δσ = arccos (sin φ ∙ sin φ + cos φ ∙ cos φ)
。
arccos 的参数已成为Pythagorean trigonometric identity,因此等于1.
所以上面简化为:
Δσ = arccos (1)
。
domain of the arccosine是:-1≤x≤1,因此值为1时,我们位于域的边界。
由于1的值是几个浮点运算(正弦,余弦,乘法)的结果,因此可能会出现值 1,但类似于1.0000000000004。这会产生一个问题,因为该值超出范围来计算反余弦。数据库引擎对这种情况的反应不同:
SQL Server将引发异常:
发生了无效的浮点运算。
MySql只会将表达式计算为 null 。
不知何故,传递给反余弦的参数应该保持在-1≤x≤1的范围内。这样做的一种方法是将参数四舍五入到足以保持一定精度的小数位数,但小到足以使浮点运算引起的超出此范围的任何超出部分。
大多数数据库引擎都有一个 round 函数,可以提供第二个参数来指定要保留的位数,因此SQL看起来像这样(保持6位小数):
SELECT *
FROM karpool.ride
WHERE Acos(Round(
Sin(Radians(23.065079)) * Sin(Radians(to_lat)) +
Cos(Radians(23.065079)) * Cos(Radians(to_lat)) *
Cos(Radians(to_lon) - Radians(72.511478)),
6
)) * 6371 <= 10;
或者,您可以使用某些数据库引擎提供的 great 和 least 函数将任何多余的值转换为1(或-1):
SELECT *
FROM karpool.ride
WHERE Acos(Greatest(Least(
Sin(Radians(23.065079)) * Sin(Radians(to_lat)) +
Cos(Radians(23.065079)) * Cos(Radians(to_lat)) *
Cos(Radians(to_lon) - Radians(72.511478)),
1), -1)
) * 6371 <= 10;
请注意,SQL Server不提供最大/最小功能。要解决此问题的问题有several answers。