Neden SQL Server 2008 coğrafya veri türünü kullanmalı?


105

Bir müşteri veri tabanını yeniden tasarlıyorum ve standart adres alanları (Cadde, Şehir vb.) İle birlikte saklamak istediğim yeni bilgilerden biri de adresin coğrafi konumu. Aklımdaki tek kullanım durumu, kullanıcıların adres başka türlü bulunamadığında Google haritalarındaki koordinatları haritalamasına izin vermektir, bu genellikle alan yeni geliştirildiğinde veya uzak / kırsal bir konumda olduğunda olur.

İlk eğilimim enlem ve boylamı ondalık değerler olarak saklamaktı, ancak daha sonra SQL Server 2008 R2'nin bir geographyveri türü olduğunu hatırladım . Kullanma konusunda kesinlikle hiç deneyimim yok geographyve ilk araştırmamdan senaryom için fazla abartılı görünüyor.

Örneğin, depolanan enlem ve boylamla çalışmak için decimal(7,4)şunu yapabilirim:

insert into Geotest(Latitude, Longitude) values (47.6475, -122.1393)
select Latitude, Longitude from Geotest

ama ile geographybunu yapardım:

insert into Geotest(Geolocation) values (geography::Point(47.6475, -122.1393, 4326))
select Geolocation.Lat, Geolocation.Long from Geotest

O olmasa o ben yoksa çok daha neden eklenti karmaşıklığı karmaşık?

Kullanım fikrinden vazgeçmeden önce, düşünmem geographygereken bir şey var mı? Enlem ve Boylam alanlarını endekslemeye kıyasla uzamsal dizin kullanarak bir konumu aramak daha mı hızlı olur? Farkında olmadığım kullanımın avantajları var geographymı? Ya da kapak tarafında, beni kullanmaktan vazgeçirecek bilmem gereken uyarılar var geographymı?


Güncelleme

@Erik Philips geography, çok güzel olan yakınlık aramaları yapma yeteneğini gündeme getirdi .

Öte yandan, hızlı bir test, selectenlem ve boylamı elde etmenin basit , kullanılırken önemli ölçüde daha yavaş olduğunu gösteriyor geography(ayrıntılar aşağıdadır). ve başka bir SO sorusunun kabul edilen yanıtıyla ilgili bir yorum geographybeni meraklandırdı:

@SaphuA Rica ederim. Bir yan not olarak, boş değer atanabilir bir GEOGRAPHY veri türü sütununda uzamsal bir dizin kullanmaya ÇOK dikkatli olun. Bazı ciddi performans sorunları var, bu nedenle şemanızı yeniden modellemeniz gerekse bile GEOGRAPHY sütununu null yapılamaz hale getirin. - Tomas 18 Haziran, 11:18

Sonuç olarak, yakınlık aramaları yapma olasılığı ile performans ve karmaşıklık arasındaki değiş tokuşu tartarak, geographybu durumda kullanımından vazgeçmeye karar verdim .


Yaptığım testin detayları:

Biri enlem ve boylam için kullanan geographyve diğeri kullanan iki tablo oluşturdum 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)
) 

ve her tabloya aynı enlem ve boylam değerlerini kullanarak tek bir satır ekledi:

insert into GeographyTest(Location) values (geography::Point(47.6475, -122.1393, 4326))
insert into LatLongTest(Latitude, Longitude) values (47.6475, -122.1393)

Son olarak, aşağıdaki kodu çalıştırmak, makinemde enlem ve boylamı seçmenin, kullanırken yaklaşık 5 kat daha yavaş olduğunu gösteriyor 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

Sonuçlar:

GeographyDuration LatLongDuration
----------------- ---------------
5146              1020
5143              1016
5169              1030

AvgGeographyDuration AvgLatLongDuration
-------------------- ------------------
5152                 1022

Daha şaşırtıcı olan ise, hiçbir satır seçilmediğinde bile, örneğin RowId = 2var olmayan yeri seçmek gibi geography, hala daha yavaştı:

GeographyDuration LatLongDuration
----------------- ---------------
1607              948
1610              946
1607              947

AvgGeographyDuration AvgLatLongDuration
-------------------- ------------------
1608                 947

4
Her ikisini de yapmayı düşünüyorum, Lat ve Lon'u kendi sütunlarına kaydetmeyi ve bir Coğrafya nesnesi için başka bir sütun oluşturmayı düşünüyorum, bu nedenle yalnızca Enlem / Boylam'a ihtiyacım varsa bunları sütunlardan alıyorum ve yakınlık aramasına ihtiyacım varsa Coğrafyayı kullanacağım. Bu akıllıca mı? Herhangi bir dezavantaj var mı (daha fazla yer kaplaması dışında ...)?
Yuval A.

@YuvalA. bu kesinlikle mantıklı geliyor ve iyi bir uzlaşma olabilir. Aklıma gelen tek endişe, tablodaki Coğrafya sütununun tabloya karşı sorgular üzerinde herhangi bir etkisinin olup olmadığıdır - bu konuda hiçbir deneyimim yok, bu yüzden doğrulamak için test etmeniz gerekir.
Jeff Ogata

1
Neden yeni sorular sormak yerine sorunuzu yeni sorularla güncelleyip durdunuz?
Chad

@Chad ne demek istediğinden emin değilim. Sorunun gövdesini bir kez güncelledim ve daha fazla soru sormak değildi.
Jeff Ogata

