SqlGeography でやりたいことがトラック ポイントだけで、SQL Server 2008 の空間インデックスを利用する場合は、他の人が指摘しているように、空間データ列を Linq から SQL に隠し、UDF またはストアド プロシージャを使用できます。緯度と経度のフィールドを含むテーブル AddressFields があるとします。そのテーブルを DBML ファイルに追加し、緯度と経度のフィールドを設定する任意のコードを記述します。次に、以下の SQL コードは、Geo geogarphy フィールドをそのテーブルに追加し、Latitude フィールドと Longitude フィールドに基づいて Geo フィールドを自動的に設定するトリガーをデータベースに作成します。一方、以下のコードは、他の便利な UDF とストアド プロシージャも作成します。DistanceBetween2 (既に DistanceBetween を持っていました) は、AddressField で表される住所と指定された緯度/経度のペアとの間の距離を返します。DistanceWithin は、指定されたマイル距離内のすべての AddressFields からさまざまなフィールドを返します。UDFDistanceWithin は、ユーザー定義関数と同じことを行います (これをより大きなクエリに埋め込みたい場合に便利です)。また、UDFNearestNeighbors は、特定のポイントに最も近い、指定された数の近傍に対応する AddressField からフィールドを返します。(UDFNearestNeighbors を使用する理由の 1 つは、DistanceBetween2 を呼び出して順序を呼び出すだけでは、SQL Server 2008 が空間インデックスの使用を最適化しないことです)。UDFDistanceWithin は、ユーザー定義関数と同じことを行います (これをより大きなクエリに埋め込みたい場合に便利です)。また、UDFNearestNeighbors は、特定のポイントに最も近い、指定された数の近傍に対応する AddressField からフィールドを返します。(UDFNearestNeighbors を使用する理由の 1 つは、DistanceBetween2 を呼び出して順序を呼び出すだけでは、SQL Server 2008 が空間インデックスの使用を最適化しないことです)。UDFDistanceWithin は、ユーザー定義関数と同じことを行います (これをより大きなクエリに埋め込みたい場合に便利です)。また、UDFNearestNeighbors は、特定のポイントに最も近い、指定された数の近傍に対応する AddressField からフィールドを返します。(UDFNearestNeighbors を使用する理由の 1 つは、DistanceBetween2 を呼び出して順序を呼び出すだけでは、SQL Server 2008 が空間インデックスの使用を最適化しないことです)。
AddressFields をテーブルに変更し、返されるテーブルのフィールドをカスタマイズして、これをカスタマイズする必要があります (AddressFieldID への参照の周りのコードを参照してください)。次に、これをデータベースで実行し、結果のストアド プロシージャと UDF を DBML にコピーして、それらをクエリで使用できます。全体として、これにより、ポイントの空間インデックスをかなり簡単に利用できます。
-----------------------------------------------------------------------------------------
--[1]
--INITIAL AUDIT
select * from dbo.AddressFields
GO
--ADD COLUMN GEO
IF EXISTS (SELECT name FROM sysindexes WHERE name = 'SIndx_AddressFields_geo')
DROP INDEX SIndx_AddressFields_geo ON AddressFields
GO
IF EXISTS (SELECT b.name FROM sysobjects a, syscolumns b
WHERE a.id = b.id and a.name = 'AddressFields' and b.name ='Geo' and a.type ='U' )
ALTER TABLE AddressFields DROP COLUMN Geo
GO
alter table AddressFields add Geo geography
--[2]
--SET GEO VALUE
GO
UPDATE AddressFields
SET Geo = geography::STPointFromText('POINT(' + CAST([Longitude] AS VARCHAR(20)) + ' ' +
CAST([Latitude] AS VARCHAR(20)) + ')', 4326)
--[3] 索引作成
IF EXISTS (SELECT name FROM sysindexes WHERE name = 'SIndx_AddressFields_geo')
DROP INDEX SIndx_AddressFields_geo ON AddressFields
GO
CREATE SPATIAL INDEX SIndx_AddressFields_geo
ON AddressFields(geo)
--UPDATE STATS
UPDATE STATISTICS AddressFields
--AUDIT
GO
select * from dbo.AddressFields
--[4] CREATE PROCEDURE USP_SET_GEO_VALUE PARA 1 LATITUDE 2 LONGITUDE
IF EXISTS (SELECT name FROM sysobjects WHERE name = 'USPSetGEOValue' AND type = 'P')
DROP PROC USPSetGEOValue
GO
GO
CREATE PROC USPSetGEOValue @latitude decimal(18,8), @longitude decimal(18,8)
AS
UPDATE AddressFields
SET Geo = geography::STPointFromText('POINT(' + CAST(@longitude AS VARCHAR(20)) + ' ' +
CAST(@latitude AS VARCHAR(20)) + ')', 4326)
WHERE [Longitude] =@longitude and [Latitude] = @latitude
GO
--TEST
EXEC USPSetGEOValue 38.87350500,-76.97627500
GO
--[5] 緯度/経度値の変更/挿入でトリガーを作成 ---> ジオコードを設定
IF EXISTS (SELECT name FROM sysobjects WHERE name = 'TRGSetGEOCode' AND type = 'TR')
DROP TRIGGER TRGSetGEOCode
GO
CREATE TRIGGER TRGSetGEOCode
ON AddressFields
AFTER INSERT,UPDATE
AS
DECLARE @latitude decimal(18,8), @longitude decimal(18,8)
IF ( UPDATE (Latitude) OR UPDATE (Longitude) )
BEGIN
SELECT @latitude = latitude ,@longitude = longitude from inserted
UPDATE AddressFields
SET Geo = geography::STPointFromText('POINT(' + CAST(@longitude AS VARCHAR(20)) + ' ' +
CAST(@latitude AS VARCHAR(20)) + ')', 4326)
WHERE [Longitude] =@longitude and [Latitude] = @latitude
END
ELSE
BEGIN
SELECT @latitude = latitude ,@longitude = longitude from inserted
UPDATE AddressFields
SET Geo = geography::STPointFromText('POINT(' + CAST(@longitude AS VARCHAR(20)) + ' ' +
CAST(@latitude AS VARCHAR(20)) + ')', 4326)
WHERE [Longitude] =@longitude and [Latitude] = @latitude
END
GO
--[6] CREATE PROC USP_SET_GEO_VALUE_INITIAL_LOAD ----> 1 回のみ実行
IF EXISTS (SELECT name FROM sysobjects WHERE name = 'USPSetAllGeo' AND type = 'P')
DROP PROC USPSetAllGeo
GO
CREATE PROC USPSetAllGeo
AS
UPDATE AddressFields
SET Geo = geography::STPointFromText('POINT(' + CAST([Longitude] AS VARCHAR(20)) + ' ' +
CAST([Latitude] AS VARCHAR(20)) + ')', 4326)
GO
--[7] 既存の PROC DistanceBetween。指定された 2 点間の距離を返します。
-- 緯度/経度の座標ペアによる。--ALTER PROC DistanceBetween2
IF EXISTS (SELECT name FROM sysobjects WHERE name = 'DistanceBetween2' AND type = 'FN')
DROP FUNCTION DistanceBetween2
GO
CREATE FUNCTION [dbo].[DistanceBetween2]
(@AddressFieldID as int, @Lat1 as real,@Long1 as real)
RETURNS real
AS
BEGIN
DECLARE @KMperNM float = 1.0/1.852;
DECLARE @nwi geography =(select geo from addressfields where AddressFieldID = @AddressFieldID)
DECLARE @edi geography = geography::STPointFromText('POINT(' + CAST(@Long1 AS VARCHAR(20)) + ' ' +
CAST(@Lat1 AS VARCHAR(20)) + ')', 4326)
DECLARE @dDistance as real = (SELECT (@nwi.STDistance(@edi)/1000.0) * @KMperNM)
return (@dDistance);
END
行く - テスト
DistanceBetween2 12159,40.75889600,-73.99228900
--[8] CREATE PROCEDURE USPDistanceWithin
-- AddressFields テーブルからアドレスのリストを返します
存在する場合 (SELECT name FROM sysobjects WHERE name = 'USPDistanceWithin' AND type = 'P') DROP PROCEDURE USPDistanceWithin
GO
CREATE PROCEDURE [dbo].USPDistanceWithin
(@lat as real,@long as real, @distance as float)
AS
BEGIN
DECLARE @edi geography = geography::STPointFromText('POINT(' + CAST(@Long AS VARCHAR(20)) + ' ' +
CAST(@Lat AS VARCHAR(20)) + ')', 4326)
SET @distance = @distance * 1609.344 -- convert distance into meter
select
AddressFieldID
,FieldID
,AddressString
,Latitude
,Longitude
,LastGeocode
,Status
--,Geo
from
AddressFields a WITH(INDEX(SIndx_AddressFields_geo))
where
a.geo.STDistance(@edi) < = @Distance
END
行く
- テスト
--3 マイル以内 USPDistanceWithin 38.90606200,-76.92943500,3 GO --5 マイル以内 USPDistanceWithin 38.90606200,-76.92943500,5 GO --10 マイル以内 USPDistanceWithin 38.90606200,-76.92943500,10
--[9] CREATE FUNCTION FNDistanceWithin
-- AddressFields テーブルからアドレスのリストを返します
存在する場合 (SELECT name FROM sysobjects WHERE name = 'UDFDistanceWithin' AND type = 'TF') DROP FUNCTION UDFDistanceWithin
GO
CREATE FUNCTION UDFDistanceWithin
(@lat as real,@long as real, @distance as real)
RETURNS @AddressIdsToReturn TABLE
(
AddressFieldID INT
,FieldID INT
)
AS
BEGIN
DECLARE @edi geography = geography::STPointFromText('POINT(' + CAST(@Long AS VARCHAR(20)) + ' ' +
CAST(@Lat AS VARCHAR(20)) + ')', 4326)
SET @distance = @distance * 1609.344 -- convert distance into meter
INSERT INTO @AddressIdsToReturn
select
AddressFieldID
,FieldID
from
AddressFields a WITH(INDEX(SIndx_AddressFields_geo))
where
a.geo.STDistance(@edi) < = @Distance
RETURN
END
行く
- テスト
-- 3 マイル以内 select * from UDFDistanceWithin(38.90606200,-76.92943500,3) GO -- 5 マイル以内 select * from UDFDistanceWithin( 38.90606200,-76.92943500,5) GO -- 10 マイル以内 select * from UDFDistanceWithin( 38.90606200,-4356.9) ,10)
--[9] CREATE FUNCTION UDFNearestNeighbors
-- AddressFields テーブルからアドレスのリストを返します
存在する場合 (SELECT name FROM sysobjects WHERE name = 'UDFNearestNeighbors' AND type = 'TF') DROP FUNCTION UDFNearestNeighbors
GO
IF EXISTS (SELECT name FROM sysobjects WHERE name = 'numbers' AND xtype = 'u') DROP TABLE numbers
GO
-- First, create a Numbers table that we will use below.
SELECT TOP 100000 IDENTITY(int,1,1) AS n INTO numbers FROM MASTER..spt_values a, MASTER..spt_values b CREATE UNIQUE CLUSTERED INDEX idx_1 ON numbers(n)
GO
CREATE FUNCTION UDFNearestNeighbors
(@lat as real,@long as real, @neighbors as int)
RETURNS @AddressIdsToReturn TABLE
(
AddressFieldID INT
,FieldID INT
)
AS
BEGIN
DECLARE @edi geography = geography::STPointFromText('POINT(' + CAST(@Long AS VARCHAR(20)) + ' ' +
CAST(@Lat AS VARCHAR(20)) + ')', 4326)
DECLARE @start FLOAT = 1000;
WITH NearestPoints AS
(
SELECT TOP(@neighbors) WITH TIES *, AddressFields.geo.STDistance(@edi) AS dist
FROM Numbers JOIN AddressFields WITH(INDEX(SIndx_AddressFields_geo))
ON AddressFields.geo.STDistance(@edi) < @start*POWER(2,Numbers.n)
ORDER BY n
)
INSERT INTO @AddressIdsToReturn
SELECT TOP(@neighbors)
AddressFieldID
,FieldID
FROM NearestPoints
ORDER BY n DESC, dist
RETURN
END
行く
- テスト
--50 個のネイバーが UDFNearestNeighbors から * を選択 (38.90606200,-76.92943500,50) GO --200 個のネイバーが UDFNearestNeighbors から * を選択 (38.90606200,-76.92943500,200) GO