Sqlserver
 sql >> Datenbank >  >> RDS >> Sqlserver

Ist es möglich, SqlGeography mit Linq to Sql zu verwenden?

Wenn Sie mit SqlGeography nur Punkte verfolgen und die räumlichen Indizes von SQL Server 2008 nutzen möchten, können Sie, wie andere angemerkt haben, Ihre räumliche Datenspalte vor Linq in SQL ausblenden und UDFs oder gespeicherte Prozeduren verwenden. Angenommen, Sie haben eine Tabelle AddressFields, die Breitengrad- und Längengradfelder enthält. Fügen Sie diese Tabelle zu Ihrer DBML-Datei hinzu und schreiben Sie beliebigen Code, der die Breitengrad- und Längengradfelder festlegt. Anschließend fügt der folgende SQL-Code ein Geo-Geografikfeld zu dieser Tabelle hinzu und erstellt einen Trigger in der Datenbank, der das Geo-Feld automatisch basierend auf den Breitengrad- und Längengradfeldern festlegt. In der Zwischenzeit erstellt der folgende Code auch andere nützliche UDFs und gespeicherte Prozeduren:DistanceBetween2 (ich hatte bereits ein DistanceBetween) gibt die Entfernung zwischen der in einem AddressField dargestellten Adresse und einem angegebenen Breiten-/Längengradpaar zurück; DistanceWithin gibt verschiedene Felder aus allen AddressFields innerhalb einer angegebenen Meilenentfernung zurück; UDFDistanceWithin tut dasselbe wie eine benutzerdefinierte Funktion (nützlich, wenn Sie diese in eine größere Abfrage einbetten möchten); und UDFNearestNeighbors gibt Felder von AddressField zurück, die der spezifizierten Anzahl von Nachbarn entsprechen, die einem bestimmten Punkt am nächsten sind. (Ein Grund für die Verwendung von UDFNearestNeighbors ist, dass SQL Server 2008 die Verwendung eines räumlichen Indexes nicht optimiert, wenn Sie einfach die Reihenfolge aufrufen, indem Sie DistanceBetween2 aufrufen.)

Sie müssen dies anpassen, indem Sie AddressFields in Ihre Tabelle ändern und die Felder aus dieser Tabelle anpassen, die Sie zurückgeben möchten (sehen Sie sich den Code um Verweise auf AddressFieldID an). Sie können dies dann in Ihrer Datenbank ausführen und die resultierenden gespeicherten Prozeduren und UDFs in Ihre DBML kopieren und sie dann in Abfragen verwenden. Insgesamt können Sie so ganz einfach einen räumlichen Index von Punkten nutzen.

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

--[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] INDEX ERSTELLEN

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 BREITE 2 LÄNGE

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] ERSTELLEN TRIGGER AUF LAT/LANGE WERTÄNDERUNG/EINFÜGE ---> GEOCODE EINSTELLEN

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 ----> NUR EINMAL AUSFÜHREN

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, das den Abstand zwischen zwei angegebenen Punkten zurückgibt

--nach Breitengrad-/Längengrad-Koordinatenpaaren. --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

GO--TEST

DistanceBetween2 12159,40.75889600,-73.99228900

--[8] CREATE PROCEDURE USPDistanceWithin

-- ZURÜCKGABE DER ADRESSENLISTE AUS DER AddressFields-Tabelle

WENN VORHANDEN (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

GEHEN

--TEST

--innerhalb von 3 MeilenUSPDistanceWithin 38.90606200,-76.92943500,3GO--innerhalb von 5 MeilenUSPDistanceWithin 38.90606200,-76.92943500,5GO--innerhalb von 10 MeilenUSPDistanceWithin 38.90606200,-76.92943500,10

--[9] FUNKTION FNDistanceWithin ERSTELLEN

-- ZURÜCKGABE DER ADRESSENLISTE AUS DER AddressFields-Tabelle

WENN VORHANDEN (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

GEHEN

--TEST

--innerhalb von 3 Meilenwählen Sie * von UDFDistanceWithin(38.90606200,-76.92943500,3)GO--innerhalb von 5 Meilenwählen Sie * von UDFDistanceWithin(38.90606200,-76.92943500,5)GO--innerhalb von 10 Meilenwählen Sie * von UDFDistanceWithin(38.90606200,-74309,-74309)

--[9] FUNKTION ERSTELLEN UDFNearestNeighbors

-- ZURÜCKGABE DER ADRESSENLISTE AUS DER AddressFields-Tabelle

WENN VORHANDEN (SELECT name FROM sysobjects WHERE name ='UDFNearestNeighbors' AND type ='TF')DROP FUNCTION UDFNearestNeighbors

GO

WENN VORHANDEN (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

GEHEN

--TEST

--50 Neighboursselect * from UDFNearestNeighbors(38.90606200,-76.92943500,50)GO--200 Neighborsselect * from UDFNearestNeighbors(38.90606200,-76.92943500,200)GO