如果您想要对 SqlGeography 做的只是跟踪点并利用 SQL Server 2008 的空间索引,那么您可以像其他人所指出的那样,将您的空间数据列从 Linq 隐藏到 SQL 并使用 UDF 或存储过程。假设您有一个包含纬度和经度字段的地址字段表。将该表添加到您的 DBML 文件中,并编写您想要设置纬度和经度字段的任何代码。然后,下面的 SQL 代码将向该表添加一个 Geo geogarphy 字段,并在数据库中创建一个触发器,该触发器会根据 Latitude 和 Longitude 字段自动设置 Geo 字段。同时,下面的代码还创建了其他有用的 UDF 和存储过程:DistanceBetween2(我已经有一个DistanceBetween)返回AddressField中表示的地址与指定的纬度/经度对之间的距离;DistanceWithin 从指定英里距离内的所有 AddressFields 返回各个字段;UDFDistanceWithin 与用户定义函数的作用相同(如果您想将其嵌入到更大的查询中,则很有用);并且 UDFNearestNeighbors 从 AddressField 返回与指定数量的最接近特定点的邻居相对应的字段。(使用 UDFNearestNeighbors 的一个原因是,如果您只是通过调用 DistanceBetween2 来调用 order,SQL Server 2008 不会优化其对空间索引的使用。)UDFDistanceWithin 与用户定义函数的作用相同(如果您想将其嵌入到更大的查询中,则很有用);并且 UDFNearestNeighbors 从 AddressField 返回与指定数量的最接近特定点的邻居相对应的字段。(使用 UDFNearestNeighbors 的一个原因是,如果您只是通过调用 DistanceBetween2 来调用 order,SQL Server 2008 不会优化其对空间索引的使用。)UDFDistanceWithin 与用户定义函数的作用相同(如果您想将其嵌入到更大的查询中,则很有用);并且 UDFNearestNeighbors 从 AddressField 返回与指定数量的最接近特定点的邻居相对应的字段。(使用 UDFNearestNeighbors 的一个原因是,如果您只是通过调用 DistanceBetween2 来调用 order,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] 创建过程 USP_SET_GEO_VALUE 参数 1 纬度 2 经度
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] 在 LAT/Long 值更改/插入时创建触发器 ---> 设置地理代码
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] 创建过程 USP_SET_GEO_VALUE_INITIAL_LOAD ----> 仅运行一次
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] EXISTING PROC DistanceBetween,返回指定两点之间的距离
--按纬度/经度坐标对。--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
去——测试
距离Between2 12159,40.75889600,-73.99228900
--[8] 创建过程 USPDistanceWithin
-- 从 AddressFields 表返回地址列表
IF EXISTS (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] 创建函数 FNDistanceWithin
-- 从 AddressFields 表返回地址列表
IF EXISTS (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 英里内选择 * 从 UDFDistanceWithin(38.90606200,-76.92943500,3) 开始 -- 在 5 英里内选择 * 从 UDFDistanceWithin(38.90606200,-76.92943500,5) 开始 -- 在 10 英里内选择 * 从 UDFDistanceWithin( 38.920606200,-7900. ,10)
--[9] 创建函数 UDFNearestNeighbors
-- 从 AddressFields 表返回地址列表
IF EXISTS (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