Jef*_*ata 104 geocoding geolocation 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
Run Code Online (Sandbox Code Playgroud)
但是geography
,我会这样做:
insert into Geotest(Geolocation) values (geography::Point(47.6475, -122.1393, 4326))
select Geolocation.Lat, Geolocation.Long from Geotest
Run Code Online (Sandbox Code Playgroud)
虽然它不是那么复杂,但如果我不需要增加复杂性呢?
在我放弃使用的想法之前geography
,有什么我应该考虑的吗?使用空间索引搜索位置与使用纬度和经度字段编制索引会更快吗?使用geography
我有什么优势我不知道?或者,另一方面,我应该知道哪些会阻止我使用geography
?
@Erik Philips提出了进行近距离搜索的能力geography
,非常酷.
另一方面,快速测试显示,使用select
时获得经纬度的简单程度明显较慢geography
(详情如下).并且对另一个SO问题的接受答案的评论geography
让我很谨慎:
@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)
)
Run Code Online (Sandbox Code Playgroud)
并在每个表中使用相同的纬度和经度值插入一行:
insert into GeographyTest(Location) values (geography::Point(47.6475, -122.1393, 4326))
insert into LatLongTest(Latitude, Longitude) values (47.6475, -122.1393)
Run Code Online (Sandbox Code Playgroud)
最后,运行以下代码表明,在我的机器上,使用时选择纬度和经度大约慢5倍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
Run Code Online (Sandbox Code Playgroud)
结果:
GeographyDuration LatLongDuration
----------------- ---------------
5146 1020
5143 1016
5169 1030
AvgGeographyDuration AvgLatLongDuration
-------------------- ------------------
5152 1022
Run Code Online (Sandbox Code Playgroud)
更令人惊讶的是,即使没有选择任何行,例如选择RowId = 2
不存在的geography
位置仍然较慢:
GeographyDuration LatLongDuration
----------------- ---------------
1607 948
1610 946
1607 947
AvgGeographyDuration AvgLatLongDuration
-------------------- ------------------
1608 947
Run Code Online (Sandbox Code Playgroud)
Eri*_*ips 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();
}
Run Code Online (Sandbox Code Playgroud)
然后有一个很好的理由使用地理.
更新了创建高性能空间数据库
正如我在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'
Run Code Online (Sandbox Code Playgroud)
结果:
name rows data
Geo 20000 728 KB
LatLon 20000 560 KB
Run Code Online (Sandbox Code Playgroud)
地理数据类型占用的空间增加了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类型也存储有空间参考ID.这些id可以是不同的领域(地球是4326).这意味着SQL Server中的计算实际上将在地球表面上正确计算(而不是可能穿过地球表面的那种蝇蝇).
另一件需要考虑的事情是每种方法占用的存储空间.地理类型存储为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'
Run Code Online (Sandbox Code Playgroud)
结果:
name rows data
Geo 20000 728 KB
LatLon 20000 400 KB
Run Code Online (Sandbox Code Playgroud)
地理数据类型占用的空间几乎是其两倍.