Perché utilizzare il tipo di dati geography di SQL Server 2008?


105

Sto ridisegnando un database clienti e una delle nuove informazioni che vorrei memorizzare insieme ai campi dell'indirizzo standard (Via, Città, ecc.) È la posizione geografica dell'indirizzo. L'unico caso d'uso che ho in mente è quello di consentire agli utenti di mappare le coordinate su Google Maps quando l'indirizzo non può essere trovato altrimenti, cosa che spesso accade quando l'area è di nuova costruzione o si trova in una posizione remota / rurale.

La mia prima inclinazione era quella di memorizzare latitudine e longitudine come valori decimali, ma poi mi sono ricordato che SQL Server 2008 R2 ha un geographytipo di dati. Non ho assolutamente esperienza nell'uso geographye, dalla mia ricerca iniziale, sembra essere eccessivo per il mio scenario.

Ad esempio, per lavorare con latitudine e longitudine memorizzate come decimal(7,4), posso fare questo:

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

ma con geography, lo farei:

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

Anche se non è che molto più complicato, perché aggiungere complessità se io non devo?

Prima di abbandonare l'idea di utilizzare geography, c'è qualcosa che dovrei considerare? Sarebbe più veloce cercare una posizione utilizzando un indice spaziale anziché indicizzare i campi Latitudine e Longitudine? Ci sono vantaggi nell'utilizzo di geographycui non sono a conoscenza? Oppure, il rovescio della medaglia, ci sono avvertenze che dovrei sapere che mi scoraggerebbero dall'uso geography?


Aggiornare

@Erik Philips ha parlato della possibilità di eseguire ricerche di prossimità geography, il che è molto interessante.

D'altra parte, un test rapido mostra che un semplice selectper ottenere la latitudine e la longitudine è significativamente più lento quando si utilizza geography(dettagli di seguito). , e un commento sulla risposta accettata a un'altra domanda SO su geographymi ha diffidente:

@SaphuA Sei il benvenuto. Come nota a margine, sii MOLTO attento a utilizzare un indice spaziale su una colonna del tipo di dati GEOGRAPHY nullable. Ci sono alcuni seri problemi di prestazioni, quindi rendi quella colonna GEOGRAPHY non annullabile anche se devi rimodellare lo schema. - Tomas, 18 giugno alle 11:18

Tutto sommato, valutando la probabilità di fare ricerche di prossimità rispetto al compromesso in termini di prestazioni e complessità, ho deciso di rinunciare all'uso di geographyin questo caso.


Dettagli del test che ho eseguito:

Ho creato due tabelle, una usando geographye un'altra usando decimal(9,6)per latitudine e longitudine:

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

e inserito una singola riga utilizzando gli stessi valori di latitudine e longitudine in ciascuna tabella:

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

Infine, l'esecuzione del codice seguente mostra che, sulla mia macchina, la selezione della latitudine e della longitudine è circa 5 volte più lenta durante l'utilizzo 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

risultati:

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

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

Ciò che è stato più sorprendente è che anche quando non sono state selezionate righe, ad esempio la selezione di dove RowId = 2, che non esiste, è geographystata ancora più lenta:

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

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

4
Sto pensando di fare entrambe le cose, salvare Lat e Lon nelle loro colonne e avere un'altra colonna per un oggetto Geography, quindi se ho solo bisogno di Lat / Lon li prendo dalle colonne e se ho bisogno di ricerca di prossimità lo userò la geografia. È saggio? Ci sono degli svantaggi (oltre a quello che richiede più spazio ...)?
Yuval A.

@YuvalA. sembra certamente ragionevole e può essere un buon compromesso. L'unica preoccupazione che ho in testa è se avere la colonna Geografia nella tabella ha qualche impatto sulle query rispetto alla tabella: non ho esperienza con questo, quindi dovresti testare per verificare.
Jeff Ogata

1
Perché hai continuato ad aggiornare la tua domanda con nuove domande invece di fare nuove domande?
Ciad

@ Non ero sicuro di cosa intendi. Ho aggiornato una volta il corpo della domanda e non era per fare altre domande.
Jeff Ogata

6
Vale la pena notare, ora, per coloro che trovano questa domanda, che SQL Server 2012 include significativi aumenti delle prestazioni con l'indicizzazione spaziale. È inoltre degno di nota il fatto che finché si memorizzano le informazioni sulla posizione, è possibile aggiungere informazioni spaziali in un secondo momento utilizzando un servizio di ricerca per geocodificare gli indirizzi già memorizzati.
Volvox

