Warum sollte der geografische Datentyp SQL Server 2008 verwendet werden?

105

Ich gestalte eine Kundendatenbank neu und eine der neuen Informationen, die ich zusammen mit den Standardadressfeldern (Straße, Stadt usw.) speichern möchte, ist der geografische Standort der Adresse. Der einzige Anwendungsfall, an den ich denke, besteht darin, Nutzern das Zuordnen der Koordinaten auf Google Maps zu ermöglichen, wenn die Adresse nicht anderweitig gefunden werden kann. Dies tritt häufig auf, wenn das Gebiet neu entwickelt wurde oder sich an einem abgelegenen / ländlichen Ort befindet.

Meine erste Neigung bestand darin, Breiten- und Längengrade als Dezimalwerte zu speichern, aber dann fiel mir ein, dass SQL Server 2008 R2 einen geographyDatentyp hat. Ich habe absolut keine Erfahrung damit geographyund nach meinen ersten Recherchen scheint es für mein Szenario übertrieben zu sein.

Um beispielsweise mit Längen- und Breitengraden zu arbeiten, die als gespeichert sind decimal(7,4), kann ich Folgendes tun:

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

aber mit geographywürde ich das machen:

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

Obwohl es nicht , dass viel komplizierter, warum Komplexität , wenn ich nicht haben?

Gibt geographyes etwas, das ich berücksichtigen sollte, bevor ich die Idee der Verwendung aufgeben kann? Wäre es schneller, mithilfe eines räumlichen Index nach einem Ort zu suchen, als die Breiten- und Längengrade zu indizieren? Gibt es Vorteile bei der Verwendung geography, die mir nicht bekannt sind? Oder gibt es auf der anderen Seite Vorbehalte, die ich kennen sollte und die mich davon abhalten würden, sie zu verwenden geography?


Aktualisieren

@Erik Philips hat die Möglichkeit zur Suche in der Nähe angesprochen geography, was sehr cool ist.

Auf der anderen Seite zeigt ein schneller Test, dass ein einfach selectzu ermittelnder Längen- und Breitengrad bei der Verwendung erheblich langsamer ist geography(Details unten). und ein Kommentar zur akzeptierten Antwort auf eine andere SO-Frage geographyhat mich misstrauisch gemacht:

@SaphuA Gern geschehen. Als Nebenbemerkung sollten Sie SEHR vorsichtig sein, wenn Sie einen räumlichen Index für eine nullfähige GEOGRAPHY-Datentypspalte verwenden. Es gibt einige schwerwiegende Leistungsprobleme. Machen Sie diese GEOGRAPHY-Spalte daher nicht nullwertfähig, selbst wenn Sie Ihr Schema neu gestalten müssen. - Tomas 18. Juni um 11:18 Uhr

Alles in allem habe ich mich entschlossen, geographyin diesem Fall auf die Verwendung von Proximity-Suchen im Vergleich zum Kompromiss zwischen Leistung und Komplexität zu verzichten .


Details des Tests, den ich durchgeführt habe:

Ich habe zwei Tabellen erstellt, eine mit geographyund eine mit decimal(9,6)Längen- und Breitengrad:

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

und in jede Tabelle eine einzelne Zeile mit denselben Breiten- und Längenwerten eingefügt:

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

Das Ausführen des folgenden Codes zeigt schließlich, dass auf meinem Computer die Auswahl des Breiten- und Längengrads bei Verwendung ungefähr fünfmal langsamer ist 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

Ergebnisse:

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

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

Überraschender ist, dass selbst wenn keine Zeilen ausgewählt sind, beispielsweise die Auswahl von Orten RowId = 2, die nicht vorhanden sind, geographynoch langsamer war:

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

AvgGeographyDuration AvgLatLongDuration
-------------------- ------------------
1608                 947
Jeff Ogata
quelle
4
Ich denke darüber nach, beides zu tun, Lat und Lon in ihren eigenen Spalten zu speichern und eine weitere Spalte für ein Geografieobjekt zu haben. Wenn ich also nur Lat / Lon benötige, nehme ich sie aus den Spalten und wenn ich eine Näherungssuche benötige Ich werde die Geographie benutzen. Ist das weise? Gibt es Nachteile (außer dass es mehr Platz benötigt ...)?
Yuval A.
@ YuvalA. Das klingt sicherlich vernünftig und kann ein guter Kompromiss sein. Die einzige Sorge, die ich habe, ist, ob sich die Spalte "Geografie" in der Tabelle auf Abfragen in Bezug auf die Tabelle auswirkt. Ich habe keine Erfahrung damit, daher müssten Sie einen Test durchführen, um dies zu überprüfen.
Jeff Ogata
1
Warum haben Sie Ihre Frage immer wieder mit neuen Fragen aktualisiert, anstatt neue Fragen zu stellen?
Tschad
@Chad nicht sicher, was du meinst. Ich habe den Hauptteil der Frage einmal aktualisiert, und es sollten keine weiteren Fragen gestellt werden.
Jeff Ogata
6
Für diejenigen, die diese Frage finden, ist es jetzt erwähnenswert, dass SQL Server 2012 mit der räumlichen Indizierung erhebliche Leistungssteigerungen beinhaltet. Bemerkenswert ist auch die Tatsache, dass Sie, solange Sie Standortinformationen speichern, später mithilfe eines Suchdienstes räumliche Informationen hinzufügen können, um Ihre bereits gespeicherten Adressen zu geocodieren.
Volvox

