从MSSQL数据库表中获取最近的经度和纬度?

时间:2013-07-09 14:41:06

标签: sql sql-server database tsql sql-server-2012

好的,我已经搜索了SO和谷歌但是还没有真正找到明确的答案,所以把它扔到那里为SO社区。

基本上我有一个经度和纬度表用于特定的兴趣点,sql查询将知道你当前在哪个位置(这将是表中的一个,作为参数传入),因此它然后我需要计算并返回一行中最接近的纬度和经度。

我要求所有这些都在MSSQL(2012 /存储过程)而不是在调用应用程序(这是.NET)中完成,因为我听说SQL通常比处理这样的查询要快得多吗?

编辑:

我找到了STDistance函数,它给出了位置之间的里程数,例如:

DECLARE @NWI geography, @EDI geography
SET @NWI = geography::Point(52.675833,1.282778,4326)
SET @EDI = geography::Point(55.95,-3.3725,4326)
SELECT @NWI.STDistance(@EDI) / 1000

但是我不想迭代表格中的所有纬度/经度,这肯定会对性能造成影响吗?

我也试过转换下面其中一条注释链接中指出的一个例子(这是MYSQL而不是MSSQL)但是我收到一个错误,代码如下:

DECLARE @orig_lat decimal(6,2), @orig_long decimal(6,2), @bounding_distance int
set @orig_lat=52.056736; 
set @orig_long=1.14822; 
set @bounding_distance=1;

SELECT *,((ACOS(SIN(@orig_lat * PI() / 180) * SIN('lat' * PI() / 180) + COS(@orig_lat * PI() / 180) * COS('lat' * PI() / 180) * COS((@orig_long - 'lon') * PI() / 180)) * 180 / PI()) * 60 * 1.1515) AS 'distance' 
FROM [DB1].[dbo].[LatLons]
WHERE
(
  'lat' BETWEEN (@orig_lat - @bounding_distance) AND (@orig_lat + @bounding_distance)
  AND 'lon' BETWEEN (@orig_long - @bounding_distance) AND (@orig_long + @bounding_distance)
)
ORDER BY 'distance' ASC

收到的错误是:

  

Msg 8114,Level 16,State 5,Line 6转换数据类型varchar时出错   数字。

任何人都能够制定出上述代码或提出更好的解决方案吗?

4 个答案:

答案 0 :(得分:8)

让我们看一个在SQL Server 2008(及更高版本)中使用STDistance函数的简单示例。

我要告诉SQL Server我在伦敦,我希望看到我的办公室离我们有多远。这是我希望SQL Server给我的结果:

Offices

首先,我们需要一些样本数据。我们将创建一个包含Microsoft办事处的几个位置的表格,我们将存储它们的经度和数据。 geography字段中的纬度值。

CREATE TABLE [Offices] (
    [Office_Id] [int] IDENTITY(1, 1) NOT NULL,
    [Office_Name] [nvarchar](200) NOT NULL,
    [Office_Location] [geography] NOT NULL,
    [Update_By] nvarchar(30) NULL,
    [Update_Time] [datetime]
) ON [PRIMARY]

GO

INSERT INTO [dbo].[Offices] VALUES ('Microsoft Zurich', 'POINT(8.590847 47.408860 )', 'mike', GetDate())
INSERT INTO [dbo].[Offices] VALUES ('Microsoft San Francisco', 'POINT(-122.403697 37.792062 )', 'mike', GetDate())
INSERT INTO [dbo].[Offices] VALUES ('Microsoft Paris', 'POINT(2.265509 48.833946)', 'mike', GetDate())
INSERT INTO [dbo].[Offices] VALUES ('Microsoft Sydney', 'POINT(151.138378 -33.796572)', 'mike', GetDate())
INSERT INTO [dbo].[Offices] VALUES ('Microsoft Dubai', 'POINT(55.286282 25.228850)', 'mike', GetDate())

现在,假设我们在伦敦。以下是如何从伦敦的经度和价值中提取geography价值的方法。纬度值:

DECLARE 
    @latitude numeric(12, 7),
    @longitude numeric(12, 7)

SET @latitude = 51.507351
SET @longitude = -0.127758

DECLARE @g geography = 'POINT(' + cast(@longitude as nvarchar) + ' ' + cast(@latitude as nvarchar) + ')';

最后,让我们看看我们每个办公室的距离。

SELECT [Office_Name], 
       cast([Office_Location].STDistance(@g) / 1609.344 as numeric(10, 1)) as 'Distance (in miles)' 
FROM [Offices]
ORDER BY 2 ASC

这给了我们希望的结果。

Results

显然,如果你只是想看到最近的办公室,你可以溜进TOP(1)

Top1results

很酷,嘿?

只有一个障碍。当您有大量geography点要比较时,即使您在该数据库字段上添加了一个空间索引,性能也不是很好。

我针对330,000 geography点的表格测试了一个点。使用此处显示的代码,它找到了大约 8秒的最近点。

当我修改表格以存储经度和纬度值,并使用[dbo].[fnCalcDistanceMiles]函数from this StackOverflow article时,它找到了最近的点 3秒

...然而

两点之间的所有距离"我在互联网上找到的样本使用了SQL Server STDistance函数,或涉及(CPU密集型)cos,sin和tan函数的数学公式。

更快的解决方案是回到高中,并记住毕达哥拉斯如何计算两点之间的距离。

假设我们想知道伦敦和巴黎之间的距离。

enter image description here

这是我的SQL Server功能:

CREATE FUNCTION [dbo].[uf_CalculateDistance] (@Lat1 decimal(8,4), @Long1 decimal(8,4), @Lat2 decimal(8,4), @Long2 decimal(8,4))
RETURNS decimal (8,4) AS
BEGIN
    DECLARE @d decimal(28,10)

    SET @d = sqrt(square(@Lat1-@Lat2) + square(@Long1-@Long2))

    RETURN @d
END

现在,请记住,此功能不会以英里,公里等方式返回值...它只是比较经度和数字。纬度值。毕达哥拉斯的意思是用于2D,而不是比较圆形行星上的点!

但是,在我的测试中,它找到了 1秒内的最近点,并产生了与使用SQL Server的STDistance函数相同的结果。

因此,请随意使用此功能比较相对距离,但如果您需要实际距离本身,请不要使用此功能。

希望这一切都有所帮助。

答案 1 :(得分:4)

几年前我写了一篇博客,解释了如何在不使用空间数据类型的情况下完成这项工作。由于看起来好像你有一张经度/纬度值表,这个博客可能会有很大的帮助。

SQL Server Zipcode Latitude Longitude Proximity Search

答案 2 :(得分:0)

由于这是SQL Server,只需将空索引放在表的Lat / Lon列上,然后使用WHERE子句中的.STDistance()函数对其进行查询。

Microsoft文档中的这篇文章解释了如何执行此操作:http://msdn.microsoft.com/en-us/library/ff929109.aspx。第二部分“最近邻查询.. ”似乎特别相关。

答案 3 :(得分:0)

这里我如何使用Mike的功能:

    SELECT TOP 1 [State], [City], [AddressID], sqrt(square(abs(la.Latitude-@lat)) + square(abs(la.Longitude-@lng))) as Distance
    FROM [LocationAddresses] la     
    ORDER BY Distance