为什么要使用SQL Server 2008地理数据类型?

时间:2011-09-13 21:55:58

标签: sql-server-2008 geolocation geocoding

我正在重新设计一个客户数据库,我想要存储的一条新信息以及标准地址字段(街道,城市等)是地址的地理位置。我想到的唯一用例是允许用户在无法找到地址的情况下映射Google地图上的坐标,这通常发生在该地区是新开发的,或位于偏远/乡村地区。

我的第一个倾向是将纬度和经度存储为十进制值,但后来我记得SQL Server 2008 R2的数据类型为geography。我完全没有使用geography的经验,而且从我最初的研究来看,它看起来对我的方案来说太过分了。

例如,要使用存储为decimal(7,4)的纬度和经度,我可以这样做:

insert into Geotest(Latitude, Longitude) values (47.6475, -122.1393)
select Latitude, Longitude from Geotest

但是geography,我会这样做:

insert into Geotest(Geolocation) values (geography::Point(47.6475, -122.1393, 4326))
select Geolocation.Lat, Geolocation.Long from Geotest

虽然 更复杂但是如果我不需要增加复杂性呢?

在我放弃使用geography的想法之前,有什么我应该考虑的吗?使用空间索引搜索位置与使用纬度和经度字段编制索引会更快吗?使用我不知道的geography是否有优势?或者,另一方面,我应该知道哪些会阻止我使用geography吗?


更新

@Erik Philips提出了使用geography进行近距离搜索的功能,这非常酷。

另一方面,快速测试表明,使用select时,获得经纬度的简单geography会明显变慢(详情如下)。 ,geographygeography上另一个SO问题的accepted answer评论让我很谨慎:

  

@SaphuA欢迎你。作为旁注是非常小心使用   可空的GEOGRAPHY数据类型列上的空间索引。有一些   严重的性能问题,所以使GEOGRAPHY列不可空   即使您必须重新构建模式。 - 托马斯6月18日11:18

总而言之,权衡接近搜索的可能性与性能和复杂性之间的权衡,我决定在这种情况下放弃使用geography


我跑的测试详情:

我创建了两个表,一个使用decimal(9,6),另一个使用CREATE TABLE [dbo].[GeographyTest] ( [RowId] [int] IDENTITY(1,1) NOT NULL, [Location] [geography] NOT NULL, CONSTRAINT [PK_GeographyTest] PRIMARY KEY CLUSTERED ( [RowId] ASC ) ) CREATE TABLE [dbo].[LatLongTest] ( [RowId] [int] IDENTITY(1,1) NOT NULL, [Latitude] [decimal](9, 6) NULL, [Longitude] [decimal](9, 6) NULL, CONSTRAINT [PK_LatLongTest] PRIMARY KEY CLUSTERED ([RowId] ASC) ) 表示纬度和经度:

insert into GeographyTest(Location) values (geography::Point(47.6475, -122.1393, 4326))
insert into LatLongTest(Latitude, Longitude) values (47.6475, -122.1393)

并使用相同的纬度和经度值在每个表中插入一行:

geography

最后,运行以下代码表明,在我的机器上,使用declare @lat float, @long float, @d datetime2, @repCount int, @trialCount int, @geographyDuration int, @latlongDuration int, @trials int = 3, @reps int = 100000 create table #results ( GeographyDuration int, LatLongDuration int ) set @trialCount = 0 while @trialCount < @trials begin set @repCount = 0 set @d = sysdatetime() while @repCount < @reps begin select @lat = Location.Lat, @long = Location.Long from GeographyTest where RowId = 1 set @repCount = @repCount + 1 end set @geographyDuration = datediff(ms, @d, sysdatetime()) set @repCount = 0 set @d = sysdatetime() while @repCount < @reps begin select @lat = Latitude, @long = Longitude from LatLongTest where RowId = 1 set @repCount = @repCount + 1 end set @latlongDuration = datediff(ms, @d, sysdatetime()) insert into #results values(@geographyDuration, @latlongDuration) set @trialCount = @trialCount + 1 end select * from #results select avg(GeographyDuration) as AvgGeographyDuration, avg(LatLongDuration) as AvgLatLongDuration from #results drop table #results 时选择纬度和经度的速度大约慢5倍。

GeographyDuration LatLongDuration
----------------- ---------------
5146              1020
5143              1016
5169              1030

AvgGeographyDuration AvgLatLongDuration
-------------------- ------------------
5152                 1022

结果:

RowId = 2

更令人惊讶的是,即使没有选择任何行,例如选择geography哪个不存在,GeographyDuration LatLongDuration ----------------- --------------- 1607 948 1610 946 1607 947 AvgGeographyDuration AvgLatLongDuration -------------------- ------------------ 1608 947 仍然较慢:

{{1}}

3 个答案:

答案 0 :(得分:65)

如果您计划进行任何空间计算,EF 5.0允许使用LINQ表达式,如:

private Facility GetNearestFacilityToJobsite(DbGeography jobsite)
{   
    var q1 = from f in context.Facilities            
             let distance = f.Geocode.Distance(jobsite)
             where distance < 500 * 1609.344     
             orderby distance 
             select f;   
    return q1.FirstOrDefault();
}

然后有一个很好的理由使用地理。

Explanation of spatial within Entity Framework

已更新为Creating High Performance Spatial Databases

正如我在Noel Abrahams Answer上所说:

  

关于空间的注释,每个坐标存储为长度为64位(8字节)的双精度浮点数,而8字节二进制值大致相当于15位小数精度,因此比较a十进制(9,6)只有5个字节,不完全是公平的比较。对于每个LatLong(总共18个字节),十进制必须是最小十进制(15,12)(9字节)才能进行实际比较。

所以比较存储类型:

CREATE TABLE dbo.Geo
(    
geo geography
)
GO

CREATE TABLE dbo.LatLng
(    
    lat decimal(15, 12),   
    lng decimal(15, 12)
)
GO

INSERT dbo.Geo
SELECT geography::Point(12.3456789012345, 12.3456789012345, 4326) 
UNION ALL
SELECT geography::Point(87.6543210987654, 87.6543210987654, 4326) 

GO 10000

INSERT dbo.LatLng
SELECT  12.3456789012345, 12.3456789012345 
UNION
SELECT 87.6543210987654, 87.6543210987654

GO 10000

EXEC sp_spaceused 'dbo.Geo'

EXEC sp_spaceused 'dbo.LatLng'

<强>结果:

name    rows    data     
Geo     20000   728 KB   
LatLon  20000   560 KB

地理数据类型占用的空间增加了30%。

此外,geography数据类型不仅限于存储Point,还可以存储LineString, CircularString, CompoundCurve, Polygon, CurvePolygon, GeometryCollection, MultiPoint, MultiLineString, and MultiPolygon and more。任何尝试将最简单的Geography类型(如Lat / Long)存储到Point之外(例如LINESTRING(1 1,2 2)实例)将为每个点产生额外的行,每个点的顺序排序列和另一列用于分组行。 SQL Server还有地理数据类型的方法,包括计算Area, Boundary, Length, Distances, and more

在Sql Server中将纬度和经度存储为十进制似乎是不明智的。

更新2

如果您计划进行距离,面积等任何计算,那么在地球表面上正确计算这些计算是很困难的。存储在SQL Server中的每个Geography类型也存储为Spatial Reference ID。这些id可以是不同的领域(地球是4326)。这意味着SQL Server中的计算实际上将在地球表面上正确计算(而不是可以通过地球表面的as-the-crow-flies)。

enter image description here

答案 1 :(得分:6)

要考虑的另一件事是每种方法占用的存储空间。地理类型存储为VARBINARY(MAX)。尝试运行此脚本:

CREATE TABLE dbo.Geo
(
    geo geography

)

GO

CREATE TABLE dbo.LatLon
(
    lat decimal(9, 6)
,   lon decimal(9, 6)

)

GO

INSERT dbo.Geo
SELECT geography::Point(36.204824, 138.252924, 4326) UNION ALL
SELECT geography::Point(51.5220066, -0.0717512, 4326) 

GO 10000

INSERT dbo.LatLon
SELECT  36.204824, 138.252924 UNION
SELECT 51.5220066, -0.0717512

GO 10000

EXEC sp_spaceused 'dbo.Geo'
EXEC sp_spaceused 'dbo.LatLon'

<强>结果:

name    rows    data     
Geo     20000   728 KB   
LatLon  20000   400 KB

地理数据类型占用的空间几乎是其两倍。

答案 2 :(得分:-1)

    CREATE FUNCTION [dbo].[fn_GreatCircleDistance]
(@Latitude1 As Decimal(38, 19), @Longitude1 As Decimal(38, 19), 
            @Latitude2 As Decimal(38, 19), @Longitude2 As Decimal(38, 19), 
            @ValuesAsDecimalDegrees As bit = 1, 
            @ResultAsMiles As bit = 0)
RETURNS decimal(38,19)
AS
BEGIN
    -- Declare the return variable here
    DECLARE @ResultVar  decimal(38,19)

    -- Add the T-SQL statements to compute the return value here
/*
Credit for conversion algorithm to Chip Pearson
Web Page: www.cpearson.com/excel/latlong.aspx
Email: chip@cpearson.com
Phone: (816) 214-6957 USA Central Time (-6:00 UTC)
Between 9:00 AM and 7:00 PM

Ported to Transact SQL by Paul Burrows BCIS
*/
DECLARE  @C_RADIUS_EARTH_KM As Decimal(38, 19)
SET @C_RADIUS_EARTH_KM = 6370.97327862
DECLARE  @C_RADIUS_EARTH_MI As Decimal(38, 19)
SET @C_RADIUS_EARTH_MI = 3958.73926185
DECLARE  @C_PI As Decimal(38, 19)
SET @C_PI =  pi()

DECLARE @Lat1 As Decimal(38, 19)
DECLARE @Lat2 As Decimal(38, 19)
DECLARE @Long1 As Decimal(38, 19)
DECLARE @Long2 As Decimal(38, 19)
DECLARE @X As bigint
DECLARE @Delta As Decimal(38, 19)

If @ValuesAsDecimalDegrees = 1 
Begin
    set @X = 1
END
Else
Begin
    set @X = 24
End 

-- convert to decimal degrees
set @Lat1 = @Latitude1 * @X
set @Long1 = @Longitude1 * @X
set @Lat2 = @Latitude2 * @X
set @Long2 = @Longitude2 * @X

-- convert to radians: radians = (degrees/180) * PI
set @Lat1 = (@Lat1 / 180) * @C_PI
set @Lat2 = (@Lat2 / 180) * @C_PI
set @Long1 = (@Long1 / 180) * @C_PI
set @Long2 = (@Long2 / 180) * @C_PI

-- get the central spherical angle
set @Delta = ((2 * ASin(Sqrt((power(Sin((@Lat1 - @Lat2) / 2) ,2)) + 
    Cos(@Lat1) * Cos(@Lat2) * (power(Sin((@Long1 - @Long2) / 2) ,2))))))

If @ResultAsMiles = 1 
Begin
    set @ResultVar = @Delta * @C_RADIUS_EARTH_MI
End
Else
Begin
    set @ResultVar = @Delta * @C_RADIUS_EARTH_KM
End

    -- Return the result of the function
    RETURN @ResultVar

END