Mengapa menggunakan tipe data geografi SQL Server 2008?

105

Saya mendesain ulang basis data pelanggan dan salah satu bagian informasi baru yang ingin saya simpan bersama dengan bidang alamat standar (Jalan, Kota, dll.) Adalah lokasi geografis dari alamat tersebut. Satu-satunya kasus penggunaan yang ada dalam pikiran saya adalah mengizinkan pengguna untuk memetakan koordinat di peta Google ketika alamatnya tidak dapat ditemukan, yang sering terjadi ketika area tersebut baru dikembangkan, atau berada di lokasi terpencil / pedesaan.

Kecenderungan pertama saya adalah menyimpan lintang dan bujur sebagai nilai desimal, tetapi kemudian saya ingat bahwa SQL Server 2008 R2 memiliki geographytipe data. Saya sama sekali tidak memiliki pengalaman menggunakan geography, dan dari penelitian awal saya, tampaknya skenario saya berlebihan.

Misalnya, untuk bekerja dengan garis lintang dan bujur yang disimpan sebagai decimal(7,4), saya dapat melakukan ini:

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

tetapi dengan geography, saya akan melakukan ini:

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

Meskipun tidak yang jauh lebih rumit, mengapa kompleksitas add jika saya tidak perlu?

Sebelum saya meninggalkan ide untuk menggunakan geography, apakah ada yang harus saya pertimbangkan? Apakah akan lebih cepat jika mencari lokasi menggunakan indeks spasial vs. mengindeks bidang Lintang dan Bujur? Apakah ada keuntungan menggunakan geographyyang tidak saya sadari? Atau, di sisi lain, apakah ada peringatan yang harus saya ketahui yang akan membuat saya enggan menggunakannya geography?


Memperbarui

@Erik Philips mengemukakan kemampuan untuk melakukan pencarian kedekatan geography, yang sangat keren.

Di sisi lain, uji cepat menunjukkan bahwa cara sederhana selectuntuk mendapatkan lintang dan bujur jauh lebih lambat saat menggunakan geography(detail di bawah). , dan komentar pada jawaban yang diterima untuk pertanyaan SO lainnya geographymembuat saya curiga:

@SaphuA Sama-sama. Sebagai catatan, berhati-hatilah saat menggunakan indeks spasial pada kolom tipe data GEOGRAFI yang tidak dapat diisi. Ada beberapa masalah kinerja yang serius, jadi buatlah kolom GEOGRAFI tersebut tidak dapat dinolkan meskipun Anda harus merombak skema Anda. - Tomas 18 Juni pukul 11:18

Secara keseluruhan, dengan mempertimbangkan kemungkinan melakukan pencarian kedekatan vs. kompromi dalam kinerja dan kompleksitas, saya telah memutuskan untuk mengabaikan penggunaan geographydalam kasus ini.


Rincian tes yang saya jalankan:

Saya membuat dua tabel, satu menggunakan geographydan menggunakan lainnya decimal(9,6)untuk garis lintang dan bujur:

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)
) 

dan menyisipkan satu baris menggunakan nilai garis lintang dan garis bujur yang sama ke dalam setiap tabel:

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

Akhirnya, menjalankan kode berikut menunjukkan bahwa, di komputer saya, memilih lintang dan bujur kira-kira 5 kali lebih lambat saat menggunakan 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

Hasil:

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

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

Yang lebih mengejutkan adalah meskipun tidak ada baris yang dipilih, misalnya memilih mana RowId = 2, yang tidak ada, geographymasih lebih lambat:

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

AvgGeographyDuration AvgLatLongDuration
-------------------- ------------------
1608                 947
Jeff Ogata
sumber
4
Saya berpikir untuk melakukan keduanya, menyimpan Lat dan Lon di kolomnya masing-masing, dan memiliki kolom lain untuk objek Geografi, jadi jika saya hanya memerlukan Lat / Lon, saya mengambilnya dari kolom, dan jika perlu pencarian kedekatan saya Akan menggunakan Geografi. Apakah ini bijaksana? Apakah ada kerugian (selain membutuhkan lebih banyak ruang ...)?
Yuval A.
@Yu. yang terdengar masuk akal, dan mungkin merupakan kompromi yang baik. Satu-satunya kekhawatiran yang saya miliki di luar kepala saya adalah apakah memiliki kolom Geografi di tabel berdampak pada kueri terhadap tabel - Saya tidak memiliki pengalaman dengan itu sehingga Anda perlu menguji untuk memverifikasi.
Jeff Ogata
1
Mengapa Anda terus memperbarui pertanyaan Anda dengan pertanyaan baru daripada mengajukan pertanyaan baru?
Chad
@Chad tidak yakin apa yang Anda maksud. Saya memperbarui isi pertanyaan sekali, dan itu bukan untuk mengajukan lebih banyak pertanyaan.
Jeff Ogata
6
Perlu dicatat, sekarang, bagi mereka yang menemukan pertanyaan ini, bahwa SQL Server 2012 menyertakan peningkatan kinerja yang signifikan dengan pengindeksan spasial. Yang juga perlu diperhatikan adalah fakta bahwa selama Anda menyimpan informasi lokasi, Anda dapat menambahkan informasi spasial nanti menggunakan layanan pencarian untuk geocode alamat Anda yang sudah tersimpan.
Volvox

