Навіщо використовувати тип даних географії SQL Server 2008?


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(деталі нижче). , і коментар до прийнятої відповіді на інше запитання про те, geographyщо мені цікаво:

@SaphuA Вітаємо вас. Як сторонне позначення слід ДУЖЕ дбайливо використовувати просторовий індекс у незмінній стовпці типу GEOGRAPHY. Існує декілька серйозних проблем із продуктивністю, тому зробіть, що стовпець GEOGRAPHY не буде нульовим, навіть якщо вам доведеться переробити свою схему. - Томаш 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
Я думаю зробити те й інше, збережіть Lat і Lon у своїх власних стовпцях і маю інший стовпець для об’єкта Geography, тож якщо мені просто потрібен Lat / Lon, я беру їх із стовпців, і якщо мені потрібен пошук близькості, я скористаюсь географією. Це мудро? Чи є якісь недоліки (крім того, що це займає більше місця ...)?
Ювал А.

@YuvalA. це, безумовно, звучить розумно, і може бути хорошим компромісом. Єдине занепокоєння, яке у мене виникає у верхній частині голови, полягає в тому, чи мати колонка "Географія" в таблиці впливає на запити до таблиці - я не маю цього досвіду, тому вам потрібно буде перевірити, щоб перевірити.
Джефф Огата

1
Чому ви постійно оновлювали своє запитання новими питаннями, а не новими?
Чад

@Chad не впевнений, що ти маєш на увазі. Я оновлював тему питання один раз, і більше запитань не було.
Джефф Огата

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 байт, не є справедливим порівнянням. Десяткові повинні мати мінімальний десятковий (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

Тип даних про географію займає на 30% більше місця.

Крім того, тип даних географії не обмежується лише збереженням точки, ви також можете зберігати LineString, CircularString, CompoundCurve, Polygon, CurvePolygon, GeometryCollection, MultiPoint, MultiLineString та MultiPolygon тощо . Будь-яка спроба зберігання навіть найпростіших типів географії (як Lat / Long) поза точкою (наприклад, екземпляр LINESTRING (1 1, 2 2)) матиме додаткові рядки для кожної точки, стовпець для послідовності послідовності для порядку кожної точки та інший стовпець для групування ліній. SQL Server також має методи для типів даних географії, які включають обчислення площі, межі, довжини, відстаней тощо .

Здається, нерозумно зберігати Latitude та Longitude як десятичну на сервері Sql.

Оновлення 2

Якщо ви плануєте робити будь-які обчислення, такі як відстань, площа тощо, правильно їх обчислити над землею складно. Кожен тип географії, що зберігається на SQL сервері, також зберігається з просторовим довідковим ідентифікатором . Ці ідентифікатори можуть бути різних сфер (Земля - ​​4326). Це означає, що обчислення в SQL Server насправді будуть правильно обчислюватися над землею (замість того, як мухи, які могли проходити через землю).

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


1
Щоб додати до цієї інформації, використання Geography по-справжньому розширює можливість пошукових запитів sql - це lat / long між іншими lat / longs (зазвичай це просто прямокутники), оскільки тип даних Geography дозволяє створювати декілька областей майже будь-якого розміру та форми.
Ерік Філіпс

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

Тип географічних даних займає майже вдвічі більше місця.


2
Зауважте про простір, кожна координата зберігається у вигляді подвійної точності з плаваючою комою, що має 64 біти (8 байт), а двійкове значення 8 байт приблизно еквівалентно 15 цифрам десяткової точності , тому порівнюючи десяткову (9 , 6), що становить лише 5 байт , не є справедливим порівнянням. Десяткові повинні мати мінімальний десятковий (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
Нові відповіді завжди вітаються, але будь ласка, додайте трохи контексту. Коротко пояснивши, як вищезазначене вирішує питання, робить відповідь кориснішою для інших.
Лі
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.