为什么使用SQL Server 2008地理数据types?

我正在重新devise一个客户数据库,我想存储的新信息之一与标准地址字段(街道,城市等)是地址的地理位置。 我唯一想到的使用案例就是允许用户在无法find地址的情况下映射Google地图上的坐标,当地区是新开发的,或者位于偏远/农村地区时经常发生这种情况。

我的第一个倾向是将经度和纬度存储为十进制值,但后来我记得SQL Server 2008 R2有一个geography数据types。 我绝对没有使用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的想法之前,我有什么需要考虑的吗? 使用空间索引search位置与索引纬度和经度字段是否会更快? 使用我不知道的geography有什么好处? 或者,另一方面,我应该知道哪些地方会阻止我使用geography


更新

@Erik飞利浦提供了使用geography进行近距离search的function,这非常酷。

另一方面,一个快速testing显示,使用geography时,简单的获取经纬度的select速度要慢得多(细节在下面)。 ,并对另一个关于geography问题的接受答案发表评论,让我有些兴奋:

@SaphuA不客气。 作为一个旁注非常谨慎使用可空的GEOGRAPHY数据types列上的空间索引。 有一些严重的性能问题,所以即使您必须重新构build您的模式,也要使GEOGRAPHY列不可为空。 – 托马斯6月18日11:18

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


我跑的testing细节:

我创build了两个表,一个使用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时select经度和纬度约慢5倍。

 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 

结果:

 GeographyDuration LatLongDuration ----------------- --------------- 5146 1020 5143 1016 5169 1030 AvgGeographyDuration AvgLatLongDuration -------------------- ------------------ 5152 1022 

更令人惊讶的是,即使没有select行,例如selectRowId = 2 (不存在), geography仍然比较慢:

 GeographyDuration LatLongDuration ----------------- --------------- 1607 948 1610 946 1607 947 AvgGeographyDuration AvgLatLongDuration -------------------- ------------------ 1608 947 

如果您打算做任何空间计算,EF 5.0允许LINQexpression式如下:

 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(); } 

那么有一个非常好的理由来使用地理。

entity framework内空间的解释 。

通过创build高性能空间数据库进行更新

正如我在Noel Abrahams上指出的那样:

关于空间的说明,每个坐标都是以64位(8字节)长的双精度浮点数存储的,而8字节的二进制值大致相当于十进制精度的15位,所以比较小数(9 ,6)只有5个字节,不完全是一个公平的比较。 十进制必须是每个LatLong(总共18个字节)的十进制(15,12)(9个字节)的最小值,以便进行真正的比较。

因此比较存储types:

 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 

地理数据types占用了30%的空间。

此外,geography数据types不仅限于存储Point,还可以存储LineString,CircularString,CompoundCurve,Polygon,CurvePolygon,GeometryCollection,MultiPoint,MultiLineString和MultiPolygon等 。 任何尝试将最简单的Geographytypes(如Lat / Long)存储在Point(例如LINESTRING(1 1,2 2)实例)之外的每个点都会产生额外的行,每个点的顺序sorting另一列用于分组线。 SQL Server也有地理数据types的方法,包括计算面积,边界,长度,距离等 。

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

更新2

如果你打算做如距离,面积等任何计算,在地球表面上正确地计算这些是困难的。 存储在SQL Server中的每个地理types也存储一个空间参考ID 。 这些身份证可以是不同的领域(地球是4326)。 这意味着SQL Server中的计算实际上可以在地球表面正确计算(而不是通过地球表面的乌鸦 )。

在这里输入图像说明

另外要考虑的是每种方法占用的存储空间。 地理types存储为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 

地理数据types占据了几乎两倍的空间。

  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