Jawaban:

66

Jika Anda berencana melakukan komputasi spasial, EF 5.0 memungkinkan Ekspresi LINQ seperti:

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

Lalu ada alasan yang sangat bagus untuk menggunakan Geografi.

Penjelasan spasial dalam Kerangka Entitas .

Diperbarui dengan Membuat Database Spasial Berkinerja Tinggi

Seperti yang saya catat di Noel Abrahams Answer :

Catatan tentang ruang, setiap koordinat disimpan sebagai bilangan floating-point presisi ganda yang panjangnya 64 bit (8 byte), dan nilai biner 8-byte kira-kira setara dengan 15 digit presisi desimal, jadi bandingkan desimal (9 , 6) yang hanya 5 byte, bukanlah perbandingan yang adil. Desimal harus minimal Desimal (15,12) (9 byte) untuk setiap LatLong (total 18 byte) untuk perbandingan nyata.

Jadi membandingkan jenis penyimpanan:

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'

Hasil:

name    rows    data     
Geo     20000   728 KB   
LatLon  20000   560 KB

Jenis data geografi membutuhkan 30% lebih banyak ruang.

Selain itu, tipe data geografi tidak terbatas hanya untuk menyimpan Titik, Anda juga dapat menyimpan LineString, CircularString, CompoundCurve, Polygon, CurvePolygon, GeometryCollection, MultiPoint, MultiLineString, dan MultiPolygon, dan lainnya . Setiap upaya untuk menyimpan bahkan tipe Geografi yang paling sederhana (sebagai Lintang / Bujur) di luar Titik (misalnya LINESTRING (1 1, 2 2) contoh) akan menimbulkan baris tambahan untuk setiap titik, kolom untuk urutan urutan setiap titik dan kolom lain untuk pengelompokan garis. SQL Server juga memiliki metode untuk tipe data Geografi yang mencakup penghitungan Area, Batas, Panjang, Jarak, dan lainnya .

Tampaknya tidak bijaksana untuk menyimpan Garis Lintang dan Bujur sebagai Desimal di Server Sql.

Perbarui 2

Jika Anda berencana melakukan penghitungan seperti jarak, luas, dll., Sulit untuk menghitungnya dengan benar di permukaan bumi. Setiap tipe Geografi yang disimpan di SQL Server juga disimpan dengan ID Referensi Spasial . Id ini bisa dari berbagai bidang (bumi adalah 4326). Ini berarti bahwa kalkulasi di SQL Server akan benar-benar menghitung dengan benar di atas permukaan bumi (bukan sebagai burung gagak yang bisa menembus permukaan bumi).

masukkan deskripsi gambar di sini

Erik Philips
sumber
1
Untuk menambah informasi ini, menggunakan Geografi benar-benar memperluas kemampuan pencarian sql dari adalah lintang / bujur di antara lintang / bujur lainnya (biasanya hanya persegi panjang) karena tipe data Geografi memungkinkan Anda membuat beberapa wilayah dengan hampir semua ukuran dan bentuk.
Erik Philips
1
Terima kasih lagi. Saya memang meminta alasan untuk mempertimbangkan menggunakan geographydan Anda memberikan beberapa yang bagus. Pada akhirnya, saya memutuskan untuk hanya menggunakan decimalbidang dalam kasus ini (lihat pembaruan bertele-tele saya), tetapi ada baiknya mengetahui bahwa saya dapat menggunakan geographyjika saya perlu melakukan sesuatu yang lebih menarik daripada sekadar memetakan koordinat.
Jeff Ogata
6

Hal lain yang perlu dipertimbangkan adalah ruang penyimpanan yang digunakan oleh masing-masing metode. Jenis geografi disimpan sebagai a VARBINARY(MAX). Coba jalankan skrip ini:

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'

Hasil:

name    rows    data     
Geo     20000   728 KB   
LatLon  20000   400 KB

Jenis data geografi memakan ruang hampir dua kali lebih banyak.

Noel Abrahams
sumber
2
Catatan tentang ruang, setiap koordinat disimpan sebagai bilangan floating-point presisi ganda yang panjangnya 64 bit (8 byte), dan nilai biner 8-byte secara kasar setara dengan 15 digit presisi desimal , jadi bandingkan desimal (9 , 6) yang hanya 5 byte , bukanlah perbandingan yang adil. Desimal harus minimal Desimal (15,12) (9 byte) untuk setiap LatLong (total 18 byte) untuk perbandingan nyata.
Erik Philips
9
@ErikPhilips intinya adalah mengapa menggunakan desimal (15, 12) ketika yang Anda butuhkan hanyalah desimal (9, 6)? Perbandingan di atas adalah perbandingan praktis - bukan latihan akademis.
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: [email protected]
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
Paul Burrows
sumber
2
Jawaban baru selalu diterima, tetapi tambahkan beberapa konteks. Jelaskan secara singkat bagaimana cara di atas menyelesaikan masalah membuat jawaban lebih berguna bagi orang lain.
Leigh