Зачем использовать тип данных SQL Server 2008 geography?
я перепроектирую базу данных клиентов и одну из новых частей информации, которую я хотел бы хранить вместе со стандартными полями адресов (улица, город и т. д.)- географическое расположение адреса. Единственный вариант использования, который я имею в виду, - это разрешить пользователям отображать координаты на картах 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
у меня хитрый:
@SaphuA добро пожаловать. В качестве побочного Примечания будьте очень осторожны с использованием пространственный индекс на ОБНУЛЯЕМОЙ географии столбец типа данных. Есть некоторые серьезная проблема с производительностью, поэтому сделайте этот столбец географии ненулевым даже если вам придется переделывать схему. - Томас 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)
наконец, выполнив следующий код показывает, что на моей машине, выбор широты и долготы примерно в 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
результаты:
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
3 ответа:
Если вы планируете выполнять какие-либо пространственные вычисления, 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(); }
тогда есть очень хорошая причина, чтобы использовать географию.
объяснение пространственного в рамках Entity Framework.
Обновлено Создание Высокопроизводительных Пространственных Баз Данных
как я заметил на Ноэль Абрахамс Ответ:
примечание о пространстве, каждая координата хранится в виде число с плавающей запятой двойной точности, которое имеет длину 64 бита (8 байт), и 8-байтовое двоичное значение примерно эквивалентно 15 цифрам десятичной точности, поэтому сравнение десятичной(9,6), которая составляет всего 5 байт, не совсем справедливое сравнение. Decimal должен быть как минимум десятичным (15,12) (9 байт) для каждого LatLong (всего 18 байт) для реального сравнения.
Так что сравнение типов хранения:
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
В тип данных geography занимает на 30% больше места.
кроме того, тип данных geography не ограничивается только хранением точки, вы также можете хранить линейные, экземпляров circularstring, экземпляр compoundcurve, многоугольник, экземпляр curvepolygon, коллекции geometrycollection, функцию Multipoint, multilinestring, и multipolygon и больше. Любая попытка сохранить даже самый простой из типов географии (как Lat/Long) за пределами точки(например, LINESTRING (1 1, 2 2) экземпляр) приведет к дополнительным строкам для каждой точки, a столбец для последовательности для порядка каждой точки и другой столбец для группировки строк. SQL Server также имеет методы для типов данных Geography, которые включают вычисление площадь, границы, длина, расстояния и многое другое.
кажется неразумным хранить широту и долготу как десятичные в Sql Server.
обновление 2
Если вы планируете делать какие-либо расчеты, такие как расстояние, площадь и т. д., Правильно вычисляя их по поверхности на земле это трудно. Каждый тип географии, хранящийся в SQL Server, также хранится с идентификатор пространственной ссылки. Эти идентификаторы могут быть разных сфер (земля 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'
результат:
name rows data Geo 20000 728 KB LatLon 20000 400 KB
тип данных географии занимает почти в два раза больше места.
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