我正在重新设计一个客户数据库,我想要存储的一条新信息以及标准地址字段(街道,城市等)是地址的地理位置。我想到的唯一用例是允许用户在无法找到地址的情况下映射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
吗?
geography
进行近距离搜索的功能,这非常酷。
另一方面,快速测试表明,使用select
时,获得经纬度的简单geography
会明显变慢(详情如下)。 ,geography
对geography
上另一个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}}
答案 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)。
答案 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