sql >> Databasteknik >  >> RDS >> Sqlserver

Är det möjligt att använda SqlGeography med Linq till Sql?

Om allt du vill göra med SqlGeography är att spåra poäng och dra nytta av SQL Server 2008:s rumsliga index, kan du, som andra har noterat, dölja din rumsliga datakolumn från Linq till SQL och använda UDF:er eller lagrade procedurer. Anta att du har en tabell Adressfält som innehåller fälten Latitud och Longitud. Lägg till den tabellen i din DBML-fil och skriv vilken kod du vill som anger fälten Latitud och Longitud. Sedan kommer SQL-koden nedan att lägga till ett Geo geogarphy-fält till den tabellen och skapa en utlösare i databasen som automatiskt ställer in Geo-fältet baserat på Latitude- och Longitud-fälten. Samtidigt skapar koden nedan även andra användbara UDF:er och lagrade procedurer:DistanceBetween2 (jag hade redan en DistanceBetween) returnerar avståndet mellan adressen som representeras i ett AddressField och ett specificerat latitud/longitudpar; DistanceWithin returnerar olika fält från alla adressfält inom ett angivet mils avstånd; UDFDistanceWithin gör samma sak som en användardefinierad funktion (användbart om du vill bädda in detta i en större fråga); och UDFNearestNeighbors returnerar fält från AddressField som motsvarar det specificerade antalet grannar närmast en viss punkt. (En anledning till att använda UDFNearestNeighbors är att SQL Server 2008 inte kommer att optimera användningen av ett rumsligt index om du bara anropar order genom att anropa DistanceBetween2.)

Du måste anpassa detta genom att ändra AddressFields till din tabell och anpassa fälten från den tabellen som du vill returnera (titta i koden runt referenser till AddressFieldID). Du kan sedan köra detta på din databas och kopiera de resulterande lagrade procedurerna och UDF:erna till din DBML, och sedan kan du använda dem i frågor. Sammantaget låter detta dig dra fördel av ett rumsligt index med poäng ganska enkelt.

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

--[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] SKAPA 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] SKAPA PROCEDUR 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] SKAPA TRIGGER PÅ LAT/LÅNG VÄRDEÄNDRING/INSERT ---> STÄLL IN GEOCOD

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] SKAPA PROC USP_SET_GEO_VALUE_INITIAL_LOAD ----> KÖR ENDAST EN GÅNG

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] EXISTERANDE PROC DistanceBetween, som returnerar avståndet mellan två angivna punkter

--efter latitud/longitud-koordinatpar. --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

GÅ--TEST

Distance Between2 12159,40.75889600,-73.99228900

--[8] SKAPA PROCEDUR USPDistanceWithin

-- RETURNERAR LISTA MED ADRESSER FRÅN Adressfältstabellen

OM FINNS (VÄLJ namn FRÅN sysobjects WHERE namn ='USPDistanceWithin' OCH skriv ='P')SLÄPP PROCEDUR 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

--TEST

--inom 3 milesUSPDistanceInom 38.90606200,-76.92943500,3GO--inom 5 milesUSPDistanceInom 38.90606200,-76.92943500,5GO--inom 10 mileUSPDistans 38.90606200,5GO--inom 10 mileUSPDistance 38.9006Wit 36.906,360906,709,3806,36,09,36.

--[9] SKAPA FUNKTION FNDistanceWithin

-- RETURNERAR LISTA MED ADRESSER FRÅN Adressfältstabellen

OM FINNS (VÄLJ namn FRÅN sysobjects WHERE name ='UDFDistanceWithin' OCH typ ='TF')SLÄPP FUNKTION 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

--TEST

--inom 3 milesvälj * från UDFDistanceWithin(38.90606200,-76.92943500,3)GO--inom 5 milesselect * från UDFDistanceWithin( 38.90606200,-76.92943500,5)GO-3tim från 06.92943500,5) 06.92943500,5) 06.92943500,5)106.92943500,5) 06.92943500,5)106.92943500,5) 06.9df.in 06.06.

--[9] SKAPA FUNKTION UDFNearestNeighbors

-- RETURNERAR LISTA MED ADRESSER FRÅN Adressfältstabellen

OM FINNS (VÄLJ namn FRÅN sysobjects WHERE name ='UDFNearestNeighbors' OCH typ ='TF')SLÄPP FUNKTION UDFNearestNeighbors

GO

OM FINNS (VÄLJ namn FRÅN sysobjects WHERE name ='nummer' OCH xtype ='u') SLIPP TABELLnummer

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

--TEST

--50 grannar välj * från UDFNearestNeighbors(38.90606200,-76.92943500,50)GO--200 grannarvälj * från UDFNearestNeighbors( 38.90606200,-76.929403500)GO03500,

  1. Hur ställer jag in sortering för en anslutning i SQL Server?

  2. Hur man uppdaterar SQL Server-statistik för stora tabeller

  3. PostgreSQL ILIKE-fråga med SQLAlchemy

  4. Interner i de sju SQL-serversorterna – Del 1