球面兩點最短距離 (大圓距離):
d = 2R × arcsin(√(sin²(Δφ/2) + cos φ1 × cos φ2 × sin²(Δλ/2)))
CREATE FUNCTION dbo.fn_GetDistance
(
@LatBegin FLOAT, -- 起點緯度
@LngBegin FLOAT, -- 起點經度
@LatEnd FLOAT, -- 終點緯度
@LngEnd FLOAT -- 終點經度
)
RETURNS FLOAT
AS
BEGIN
DECLARE @EarthRadius FLOAT = 6371.0; -- 公里,改 6371000 為米
DECLARE @RadLat1 FLOAT = RADIANS(@LatBegin);
DECLARE @RadLat2 FLOAT = RADIANS(@LatEnd);
DECLARE @RadLng1 FLOAT = RADIANS(@LngBegin);
DECLARE @RadLng2 FLOAT = RADIANS(@LngEnd);
DECLARE @DLat FLOAT = @RadLat1 - @RadLat2;
DECLARE @DLng FLOAT = @RadLng1 - @RadLng2;
DECLARE @A FLOAT = SIN(@DLat/2) * SIN(@DLat/2)
+ COS(@RadLat1) * COS(@RadLat2)
* SIN(@DLng/2) * SIN(@DLng/2);
DECLARE @C FLOAT = 2 * ATN2(SQRT(@A), SQRT(1-@A)); -- 2*asin(sqrt(a)) 等價
-- SQL Server 無 ASIN,用 ATN2(x, sqrt(1-x^2)) 替代
RETURN @EarthRadius * @C; -- 公里
END;
GO
-- 台北車站 (25.0478, 121.5170) → 高雄車站 (22.6300, 120.3000)
SELECT dbo.fn_GetDistance(25.0478, 121.5170, 22.6300, 120.3000) AS Distance_KM;
-- 結果約 340 公里
-- 就近查詢:找 5 公里內門市
DECLARE @MyLat FLOAT = 25.0478, @MyLng FLOAT = 121.5170;
SELECT *, dbo.fn_GetDistance(@MyLat, @MyLng, Lat, Lng) AS Dist_KM
FROM Stores
WHERE dbo.fn_GetDistance(@MyLat, @MyLng, Lat, Lng) <= 5
ORDER BY Dist_KM;
geography 型別 + STDistance 原生支援空間索引
ALTER TABLE Stores ADD GeoLoc AS geography::Point(Lat, Lng, 4326) PERSISTED;
CREATE SPATIAL INDEX idx_geo ON Stores(GeoLoc);
SELECT * FROM Stores WHERE GeoLoc.STDistance(geography::Point(@MyLat, @MyLng, 4326)) <= 5000; -- 米
WHERE Lat BETWEEN @MyLat - 0.05 AND @MyLat + 0.05
AND Lng BETWEEN @MyLng - 0.05 AND @MyLng + 0.05
AND dbo.fn_GetDistance(...) <= 5
| 方法 | 精度 | 效能 | 支援索引 |
|---|---|---|---|
| Haversine 標量函數 | 高 (米級) | 低 | ❌ |
| geography STDistance | 極高 (毫米級) | 中 (有索引時極快) | ✅ 空間索引 |
| 平面近似 (小範圍) | 中 (百米級) | 極高 | ✅ B-Tree |