Sqlserver
 sql >> Base de données >  >> RDS >> Sqlserver

Est-il possible d'utiliser SqlGeography avec Linq to Sql ?

Si tout ce que vous voulez faire avec SqlGeography est de suivre des points et de tirer parti des index spatiaux de SQL Server 2008, vous pouvez, comme d'autres l'ont noté, masquer votre colonne de données spatiales de Linq vers SQL et utiliser des UDF ou des procédures stockées. Supposons que vous disposiez d'une table AddressFields comprenant des champs Latitude et Longitude. Ajoutez cette table à votre fichier DBML et écrivez le code de votre choix qui définit les champs Latitude et Longitude. Ensuite, le code SQL ci-dessous ajoutera un champ Geo geogarphy à cette table et créera un déclencheur dans la base de données qui définit automatiquement le champ Geo en fonction des champs Latitude et Longitude. Pendant ce temps, le code ci-dessous crée également d'autres UDF et procédures stockées utiles :DistanceBetween2 (j'avais déjà un DistanceBetween) renvoie la distance entre l'adresse représentée dans un champ d'adresse et une paire latitude/longitude spécifiée ; DistanceWithin renvoie divers champs de tous les AddressFields à une distance en milles spécifiée ; UDFDistanceWithin fait la même chose qu'une fonction définie par l'utilisateur (utile si vous souhaitez l'intégrer dans une requête plus large) ; et UDFNearestNeighbors renvoie les champs de AddressField correspondant au nombre spécifié de voisins les plus proches d'un point particulier. (L'une des raisons d'utiliser UDFNearestNeighbors est que SQL Server 2008 n'optimisera pas son utilisation d'un index spatial si vous appelez simplement order en appelant DistanceBetween2.)

Vous devrez personnaliser cela en modifiant AddressFields dans votre table et en personnalisant les champs de cette table que vous souhaitez renvoyer (regardez dans le code autour des références à AddressFieldID). Vous pouvez ensuite l'exécuter sur votre base de données et copier les procédures stockées et les UDF résultantes sur votre DBML, puis vous pouvez les utiliser dans des requêtes. Globalement, cela permet de profiter assez facilement d'un index spatial de points.

-----------------------------------------------------------------------------------------

--[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] CRÉER UN INDEX

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] CRÉER PROCÉDURE 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] [email protected] and [Latitude] = @latitude

GO
--TEST
EXEC USPSetGEOValue 38.87350500,-76.97627500

GO

--[5] CRÉER UN DÉCLENCHEUR SUR LE CHANGEMENT/INSÉRER DE VALEUR LAT/LONG ---> DÉFINIR LE GÉOCODE

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] [email protected] 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] [email protected] and [Latitude] = @latitude
        END 
GO

--[6] CREATE PROC USP_SET_GEO_VALUE_INITIAL_LOAD ----> EXÉCUTION UNIQUEMENT UNE SEULE FOIS

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, qui renvoie la distance entre deux points spécifiés

--par paires de coordonnées latitude/longitude. --ALTER PROC DistanceEntre2

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

ALLER - TESTER

DistanceEntre2 12159,40.75889600,-73.99228900

--[8] CRÉER PROCÉDURE USPDistanceWithin

-- RENVOIE LA LISTE DES ADRESSES DE LA table 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

ALLER

--TEST

--dans un rayon de 3 milesUSPDistancedans un rayon de 38,90606200,-76,92943500,3GO--dans un rayon de 5 milesUSPDistancedans un rayon de 38,90606200,-76,92943500,5GO--dans un rayon de 10 milesUSPDistancedans un rayon de 38,90606200,-76,92943500,10

--[9] CRÉER LA FONCTION FNDistanceDans

-- RENVOIE LA LISTE DES ADRESSES DE LA table 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

ALLER

--TEST

--dans un rayon de 3 milesselect * from UDFDistanceWithin(38.90606200,-76.92943500,3)GO--dans un rayon de 5 milesselect * from UDFDistanceWithin( 38.90606200,-76.92943500,5)GO--dans un rayon de 10 milesselect * from UDFDistanceWithin( 38.90606200,-736.92)

--[9] CRÉER LA FONCTION UDFNearestNeighbors

-- RENVOIE LA LISTE DES ADRESSES DE LA table 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

ALLER

--TEST

--50 voisinssélectionnez * parmi UDFNeighbors(38.90606200,-76.92943500,50)GO--200 voisinssélectionnez * parmi UDFNearestNeighbors( 38.90606200,-76.92943500,200)GO