it-swarm.cn

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

我正在重新设计一个客户数据库,我想要存储的一条新信息以及标准地址字段(街道,城市等)是地址的地理位置。我想到的唯一用例是允许用户在无法找到地址时映射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来获取纬度和经度会明显变慢(详情如下)。 ,以及对geography的另一个SO问题的 接受的答案 的评论让我很谨慎:

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

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


我跑的测试细节:

我创建了两个表,一个使用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时,选择纬度和经度的速度大约慢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

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

GeographyDuration LatLongDuration
----------------- ---------------
1607              948
1610              946
1607              947

AvgGeographyDuration AvgLatLongDuration
-------------------- ------------------
1608                 947
104
Jeff Ogata

如果您计划进行任何空间计算,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();
}

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

实体框架内的空间说明

更新了 创建高性能空间数据库

正如我在 Noel Abrahams回答

关于空间的注释,每个坐标存储为长度为64位(8字节)的双精度浮点数,8字节二进制值大致相当于15位小数精度,因此比较小数(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和MultiPolygon等 。任何尝试将最简单的Geography类型(如Lat/Long)存储到Point之外(例如LINESTRING(1 1,2 2)实例)将为每个点产生额外的行,每个点的顺序排序列和另一列用于分组行。 SQL Server还有地理数据类型的方法,包括计算 面积,边界,长度,距离等

在Sql Server中将Latitude和Longitude存储为Decimal似乎是不明智的。

更新2

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

enter image description here

65
Erik Philips

另一件需要考虑的事情是每种方法占用的存储空间。地理类型存储为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

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

6
Noel Abrahams