Antworten:

66

Wenn Sie eine räumliche Berechnung planen, ermöglicht EF 5.0 LINQ-Ausdrücke wie:

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

Dann gibt es einen sehr guten Grund, Geographie zu verwenden.

Erklärung des räumlichen Rahmens innerhalb von Entity Framework .

Aktualisiert mit Erstellen von Hochleistungs-Geodatenbanken

Wie ich auf Noel Abrahams Antwort notiert habe :

Als Koordinate im Leerzeichen wird jede Koordinate als Gleitkommazahl mit doppelter Genauigkeit gespeichert, die 64 Bit (8 Byte) lang ist, und der 8-Byte-Binärwert entspricht ungefähr 15 Stellen mit Dezimalgenauigkeit, sodass eine Dezimalstelle (9) verglichen wird , 6) das sind nur 5 Bytes, ist nicht gerade ein fairer Vergleich. Für einen echten Vergleich müsste die Dezimalzahl für jede LatLong (insgesamt 18 Byte) mindestens Dezimalzahl (15,12) (9 Byte) betragen.

Vergleichen der Speichertypen:

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'

Ergebnis:

name    rows    data     
Geo     20000   728 KB   
LatLon  20000   560 KB

Der geografische Datentyp nimmt 30% mehr Platz ein.

Darüber hinaus ist der Geografiedatentyp nicht nur auf das Speichern eines Punkts beschränkt, sondern Sie können auch LineString, CircularString, CompoundCurve, Polygon, CurvePolygon, GeometryCollection, MultiPoint, MultiLineString und MultiPolygon und mehr speichern . Jeder Versuch, selbst den einfachsten Geografietyp (als Lat / Long) über einen Punkt hinaus zu speichern (z. B. LINESTRING (1 1, 2 2) -Instanz), führt zu zusätzlichen Zeilen für jeden Punkt, einer Spalte zur Sequenzierung für die Reihenfolge jedes Punkts und eine weitere Spalte zum Gruppieren von Zeilen. SQL Server verfügt auch über Methoden für die Geografie-Datentypen, einschließlich der Berechnung von Fläche, Grenze, Länge, Entfernungen und mehr .

Es erscheint unklug, Latitude und Longitude als Dezimalzahl in SQL Server zu speichern.

Update 2

Wenn Sie Berechnungen wie Entfernung, Fläche usw. durchführen möchten, ist es schwierig, diese über die Erdoberfläche richtig zu berechnen. Jeder in SQL Server gespeicherte Geografietyp wird auch mit einer räumlichen Referenz-ID gespeichert . Diese IDs können aus verschiedenen Sphären bestehen (die Erde ist 4326). Dies bedeutet, dass die Berechnungen in SQL Server tatsächlich korrekt über der Erdoberfläche berechnet werden (anstelle von Luftlinie, die durch die Erdoberfläche verlaufen könnte).

Geben Sie hier die Bildbeschreibung ein

Erik Philips
quelle
1
Um diese Informationen zu ergänzen, erweitert die Verwendung von Geografie die Fähigkeit der SQL-Suche von einem Lat / Long zwischen anderen Lat / Longs (normalerweise nur Rechtecken), da Sie mit dem Geografie-Datentyp mehrere Regionen mit nahezu jeder Größe und Form erstellen können.
Erik Philips
1
Danke noch einmal. Ich habe nach Gründen für die Verwendung gefragt, geographyund Sie haben einige gute angegeben. Letztendlich habe ich mich entschieden, decimalin diesem Fall nur Felder zu verwenden (siehe mein langwieriges Update), aber es ist gut zu wissen, dass ich es verwenden kann, geographywenn ich jemals etwas ausgefalleneres tun muss, als nur Koordinaten abzubilden.
Jeff Ogata
6

Eine andere zu berücksichtigende Sache ist der Speicherplatz, der von jeder Methode belegt wird. Der Geografietyp wird als gespeichert VARBINARY(MAX). Versuchen Sie, dieses Skript auszuführen:

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'

Ergebnis:

name    rows    data     
Geo     20000   728 KB   
LatLon  20000   400 KB

Der geografische Datentyp nimmt fast doppelt so viel Platz ein.

Noel Abrahams
quelle
2
Als Koordinate im Leerzeichen wird jede Koordinate als Gleitkommazahl mit doppelter Genauigkeit gespeichert, die 64 Bit (8 Byte) lang ist, und der 8-Byte-Binärwert entspricht ungefähr 15 Stellen mit Dezimalgenauigkeit , sodass eine Dezimalstelle (9) verglichen wird , 6) das sind nur 5 Bytes , ist nicht gerade ein fairer Vergleich. Für einen echten Vergleich müsste die Dezimalzahl für jede LatLong (insgesamt 18 Byte) mindestens Dezimalzahl (15,12) (9 Byte) betragen.
Erik Philips
9
@ErikPhilips der Punkt ist, warum eine Dezimalstelle (15, 12) verwenden, wenn Sie nur eine Dezimalstelle (9, 6) benötigen? Der obige Vergleich ist praktisch - keine akademische Übung.
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
quelle
2
Neue Antworten sind immer willkommen, aber bitte fügen Sie einen Kontext hinzu. Wenn Sie kurz erklären, wie das oben genannte Problem gelöst wird, ist die Antwort für andere nützlicher.
Leigh