如何通过 SQL 连接选择使用 lat/lng 半径

Posted

技术标签:

【中文标题】如何通过 SQL 连接选择使用 lat/lng 半径【英文标题】:How to select using lat/lng radius with a SQL join 【发布时间】:2012-10-18 22:11:42 【问题描述】:

我有一个SQL function,它使用它们的纬度/经度坐标计算两点之间的距离。使用此功能,我可以根据它们与给定点的距离从一个大集合(GEOCODES 表)中选出一组位置,如下所示:

DECLARE @LAT1 float, @LNG1 float,
SET @LAT1 = '34.169540'
SET @LNG1 = '-92.590990'

SELECT E.* FROM GEOCODES E
WHERE dbo.fnGetDistance(E.lat, E.lng, @LAT1, @LNG1, 'miles') < '20'

我有第二个表格 (GEOMILES),表格中的点之间的距离:

LOCATION1  | STATE1  |  LOCATION2  |  STATE2  | DISTANCE
-----------+---------+-------------+----------+---------
New York   |   NY    |    Boston   |   MA     |   220

我想要实现的是 SELECT 查询,如果出发点在纽约 20 英里范围内,并且到达点在波士顿 20 英里范围内,则返回距离 220。到目前为止,我有这个,但是当我通过手动检查 lat/lng 点等加起来知道时,它什么也没返回。非常感谢任何帮助!

DECLARE @FROM_LOC VARCHAR(100), @FROM_STA VARCHAR(2), @TO_LOC VARCHAR(100), @TO_STA VARCHAR(2)

SET @FROM_LOC = 'NEWARK'
SET @FROM_STA = 'NJ'

SET @TO_LOC = 'MEDFORD'
SET @TO_STA = 'MA'      

SELECT G.DIST FROM GEOMILES G, GEOCODES E
JOIN GEOCODES C ON C.asciiname = @FROM_LOC AND C.admin1 = @FROM_STA
JOIN GEOCODES D ON D.asciiname = @TO_LOC AND D.admin1 = @TO_STA
WHERE dbo.fnGetDistance(E.lat, E.lng, C.lat, C.lng, 'miles') < '20'
AND dbo.fnGetDistance(E.lat, E.lng, D.lat, D.lng, 'miles') < '20'

【问题讨论】:

您使用的是 SQL Server 吗?如果是这样,您可以使用 GEOGRAPHY 数据类型,还是无法更改表定义? blog.sqlauthority.com/2011/08/18/… 我正在使用 SQL 服务器,我可以更改表。你知道使用 STDistance() 方法是否会提高性能吗? 不确定,因为我没有看到你的函数的代码 - 玩一下比较一下。我建议的原因通常是最好使用现有功能而不是滚动您自己的功能,因为它已经为您编码、测试和优化,其他 sql 人员已经熟悉它,它会随着您的数据库升级。 【参考方案1】:

我认为这就是您所追求的(使用您的表结构)。 . .

SELECT G.DIST
FROM GEOMILES G
, GEOCODES F --from airport
, GEOCODES E --to airport
, GEOCODES C --from location
, GEOCODES D --to location

WHERE C.asciiname = @FROM_LOC 
AND C.admin1 = @FROM_STA

AND D.asciiname = @TO_LOC 
AND D.admin1 = @TO_STA

AND dbo.fnGetDistance(F.lat, F.lng, C.lat, C.lng, 'miles') < 20
AND dbo.fnGetDistance(E.lat, E.lng, D.lat, D.lng, 'miles') < 20

and G.Location1 = F.asciiname
and G.Location2 = E.asciiname
and G.State1 = F.admin1
and G.State2 = E.admin1

替代版本(我认为这会更好)

SELECT G.DIST
FROM GEOMILES G
inner join
(
    select fromAirport.asciiname
    , fromAirport.admin1
    from GeoCodes fromAirport
    where exists
    (
        select top 1 1
        from GeoCodes fromLocation
        where fromLocation.asciiname = @FROM_LOC
        and fromLocation.admin1 = @FROM_STA
        and dbo.fnGetDistance(fromLocation.lat, fromLocation.lng, fromAirport.lat, fromAirport.lng, 'miles') < 20
    )
) fromA
    on G.Location1 = fromA.asciiname
    and G.State1 = fromA.admin1
