Je suis en train de remodeler une base de données de clients et l'une des nouvelles informations que j'aimerais stocker, ainsi que les champs d'adresse standard (rue, ville, etc.) est l'emplacement géographique de l'adresse. Le seul cas d'utilisation que je me propose est de permettre aux utilisateurs de cartographier les coordonnées sur des cartes Google lorsque l'adresse est introuvable, ce qui se produit souvent lorsque la zone est nouvellement développée ou située dans une zone reculée ou rurale.
Ma première tendance était de stocker la latitude et la longitude sous forme décimale, mais je me suis souvenu que SQL Server 2008 R2 avait un type de données geography
. Je n'ai absolument aucune expérience d'utilisation de geography
et, d'après mes recherches initiales, le scénario semble exagéré.
Par exemple, pour travailler avec la latitude et la longitude stockées sous la forme decimal(7,4)
, je peux le faire:
insert into Geotest(Latitude, Longitude) values (47.6475, -122.1393)
select Latitude, Longitude from Geotest
mais avec geography
, je ferais ceci:
insert into Geotest(Geolocation) values (geography::Point(47.6475, -122.1393, 4326))
select Geolocation.Lat, Geolocation.Long from Geotest
Bien que ce ne soit pas aussi beaucoup plus compliqué, pourquoi ajouter de la complexité si ce n'est pas nécessaire?
Avant d’abandonner l’idée d’utiliser geography
, puis-je envisager quelque chose? Serait-il plus rapide de rechercher un emplacement à l'aide d'un index spatial plutôt que d'indexer les champs Latitude et Longitude? Y a-t-il des avantages à utiliser geography
dont je ne suis pas au courant? Ou bien, y a-t-il des mises en garde que je devrais connaître qui me décourageraient d'utiliser geography
?
@Erik Philips a évoqué la possibilité d'effectuer des recherches de proximité avec geography
, ce qui est très pratique.
D'autre part, un test rapide montre qu'un simple select
pour obtenir la latitude et la longitude est nettement plus lent lorsque vous utilisez geography
(détails ci-dessous). , et un commentaire sur le réponse acceptée à un autre SO question sur geography
m'a méprisé:
@SaphuA de rien. En tant que note, soyez TRÈS prudent d'utiliser un index spatial sur une colonne de type de données GEOGRAPHY à valeur NULL. Il y a de sérieux problèmes de performances. Par conséquent, assurez-vous que la colonne GEOGRAPHY soit non nullable même si vous devez remodeler votre schéma. - Tomas 18 juin à 11h18
Globalement, en pesant la probabilité de faire des recherches de proximité par rapport aux compromis entre performances et complexité, j'ai décidé de renoncer à l'utilisation de geography
dans ce cas.
Détails du test que j'ai effectué:
J'ai créé deux tables, l'une utilisant geography
et l'autre utilisant decimal(9,6)
pour la latitude et la longitude:
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)
)
et inséré une seule ligne en utilisant les mêmes valeurs de latitude et de longitude dans chaque tableau:
insert into GeographyTest(Location) values (geography::Point(47.6475, -122.1393, 4326))
insert into LatLongTest(Latitude, Longitude) values (47.6475, -122.1393)
Enfin, l’exécution du code suivant montre que, sur ma machine, la sélection de la latitude et de la longitude est environ 5 fois plus lente lorsqu’on utilise 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
Résultats:
GeographyDuration LatLongDuration
----------------- ---------------
5146 1020
5143 1016
5169 1030
AvgGeographyDuration AvgLatLongDuration
-------------------- ------------------
5152 1022
Ce qui était plus surprenant, c'est que même lorsqu'aucune ligne n'est sélectionnée, par exemple lorsque RowId = 2
, Qui n'existe pas, geography
était encore plus lent:
GeographyDuration LatLongDuration
----------------- ---------------
1607 948
1610 946
1607 947
AvgGeographyDuration AvgLatLongDuration
-------------------- ------------------
1608 947
Si vous envisagez d'effectuer un calcul spatial, EF 5.0 autorise les expressions LINQ telles que:
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();
}
Alors il y a une très bonne raison d'utiliser la géographie.
Explication de l'espace dans Entity Framework .
Mis à jour avec Création de bases de données spatiales hautes performances
Comme je l'ai noté sur Noel Abrahams Answer :
Une note sur l’espace, chaque coordonnée est stockée sous la forme d’un nombre à virgule flottante à double précision de 64 bits (8 octets) de long, et une valeur binaire de 8 octets équivaut à environ 15 chiffres de précision décimale. , 6) qui n’est que de 5 octets, n’est pas une comparaison juste. La virgule décimale doit être au minimum décimale (15,12) (9 octets) pour chaque LatLong (total de 18 octets) pour une comparaison réelle.
Donc, en comparant les types de stockage:
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'
Résultat:
name rows data
Geo 20000 728 KB
LatLon 20000 560 KB
Le type de données geography occupe 30% plus d'espace.
De plus, le type de données geography ne se limite pas à stocker un point, vous pouvez également stocker LineString, CircularString, CompoundCurve, Polygon, CurvePolygon, GeometryCollection, MultiPoint, MultiLineString et MultiPolygon, etc. . Toute tentative de stocker même les types de géographie les plus simples (Lat/Long) au-delà d'un point (par exemple, instance LINESTRING (1 1, 2 2)) entraînera des lignes supplémentaires pour chaque point, une colonne permettant de séquencer l'ordre de chaque point. et une autre colonne pour regrouper les lignes. SQL Server propose également des méthodes pour les types de données Geography, notamment le calcul Surface, limite, longueur, distances, etc. .
Il semble peu judicieux de stocker Latitude et Longitude sous forme décimale dans Sql Server.
Mise à jour 2
Si vous envisagez de faire des calculs tels que la distance, la surface, etc., il est difficile de les calculer correctement à la surface de la Terre. Chaque type de géographie stocké dans SQL Server est également stocké avec un ID de référence spatiale . Ces identifiants peuvent être de différentes sphères (la Terre est 4326). Cela signifie que les calculs effectués dans SQL Server seront calculés correctement sur la surface de la Terre (au lieu de à vol d'oisea qui pourrait être à la surface de la Terre).
Une autre chose à considérer est l’espace de stockage occupé par chaque méthode. Le type de géographie est enregistré en tant que VARBINARY(MAX)
. Essayez d'exécuter ce 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'
Résultat:
name rows data
Geo 20000 728 KB
LatLon 20000 400 KB
Le type de données geography occupe presque deux fois plus d'espace.