Зачем использовать тип данных SQL Server 2008 geography?

105

Я модифицирую базу данных клиентов, и одна из новых частей информации, которую я хотел бы сохранить вместе со стандартными полями адреса (улица, город и т. Д.), - это географическое расположение адреса. Единственный вариант использования, который я имею в виду, - это позволить пользователям отображать координаты на картах 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(подробности ниже). , и комментарий к принятому ответу на другой вопрос SO geographyвызывает у меня подозрение:

@SaphuA Пожалуйста. В качестве примечания будьте ОЧЕНЬ осторожны при использовании пространственного индекса для столбца типа данных GEOGRAPHY, допускающего значение NULL. Существует серьезная проблема с производительностью, поэтому сделайте столбец GEOGRAPHY не допускающим значения NULL, даже если вам придется переделывать схему. - Tomas 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
Джефф Огата
источник
4
Я подумываю сделать и то, и другое: сохранить широту и долготу в их собственных столбцах и создать еще один столбец для объекта географии, поэтому, если мне просто нужны широта и долгота, я беру их из столбцов, а если мне нужен поиск близости, я Воспользуюсь Географией. Это мудро? Есть ли недостатки (кроме того, что занимает больше места ...)?
Yuval A.
@YuvalA. это, безусловно, звучит разумно и может быть хорошим компромиссом. Единственное, что меня беспокоит, это то, влияет ли столбец «География» в таблице на запросы к таблице - у меня нет опыта в этом, поэтому вам нужно будет протестировать, чтобы проверить.
Джефф Огата,
1
Почему вы продолжали обновлять свой вопрос новыми вопросами вместо того, чтобы задавать новые вопросы?
Чад
@ Не понимаю, о чем ты. Я обновил текст вопроса один раз, и не для того, чтобы задавать больше вопросов.
Джефф Огата
6
Тем, кто задает этот вопрос, стоит отметить, что в SQL Server 2012 значительно повысилась производительность за счет пространственного индексирования. Также следует отметить тот факт, что, пока вы храните информацию о местоположении, вы можете добавить пространственную информацию позже, используя службу поиска для геокодирования ваших уже сохраненных адресов.
Volvox

Ответы:

66

Если вы планируете выполнять какие-либо пространственные вычисления, 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 должно быть минимум 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 не ограничивается только хранением Point, вы также можете хранить LineString, CircularString, CompoundCurve, Polygon, CurvePolygon, GeometryCollection, MultiPoint, MultiLineString и MultiPolygon и другие . Любая попытка сохранить даже простейшие типы географии (как широта / долгота) за пределами точки (например, экземпляр LINESTRING (1 1, 2 2)) приведет к появлению дополнительных строк для каждой точки, столбца для упорядочивания порядка каждой точки. и еще один столбец для группировки строк. SQL Server также имеет методы для типов данных Geography, которые включают вычисление площади, границы, длины, расстояний и т . Д.

Кажется неразумным хранить широту и долготу как десятичные в Sql Server.

Обновление 2

Если вы планируете выполнять какие-либо вычисления, такие как расстояние, площадь и т. Д., Правильно рассчитать их по поверхности земли будет сложно. Каждый тип Geography, хранящийся в SQL Server, также хранится с идентификатором пространственной привязки . Эти id могут быть разных сфер (земля 4326). Это означает, что вычисления в SQL Server на самом деле будут правильно рассчитывать по поверхности земли ( а не по прямой, которые могли бы проходить через поверхность земли).

введите описание изображения здесь

Эрик Филипс
источник
1
Чтобы добавить к этой информации, использование географии действительно расширяет возможности поиска в sql из широты и долготы между другими широтой и долготой (обычно это просто прямоугольники), потому что тип данных География позволяет вам создавать несколько областей практически любого размера и формы.
Эрик Филипс,
1
Спасибо еще раз. Я действительно спросил о причинах использования, geographyи вы привели несколько хороших. В конце концов, я решил просто использовать decimalполя в этом случае (см. Мое длинное обновление), но хорошо знать, что я могу использовать, geographyесли мне когда-либо понадобится что-то более интересное, чем просто отображение координат.
Джефф Огата,
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

Тип данных geography занимает почти вдвое больше места.

Ноэль Абрахамс
источник
2
Примечание о пробеле: каждая координата хранится как число с плавающей запятой двойной точности длиной 64 бита (8 байтов), а 8-байтовое двоичное значение примерно эквивалентно 15 цифрам десятичной точности , поэтому сравнение десятичного (9 , 6), который составляет всего 5 байтов , не совсем корректное сравнение. Для реального сравнения Decimal должно быть минимум Decimal (15,12) (9 байтов) для каждого LatLong (всего 18 байтов).
Эрик Филипс
9
@ErikPhilips, вопрос в том, зачем использовать десятичную дробь (15, 12), когда все, что вам нужно, это десятичная дробь (9, 6)? Приведенное выше сравнение носит практический характер, а не академическое упражнение.
Ноэль Абрахамс
-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
Пол Берроуз
источник
2
Всегда приветствуются новые ответы, но, пожалуйста, добавьте контекст. Краткое объяснение того, как вышеприведенное решение решает проблему, сделает ответ более полезным для других.
Ли