inner join
(
    select toAirport.asciiname
    , toAirport.admin1
    from GeoCodes toAirport
    where exists
    (
        select top 1 1
        from GeoCodes toLocation
        where toLocation.asciiname = @TO_LOC
        and toLocation.admin1 = @TO_STA
        and dbo.fnGetDistance(toLocation.lat, toLocation.lng, toAirport.lat, toAirport.lng, 'miles') < 20
    )
) toA
    on G.Location1 = toA.asciiname
    and G.State1 = toA.admin1

【讨论】:

是的,这个有效,谢谢。我也在玩原生的 GEOGRAPHY 类型,因为我的性能受到了很大的影响。顺便说一句,该功能位于问题第一行的链接中。 alt 版本效果更好,从 5 秒缩短到 1 秒。再次感谢。 看起来不错(而且比我有时间更彻底),但如果没有 ORDER BYTOP 一起使用,您的结果是未定义的。 @Clockwork-Muse 谢谢老兄。在这种情况下,不应该需要 order by - 我只是检查是否存在至少 1 个匹配项。当至少有 1 个匹配项时,它将返回一个 1 的单行,否则不返回任何内容。我怀疑 SQL 可能无论如何都会在后台进行优化,所以我的前 1 个可能不需要,但它没有害处/可能有助于某些 dbs 的性能。 @John - 只记得我有一个额外的复杂性,即 FROM_LOC 和 TO_LOC 在返回与 GEOMILES 的距离的上下文中是可互换的。我能把它放进去吗?【参考方案2】:

检查您的 WHERE 子句,您似乎正在寻找距离起始 AND 两个位置 20 英里范围内的所有代码 - 您已经指定为相距 220 英里。

此外,您将显式 JOINs 与隐式 JOINs 混合在一起(逗号分隔的 FROM 子句)。请明确限定所有联接;除此之外,GE 应该如何相互关联?

【讨论】:

C.lat C.lng 获取起始位置的坐标,D.lat D.lng 获取结束位置的坐标。 GE 不相关,但每个 fnGetDistance 调用都必须在 GEOCODES 中查找,这就是我隐式添加 E 的原因。这就是造成麻烦的原因吗? 嗯,事实上你在函数中都使用了E,看看@JohnLBevan 的回答,然后比较fnGetDistance() 的使用方式。【参考方案3】:

这是我对解决方案的尝试 - 我在这里使用 GEOGRAPHY 来保存表格脚本,但希望您可以根据需要轻松调整它 - 如果没有,请告诉我,我会适应:

--setup
    declare @OneMileInMeters int = 1609

    declare @t table
    (
        id bigint not null identity(1,1) primary key clustered
        , name nvarchar(64)
        , lat float
        , long float
        , geo geography null
    )

    insert @t
          select 'Seattle', 47.455, -122.2310, null
    union select 'Boston',  42.372,  -71.0298, null
    union select 'Chicago', 41.953,  -87.6430, null
    union select 'Spokane', 47.668, -117.5290, null
    union select 'Philly',  39.888,  -75.2510, null
    union select 'NY',      40.7142, -74.0064, null
    union select 'Newark',  40.7356, -74.1728, null
    union select 'Medford', 42.4183, -71.1067, null

    update @t
    set geo = geography::Point(lat, long, 4326)

--query
    declare @fromLoc nvarchar(64) = 'Newark'
    , @toLoc nvarchar(64) = 'Medford'
    , @maxDistanceToAirport int = 20 * @OneMileInMeters


    select f2.name AirportFrom, t2.name AirportTo, f2.geo.STDistance(t2.geo) / @OneMileInMeters FlightDistance
    from @t f
    , @t t
    , @t f2
    , @t t2
    where f.name = @fromLoc
    and t.name = @toLoc
    and f.geo.STDistance(f2.geo) < @maxDistanceToAirport    
    and t.geo.STDistance(t2.geo) < @maxDistanceToAirport
    and t.id != t2.id
    and f.id != f2.id

【讨论】:

以上是关于如何通过 SQL 连接选择使用 lat/lng 半径的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Google MAP API 通过 lat、lng 找到最近的城市

我如何知道一个 Lat,Lng 点是不是包含在一个圆圈内?

sql geolocation lat,lng

如果 lat/lng 为空,要在 lat/lng 数据库字段中添加啥?

从 PHP 变量中的 GeoLocation 访问用户 lat/lng

使用空间点类型在 MySQL 中存储 Lat Lng 值