顧客データベースを再設計していますが、標準の住所フィールド(通り、市など)とともに保存したい新しい情報の1つは、住所の地理的な場所です。私が念頭に置いている唯一のユースケースは、住所が見つからない場合に、ユーザーがGoogleマップの座標をマップできるようにすることです。これは、エリアが新しく開発されたり、リモート/田舎の場所にあるときによく起こります.
最初の傾向は、緯度と経度を小数値として保存することでしたが、SQL Server 2008 R2にはgeography
データ型があることを思い出しました。 geography
を使用した経験はまったくありません。最初の調査から、このシナリオではやり過ぎに見えます。
たとえば、decimal(7,4)
として保存されている緯度と経度を使用するには、次のようにします。
insert into Geotest(Latitude, Longitude) values (47.6475, -122.1393)
select Latitude, Longitude from Geotest
geography
を使用すると、次のようになります。
insert into Geotest(Geolocation) values (geography::Point(47.6475, -122.1393, 4326))
select Geolocation.Lat, Geolocation.Long from Geotest
thatほど複雑ではありませんが、必要がないのになぜ複雑になるのですか?
geography
を使用するという考えを捨てる前に、考慮すべきことはありますか?空間インデックスを使用して場所を検索する方が、緯度と経度のフィールドをインデックスするよりも速いでしょうか?知らないgeography
を使用する利点はありますか?または、反対に、どちらがgeography
を使用することを思いとどまらせるかについて知っておくべき警告がありますか?
@Erik Philipsは、geography
を使用して近接検索を実行する機能を導入しました。これは非常に便利です。
一方、簡単なテストでは、select
を使用すると、緯度と経度を取得する単純なgeography
が大幅に遅くなることが示されています(詳細は以下)。 、および 受け入れられた回答 に対するコメントgeography
に関する別のSO質問に対する不満があります:
@SaphuAどういたしまして。補足として、null許容のGEOGRAPHYデータ型列で空間インデックスを使用する場合は、十分注意してください。重大なパフォーマンスの問題があるため、スキーマを再構築する必要がある場合でも、GEOGRAPHY列をNULL不可にします。 –トマス6月18日11時18分
全体として、近接検索を行う可能性とパフォーマンスと複雑さのトレードオフを比較検討し、この場合はgeography
の使用を控えることにしました。
実行したテストの詳細:
緯度と経度にgeography
を使用するテーブルとdecimal(9,6)
を使用するテーブルの2つのテーブルを作成しました。
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)
)
同じ緯度と経度の値を使用して各テーブルに単一の行を挿入しました。
insert into GeographyTest(Location) values (geography::Point(47.6475, -122.1393, 4326))
insert into LatLongTest(Latitude, Longitude) values (47.6475, -122.1393)
最後に、次のコードを実行すると、私のマシンでは、geography
を使用すると緯度と経度の選択が約5倍遅くなります。
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
結果:
GeographyDuration LatLongDuration
----------------- ---------------
5146 1020
5143 1016
5169 1030
AvgGeographyDuration AvgLatLongDuration
-------------------- ------------------
5152 1022
さらに驚くべきことは、行が選択されていない場合でも、たとえばRowId = 2
(存在しない)を選択した場合でも、geography
は依然として遅いということです。
GeographyDuration LatLongDuration
----------------- ---------------
1607 948
1610 946
1607 947
AvgGeographyDuration AvgLatLongDuration
-------------------- ------------------
1608 947
空間計算を行う予定がある場合、EF 5.0では次のようなLINQ式を使用できます。
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();
}
次に、地理を使用する非常に良い理由があります。
高性能空間データベースの作成 で更新
Noel Abrahams Answer :で述べたように
スペースに関する注意。各座標は64ビット(8バイト)の長さの倍精度浮動小数点数として格納され、8バイトのバイナリ値は15桁の10進精度にほぼ等しいため、decimal(9 、6)はわずか5バイトで、正確な比較ではありません。 Decimalは、実際の比較のために、各LatLong(合計18バイト)に対して少なくともDecimal(15,12)(9バイト)でなければなりません。
ストレージタイプの比較:
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'
結果:
name rows data
Geo 20000 728 KB
LatLon 20000 560 KB
地理データ型は、30%以上のスペースを占有します。
さらに、geographyデータ型はPointの格納だけに限定されず、 LineString、CircularString、CompoundCurve、Polygon、CurvePolygon、GeometryCollection、MultiPoint、MultiLineString、MultiPolygonなど も格納できます。ポイント(たとえば、LINESTRING(1 1、2 2)インスタンス)を超えて最も単純なGeographyタイプ(緯度/経度など)でさえ保存しようとすると、各ポイントに追加の行、各ポイントの順序付けのための列が発生します行をグループ化するための別の列。 SQL Serverには、 Area、Boundary、Length、Distancesなど の計算を含むGeographyデータ型のメソッドもあります。
LatitudeとLongitudeをSql ServerにDecimalとして保存するのは賢明ではないようです。
更新2
距離や面積などの計算を行う予定がある場合、地球の表面でこれらを適切に計算することは困難です。 SQL Serverに保存される各Geographyタイプは、 空間参照ID とともに保存されます。これらのIDは異なる球体にすることができます(地球は4326です)。これは、SQL Serverでの計算が実際に地球の表面上で正しく計算されることを意味します( as-the-crow-flies の代わりに、地球の表面を通過できます)。
考慮すべきもう1つのことは、各方法で使用されるストレージスペースです。地理タイプはVARBINARY(MAX)
として保存されます。このスクリプトを実行してみてください。
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'
結果:
name rows data
Geo 20000 728 KB
LatLon 20000 400 KB
地理データ型は、ほぼ2倍のスペースを占有します。