6
Şimdi, bu soruyu bulanlar için, SQL Server 2012'nin uzamsal indeksleme ile önemli performans artışları içerdiğini belirtmek gerekir. Ayrıca, konum bilgilerini sakladığınız sürece, önceden depolanmış adreslerinizi coğrafi kodlamak için bir arama hizmeti kullanarak uzamsal bilgileri daha sonra ekleyebileceğiniz de not edilmelidir.
Volvox

Yanıtlar:


66

Herhangi bir uzamsal hesaplama yapmayı planlıyorsanız, EF 5.0 aşağıdaki gibi LINQ İfadelerine izin verir:

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();
}

O zaman Coğrafyayı kullanmak için çok iyi bir neden var.

Entity Framework içinde uzamsal açıklama .

Yüksek Performanslı Mekansal Veritabanları Oluşturarak Güncellenmiştir

Noel Abrahams Cevabında da belirttiğim gibi :

Uzay üzerine bir not, her koordinat, 64 bit (8 bayt) uzunluğunda çift duyarlıklı kayan noktalı sayı olarak saklanır ve 8 baytlık ikili değer, kabaca 15 basamaklı ondalık hassasiyete eşittir, bu nedenle ondalık sayı (9 , 6) sadece 5 bayttır, tam olarak adil bir karşılaştırma değildir. Gerçek bir karşılaştırma için ondalık, her LatLong (toplam 18 bayt) için minimum Ondalık (15,12) (9 bayt) olmalıdır.

Dolayısıyla, depolama türlerini karşılaştırmak:

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'

Sonuç:

name    rows    data     
Geo     20000   728 KB   
LatLon  20000   560 KB

Coğrafya veri türü% 30 daha fazla yer kaplar.

Ek olarak, coğrafya veri türü yalnızca bir Noktayı depolamakla sınırlı değildir; LineString, CircularString, CompoundCurve, Polygon, CurvePolygon, GeometryCollection, MultiPoint, MultiLineString ve MultiPolygon ve daha fazlasını da depolayabilirsiniz . En basit Coğrafya türlerini bile (Enlem / Boylam olarak) bir Nokta ötesinde (örneğin, LINESTRING (1 1, 2 2) örneği) depolamaya yönelik herhangi bir girişim, her nokta için ek satırlar, her noktanın sırası için bir sütun oluşturacaktır. ve çizgilerin gruplanması için başka bir sütun. SQL Server ayrıca Coğrafya veri türleri için Alan, Sınır, Uzunluk, Mesafeler ve daha fazlasını hesaplamayı içeren yöntemlere sahiptir .

Enlem ve Boylamı Sql Sunucusunda Ondalık olarak saklamak akıllıca görünmüyor.

Güncelleme 2

Mesafe, alan vb. Gibi hesaplamalar yapmayı planlıyorsanız, bunları dünya yüzeyinde doğru şekilde hesaplamak zordur. SQL Server'da depolanan her Coğrafya türü ayrıca bir Uzamsal Referans Kimliği ile depolanır . Bu kimlikler farklı alanlarda olabilir (dünya 4326'dır). Bu, SQL Server'daki hesaplamaların aslında dünya yüzeyi üzerinde doğru bir şekilde hesaplanacağı anlamına gelir (dünyanın yüzeyinden geçebilecek karga sinekleri yerine ).

görüntü açıklamasını buraya girin


1
Bu bilgilere ek olarak, Coğrafya kullanmak, diğer enlem / boylamlar (genellikle sadece dikdörtgenler) arasında enlem / boylamdan sql aramalarının yeteneğini gerçekten genişletir çünkü Coğrafya veri türü hemen hemen her boyutta ve şekilde birden çok bölge oluşturmanıza olanak tanır.
Erik Philips

1
Tekrar teşekkürler. Kullanmayı düşünmek için nedenler sordum geographyve bazı iyi nedenler sağladınız. Sonunda, decimalbu durumda yalnızca alanları kullanmaya karar verdim (uzun soluklu güncellememe bakın), ancak geographykoordinatları haritalamaktan daha ilginç bir şey yapmam gerekirse kullanabileceğimi bilmek güzel .
Jeff Ogata

6

Dikkate alınması gereken bir diğer husus, her yöntemin kapladığı depolama alanıdır. Coğrafya türü, bir VARBINARY(MAX). Bu komut dosyasını çalıştırmayı deneyin:

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'

Sonuç:

name    rows    data     
Geo     20000   728 KB   
LatLon  20000   400 KB

Coğrafya veri türü neredeyse iki kat daha fazla yer kaplar.


2
Uzay üzerine bir not, her koordinat, 64 bit (8 bayt) uzunluğunda çift duyarlıklı kayan noktalı sayı olarak saklanır ve 8 baytlık ikili değer, kabaca 15 basamaklı ondalık hassasiyete eşittir , bu nedenle ondalık sayı (9 , 6) sadece 5 bayttır , tam olarak adil bir karşılaştırma değildir. Gerçek bir karşılaştırma için ondalık, her LatLong (toplam 18 bayt) için minimum Ondalık (15,12) (9 bayt) olmalıdır.
Erik Philips

9
@ErikPhilips, ihtiyacınız olan tek şey bir ondalık (9, 6) iken neden ondalık (15, 12) kullanıyorsunuz? Yukarıdaki karşılaştırma pratiktir - akademik bir uygulama değildir.
Noel Abrahams

-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
Yeni yanıtlar her zaman memnuniyetle karşılanır, ancak lütfen biraz içerik ekleyin. Yukarıdakilerin sorunu nasıl çözdüğünü kısaca açıklamak, cevabı başkaları için daha yararlı hale getirir.
Leigh,
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.