如何通过 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 BY
与 TOP
一起使用,您的结果是未定义的。
@Clockwork-Muse 谢谢老兄。在这种情况下,不应该需要 order by - 我只是检查是否存在至少 1 个匹配项。当至少有 1 个匹配项时,它将返回一个 1 的单行,否则不返回任何内容。我怀疑 SQL 可能无论如何都会在后台进行优化,所以我的前 1 个可能不需要,但它没有害处/可能有助于某些 dbs 的性能。
@John - 只记得我有一个额外的复杂性,即 FROM_LOC 和 TO_LOC 在返回与 GEOMILES 的距离的上下文中是可互换的。我能把它放进去吗?【参考方案2】:
检查您的 WHERE
子句,您似乎正在寻找距离起始 AND 两个位置 20 英里范围内的所有代码 - 您已经指定为相距 220 英里。
此外,您将显式 JOIN
s 与隐式 JOIN
s 混合在一起(逗号分隔的 FROM
子句)。请明确限定所有联接;除此之外,G
和 E
应该如何相互关联?
【讨论】:
C.lat C.lng
获取起始位置的坐标,D.lat D.lng
获取结束位置的坐标。 G
和 E
不相关,但每个 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 为空,要在 lat/lng 数据库字段中添加啥?