Risposte:


66

Se prevedi di eseguire calcoli spaziali, EF 5.0 consente espressioni LINQ come:

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

Quindi c'è un'ottima ragione per usare Geografia.

Spiegazione dello spazio all'interno di Entity Framework .

Aggiornato con la creazione di database spaziali ad alte prestazioni

Come ho notato sulla risposta di Noel Abrahams :

Una nota sullo spazio, ogni coordinata viene memorizzata come un numero a virgola mobile a doppia precisione lungo 64 bit (8 byte) e il valore binario di 8 byte è approssimativamente equivalente a 15 cifre di precisione decimale, quindi confrontando un decimale (9 , 6) che è di soli 5 byte, non è esattamente un confronto equo. Decimal dovrebbe essere un minimo di Decimal (15,12) (9 byte) per ogni LatLong (totale di 18 byte) per un confronto reale.

Quindi confrontando i tipi di archiviazione:

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'

Risultato:

name    rows    data     
Geo     20000   728 KB   
LatLon  20000   560 KB

Il tipo di dati geography occupa il 30% di spazio in più.

Inoltre, il tipo di dati geografico non si limita alla sola memorizzazione di un punto, puoi anche memorizzare LineString, CircularString, CompoundCurve, Polygon, CurvePolygon, GeometryCollection, MultiPoint, MultiLineString e MultiPolygon e altro ancora . Qualsiasi tentativo di memorizzare anche il più semplice dei tipi di geografia (come Lat / Long) oltre un punto (ad esempio LINESTRING (1 1, 2 2) istanza) comporterà righe aggiuntive per ogni punto, una colonna per la sequenza per l'ordine di ogni punto e un'altra colonna per raggruppare le righe. SQL Server dispone anche di metodi per i tipi di dati geografici che includono il calcolo di area, confine, lunghezza, distanze e altro ancora .

Non sembra saggio memorizzare Latitude e Longitude come Decimal in Sql Server.

Aggiorna 2

Se prevedi di eseguire calcoli come distanza, area, ecc., Calcolarli correttamente sulla superficie terrestre è difficile. Ogni tipo di area geografica archiviato in SQL Server viene inoltre archiviato con un ID di riferimento spaziale . Questi ID possono essere di sfere diverse (la terra è 4326). Ciò significa che i calcoli in SQL Server verranno effettivamente calcolati correttamente sulla superficie della terra (invece che in linea d'aria che potrebbe essere attraverso la superficie della terra).

inserisci qui la descrizione dell'immagine


1
Per aggiungere a queste informazioni, l'utilizzo di Geografia espande davvero la capacità delle ricerche sql da è un lat / long tra altri lat / long (di solito solo rettangoli) perché il tipo di dati Geografia consente di creare più regioni di quasi qualsiasi dimensione e forma.
Erik Philips

1
grazie ancora. Ho chiesto dei motivi per considerare l'utilizzo geographye tu ne hai forniti alcuni buoni. Alla fine, ho deciso di utilizzare solo i decimalcampi in questo caso (vedere il mio aggiornamento prolisso), ma è bello sapere che posso usarlo geographyse mai avessi bisogno di fare qualcosa di più elaborato della semplice mappatura delle coordinate.
Jeff Ogata

6

Un'altra cosa da considerare è lo spazio di archiviazione occupato da ciascun metodo. Il tipo di geografia viene archiviato come file VARBINARY(MAX). Prova a eseguire questo script:

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'

Risultato:

name    rows    data     
Geo     20000   728 KB   
LatLon  20000   400 KB

Il tipo di dati geography occupa quasi il doppio dello spazio.


2
Una nota sullo spazio, ogni coordinata viene memorizzata come un numero a virgola mobile a doppia precisione lungo 64 bit (8 byte) e il valore binario di 8 byte è approssimativamente equivalente a 15 cifre di precisione decimale , quindi confrontando un decimale (9 , 6) che è di soli 5 byte , non è esattamente un confronto equo. Decimal dovrebbe essere un minimo di Decimal (15,12) (9 byte) per ogni LatLong (totale di 18 byte) per un confronto reale.
Erik Philips

9
@ErikPhilips il punto è perché usare un decimale (15, 12) quando tutto ciò di cui hai bisogno è un decimale (9, 6)? Il confronto sopra è pratico, non un esercizio accademico.
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
Nuove risposte sono sempre benvenute, ma aggiungi un po 'di contesto. Spiegare brevemente come quanto sopra risolve il problema rende la risposta più utile per gli altri.
Leigh
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.