Sql Server spatial Find nearest neighbour like in oracle - sql-server

I'm having a trouble in finding the nearest point in sql server using Long Lat.
I want to pass a longitued and latitude parameter then use it to find the nearest point in that area.
Also is there a way to Select all tables that have a geometry function?

I already found a solution to this problem. This might be helpful to someone.
i already add some optimization to this code. the #Points.STBuffer(1) determine the search distance so i only put 1m because i need a exact long and lat because i get it on click.
ALTER PROCEDURE [dbo].[getFacilityDetailsOnClick]
-- Add the parameters for the stored procedure here
#long float,
#lat float
AS
BEGIN
DECLARE #points geometry
SET #points = geometry :: Point(#long,#lat,32651);
DECLARE #searchArea GEOMETRY;
SET #searchArea = #Points.STBuffer(1);
#tbl nvarchar(255)
SELECT #sSQL = 'SELECT FEATID, NAME , #tbl as FACILITYNAME , #facility as FACILITYID FROM (SELECT TOP 1 featid, NAME , geometry.STDistance(#pnts) as distance from '
+ QUOTENAME(#tbl) +' WHERE geometry.Filter(#searchArea) = 1 ORDER BY distance) AS tbl'
EXEC sp_executesql #sSQL , N'#pnts GEOMETRY , #searchArea GEOMETRY , #tbl nvarchar(255) , #facility int ', #Points , #searchArea , #tbl ,#facility;

Related

Free memory when using geometry in SQL Server 2008 R2

This text was translated in Google Translate.
When using the geometry functions in SQL Server 2008 R2, I have noticed that my server memory is increasing, and I have no way to release used memory.
I have a table where we store the different polygons that cover the different neighborhoods of my city.
CREATE TABLE dbo.Mapa_MSB (
id int IDENTITY(1, 1) NOT NULL,
zona varchar(50),
GeogCol1 geometry,
GeogCol2 AS [GeogCol1].[STAsText]()
)
So, I have a function whose parameters are the coordinates of a point in my city, and with this it returns the different polygons that point enters. With the coordinates I create my point using geometry::STGeomFromText('POINT(X, Y)', 4326)
declare #geo geometry
declare #flete geometry
declare #result geometry
set #geo = geometry::STGeomFromText('POINT(-100.35171446 25.66744965)', 4326)
Then I make a cursor to read all my polygons from my Mapa_MSB table, and I use the STIntersection function so that it returns me whether or not that point enters that polygon, the result is stored in a temporary memory, and at the end I perform a simple query to obtain the different polygons where there was an intersection.
declare #resultado_temporal table(
resultado varchar(max),
zona varchar(50))
declare #id int,
#zona varchar(50),
#GeogCol1 geometry
declare localiza_cursor cursor for
select id, zona, GeogCol1
from Zonas2.dbo.Mapa_MSB
open localiza_cursor
fetch next from localiza_cursor
into #id, #zona, #GeogCol1
while ##FETCH_STATUS = 0
begin
select #flete = GeogCol1 from Zonas2.dbo.Mapa_MSB where id = #id
set #flete = #flete.MakeValid()
if #flete.STIsValid() = 1
begin
select #result = #flete.STIntersection(#geo)
insert into #resultado_temporal
select #result.STAsText(), #zona
where #result.STAsText() <> 'GEOMETRYCOLLECTION EMPTY'
end
fetch next from localiza_cursor
into #id, #zona, #GeogCol1
end
close localiza_cursor
deallocate localiza_cursor
select * from #resultado_temporal
Every time I use this procedure, the memory of my server is going up, what can I do to optimize this procedure or so that the memory of the server does not go up?
I already managed to optimize the code, I found the book "Beginning Spatial with SQL Server 2008", where they recommend using the Filter() function, so I managed to optimize the code, being like this:
declare #geo geometry
set #geo = geometry::STGeomFromText('POINT(-100.35171446 25.66744965)', 4326)
select GeogCol2, zona
from Zonas2.dbo.Mapa_MSB
where GeogCol1.MakeValid().Filter(#geo) = 1

Way to call SQL stored procedure purely dynamically (dynamic parameters in sp_executesql)?

When calling a SQL Server stored procedure from an external language (c# for example), the manner in which the call is coded is such that a given stored procedure can be entirely described in metadata and called using a generic function. Support for varying numbers of parameters between different procedures is facilitated by a parameters collection on the calling command object.
If one wanted to implement a similar capability entirely within SQL Server, if one uses sp_executesql (a somewhat relevant example of which can be found here: Dynamic Search Conditions in T‑SQL)....you can get most of the way there, but the main problem is that the parameters in the function call must be hardcoded.
Example from the article:
EXEC sp_executesql #sql, #paramlist,
#orderid, #fromdate, #todate, #minprice,
#maxprice, #custid, #custname, #city, #region,
#country, #prodid, #prodname
In this example, the SQL statement is stored within #sql, the parameters list is stored within #paramList, and what follows is a list of the actual parameters.
Both #sql and #paramList are simple nvarchar variables that can be set programmatically (by reading from metadata and assigning to variables), but the actual parameters themselves are hardcoded.
So, the question is:
Is there a way to specify the actual parameters & values such that the implementation of this entire function could be entirely generic?
The list of parameters can be sent as comma-delimited (or special character delimited) list, the list can be parsed into the table then.
In this example I used "," and "=".
This one was my initial solution:
DECLARE #List VARCHAR(MAX) = 'a=1,b=3,c=hey,d=12/05/10,val5='
DECLARE #Delimiter1 VARCHAR(1) = ','
DECLARE #Delimiter2 VARCHAR(1) = '='
----
SELECT y.i.value('(./text())[1]', 'nvarchar(4000)') pass1
INTO #Buffer
FROM (
SELECT x = CONVERT(XML, '<i>'
+ REPLACE(#List, #Delimiter1, '</i><i>')
+ '</i>').query('.')
) a
CROSS APPLY x.nodes('i') y(i)
SELECT ROW_NUMBER()OVER(ORDER BY(SELECT 1)) rn,y.i.value('(./text())[1]', 'nvarchar(4000)') pass2
INTO #PreResult
FROM (
SELECT x = CONVERT(XML, '<i>'
+ REPLACE(b.pass1, #Delimiter2, '</i><i>')
+ '</i>').query('.')
FROM #Buffer b
WHERE b.pass1 LIKE '%=%' AND b.pass1 NOT LIKE '%=%=%' -- to make sure assignment has place and there is no double or more assignments
) a
CROSS APPLY x.nodes('i') y(i)
SELECT #List '#List'
--SELECT '' '#Buffer',* FROM #Buffer b
--SELECT '' '#PreResult',* FROM #PreResult p
SELECT p.pass2 [Variable],p2.pass2 [Value]
FROM #PreResult p
INNER JOIN #PreResult p2 ON p2.rn = p.rn + 1
WHERE p.rn%2 > 0
DROP TABLE #Buffer
DROP TABLE #PreResult
The smarter one:
DECLARE #List VARCHAR(MAX) = 'a=1,b=3,c=hey,d=12/05/10,val5='
DECLARE #Delimiter1 VARCHAR(1) = ','
DECLARE #Delimiter2 VARCHAR(1) = '='
SELECT v.v.value('(./text())[1]', 'nvarchar(4000)') [Variable],n.n.value('(./text())[1]', 'nvarchar(4000)') [Value]
FROM (
SELECT x = CONVERT(XML,
'<a><v>' + REPLACE(REPLACE(#List,#Delimiter1,'</n></a><a><v>'),#Delimiter2,'</v><n>') + '</n></a>'
).query('.')
) a
CROSS APPLY x.nodes('a') y(a)
CROSS APPLY y.a.nodes('v') v(v)
CROSS APPLY y.a.nodes('n') n(n)
The best one is to send XML with list of parameters and then parse this XML to the table.
Please let me know if you have any questions.
Update:
So here you need to provide only one value - list of parameters and their values.
Inside the query you can do whatever you want with them.
DECLARE #sql NVARCHAR(MAX),#paramlist NVARCHAR(MAX)
SET #sql = N'
DECLARE #Delimiter1 VARCHAR(1) = '',''
DECLARE #Delimiter2 VARCHAR(1) = ''=''
SELECT v.v.value(''(./text())[1]'', ''NVARCHAR(4000)'') [Variable],n.n.value(''(./text())[1]'', ''NVARCHAR(4000)'') [Value]
INTO #Values
FROM (
SELECT x = CONVERT(XML,
''<a><v>'' + REPLACE(REPLACE(#List,#Delimiter1,''</n></a><a><v>''),#Delimiter2,''</v><n>'') + ''</n></a>''
).query(''.'')
) a
CROSS APPLY x.nodes(''a'') y(a)
CROSS APPLY y.a.nodes(''v'') v(v)
CROSS APPLY y.a.nodes(''n'') n(n)
/*Do whatever you want with the values*/
/*There even could be a stored proc call based on parameters provided*/
SELECT v.Value FROM #Values v WHERE v.Variable = ''c''
DROP TABLE #Values
'
SET #paramlist = '#list nvarchar(max)'
DECLARE #List VARCHAR(MAX) = 'a=1,b=3,c=hey,d=12/05/10,val5='
EXEC sp_executesql #sql, #paramlist, #list=#List
It can be done, but I do not yet know if there are performance implications, and some approaches are open to sql injection.
Some examples are shown here in a secondary question asking specifically about performance using the different syntaxes (some of which are conducive to purely dynamic SQL, others which are not):
Performance differences calling sp_executesql with dynamic SQL vs parameters

SQL variable to hold list of integers

I'm trying to debug someone else's SQL reports and have placed the underlying reports query into a query windows of SQL 2012.
One of the parameters the report asks for is a list of integers. This is achieved on the report through a multi-select drop down box. The report's underlying query uses this integer list in the where clause e.g.
select *
from TabA
where TabA.ID in (#listOfIDs)
I don't want to modify the query I'm debugging but I can't figure out how to create a variable on the SQL Server that can hold this type of data to test it.
e.g.
declare #listOfIDs int
set listOfIDs = 1,2,3,4
There is no datatype that can hold a list of integers, so how can I run the report query on my SQL Server with the same values as the report?
Table variable
declare #listOfIDs table (id int);
insert #listOfIDs(id) values(1),(2),(3);
select *
from TabA
where TabA.ID in (select id from #listOfIDs)
or
declare #listOfIDs varchar(1000);
SET #listOfIDs = ',1,2,3,'; --in this solution need put coma on begin and end
select *
from TabA
where charindex(',' + CAST(TabA.ID as nvarchar(20)) + ',', #listOfIDs) > 0
Assuming the variable is something akin to:
CREATE TYPE [dbo].[IntList] AS TABLE(
[Value] [int] NOT NULL
)
And the Stored Procedure is using it in this form:
ALTER Procedure [dbo].[GetFooByIds]
#Ids [IntList] ReadOnly
As
You can create the IntList and call the procedure like so:
Declare #IDs IntList;
Insert Into #IDs Select Id From dbo.{TableThatHasIds}
Where Id In (111, 222, 333, 444)
Exec [dbo].[GetFooByIds] #IDs
Or if you are providing the IntList yourself
DECLARE #listOfIDs dbo.IntList
INSERT INTO #listofIDs VALUES (1),(35),(118);
You are right, there is no datatype in SQL-Server which can hold a list of integers. But what you can do is store a list of integers as a string.
DECLARE #listOfIDs varchar(8000);
SET #listOfIDs = '1,2,3,4';
You can then split the string into separate integer values and put them into a table. Your procedure might already do this.
You can also use a dynamic query to achieve the same outcome:
DECLARE #SQL nvarchar(8000);
SET #SQL = 'SELECT * FROM TabA WHERE TabA.ID IN (' + #listOfIDs + ')';
EXECUTE (#SQL);
Note: I haven't done any sanitation on this query, please be aware that it's vulnerable to SQL injection. Clean as required.
For SQL Server 2016+ and Azure SQL Database, the STRING_SPLIT function was added that would be a perfect solution for this problem. Here is the documentation:
https://learn.microsoft.com/en-us/sql/t-sql/functions/string-split-transact-sql
Here is an example:
/*List of ids in a comma delimited string
Note: the ') WAITFOR DELAY ''00:00:02''' is a way to verify that your script
doesn't allow for SQL injection*/
DECLARE #listOfIds VARCHAR(MAX) = '1,3,a,10.1,) WAITFOR DELAY ''00:00:02''';
--Make sure the temp table was dropped before trying to create it
IF OBJECT_ID('tempdb..#MyTable') IS NOT NULL DROP TABLE #MyTable;
--Create example reference table
CREATE TABLE #MyTable
([Id] INT NOT NULL);
--Populate the reference table
DECLARE #i INT = 1;
WHILE(#i <= 10)
BEGIN
INSERT INTO #MyTable
SELECT #i;
SET #i = #i + 1;
END
/*Find all the values
Note: I silently ignore the values that are not integers*/
SELECT t.[Id]
FROM #MyTable as t
INNER JOIN
(SELECT value as [Id]
FROM STRING_SPLIT(#listOfIds, ',')
WHERE ISNUMERIC(value) = 1 /*Make sure it is numeric*/
AND ROUND(value,0) = value /*Make sure it is an integer*/) as ids
ON t.[Id] = ids.[Id];
--Clean-up
DROP TABLE #MyTable;
The result of the query is 1,3
In the end i came to the conclusion that without modifying how the query works i could not store the values in variables. I used SQL profiler to catch the values and then hard coded them into the query to see how it worked. There were 18 of these integer arrays and some had over 30 elements in them.
I think that there is a need for MS/SQL to introduce some aditional datatypes into the language. Arrays are quite common and i don't see why you couldn't use them in a stored proc.
There is a new function in SQL called string_split if you are using list of string.
Ref Link STRING_SPLIT (Transact-SQL)
DECLARE #tags NVARCHAR(400) = 'clothing,road,,touring,bike'
SELECT value
FROM STRING_SPLIT(#tags, ',')
WHERE RTRIM(value) <> '';
you can pass this query with in as follows:
SELECT *
FROM [dbo].[yourTable]
WHERE (strval IN (SELECT value FROM STRING_SPLIT(#tags, ',') WHERE RTRIM(value) <> ''))
I use this :
1-Declare a temp table variable in the script your building:
DECLARE #ShiftPeriodList TABLE(id INT NOT NULL);
2-Allocate to temp table:
IF (SOME CONDITION)
BEGIN
INSERT INTO #ShiftPeriodList SELECT ShiftId FROM [hr].[tbl_WorkShift]
END
IF (SOME CONDITION2)
BEGIN
INSERT INTO #ShiftPeriodList
SELECT ws.ShiftId
FROM [hr].[tbl_WorkShift] ws
WHERE ws.WorkShift = 'Weekend(VSD)' OR ws.WorkShift = 'Weekend(SDL)'
END
3-Reference the table when you need it in a WHERE statement :
INSERT INTO SomeTable WHERE ShiftPeriod IN (SELECT * FROM #ShiftPeriodList)
You can't do it like this, but you can execute the entire query storing it in a variable.
For example:
DECLARE #listOfIDs NVARCHAR(MAX) =
'1,2,3'
DECLARE #query NVARCHAR(MAX) =
'Select *
From TabA
Where TabA.ID in (' + #listOfIDs + ')'
Exec (#query)

Is it possible to select a select in SQL without making a nvarchar variable?

I'm trying to do something like this:
--This table has a list of columns I need from #testTableTwo
CREATE TABLE #testTableOne
(
id nvarchar(250)
)
--This column has the values I need. I just don't know which columns I want.
CREATE TABLE #testTableTwo
(
one INT,
two INT,
three INT,
four int
)
INSERT INTO #testTableOne VALUES ('one'),('two'),('three')
INSERT INTO #testTableTwo VALUES (1,2,3,4)
SELECT (SELECT * FROM #testTableOne) FROM #testTableTwo
So what I want this to be is:
SELECT ONE, TWO, THREE FROM #testTableTwo
Is this possible? I know I can make a nvarchar and do some stuff with COALESCE, but I'm trying to avoid that. Is there any good way to do this?
You will have to use dynamic SQL like this.
-- First construct the fields list
DECLARE #fields varchar(500)
SET #fields = STUFF((SELECT ', ' + #testTableOne.id
FROM #testTableOne
FOR XML PATH(''),TYPE).value('.','VARCHAR(MAX)')
, 1, 2, '')
-- Next construct the actual SQL statement
DECLARE #sql nvarchar(max)
SET #sql = 'SELECT '+#fields+' FROM #testTableTwo'
-- And last but not least run it.
EXEC sp_executesql #sql

String Expression to be evaluated to number

I need to write a TSQL user defined function which will accept a string and return a number.
I will call the function like dbo.EvaluateExpression('10*4.5*0.5') should return the number 22.5
Can any one help me to write this function EvaluateExpression.
Currently I am using CLR function which I need to avoid.
Edit1
I know this can be done using stored procedure, but I want to call this function in some statements ex: select 10* dbo.EvaluateExpression('10*4.5*0.5')
Also I have around 400,000 formulas like this to be evaluated.
Edit2
I know we can do it using osql.exe inside function as explained here. But due to permission settings, I can not use this also.
I don't think that is possible in a user defined function.
You could do it in a stored procedure, like:
declare #calc varchar(max)
set #calc = '10*4.5*0.5'
declare #sql nvarchar(max)
declare #result float
set #sql = N'set #result = ' + #calc
exec sp_executesql #sql, N'#result float output', #result out
select #result
But dynamic SQL, like exec or sp_executesql, is not allowed in user defined functions.
Disclaimer: I'm the owner of the project Eval SQL.NET
For SQL 2012+, you can use Eval SQL.NET which can be run with SAFE Permission.
The performance is great (better than UDF) and honors operator precedence and parenthesis. In fact, almost all the C# language is supported.
You can also specify parameters to your formula.
-- SELECT 225.00
SELECT 10 * CAST(SQLNET::New('10*4.5*0.5').Eval() AS DECIMAL(18, 2))
-- SELECT 70
DECLARE #formula VARCHAR(50) = 'a+b*c'
SELECT 10 * SQLNET::New(#formula)
.Val('a', 1)
.Val('b', 2)
.Val('c', 3)
.EvalInt()
Use this Function, It will absolutely working.
CREATE FUNCTION dbo.EvaluateExpression(#list nvarchar(MAX))
RETURNS Decimal(10,2)
AS
BEGIN
Declare #Result Decimal(10,2)
set #Result=1
DECLARE #pos int,
#nextpos int,
#valuelen int
SELECT #pos = 0, #nextpos = 1
WHILE #nextpos > 0
BEGIN
SELECT #nextpos = charindex('*', #list, #pos + 1)
SELECT #valuelen = CASE WHEN #nextpos > 0
THEN #nextpos
ELSE len(#list) + 1
END - #pos - 1
Set #Result=#Result*convert(decimal(10,2),substring(#list, #pos + 1, #valuelen))
SELECT #pos = #nextpos
END
RETURN #Result
END
You Can use this
Select 10* dbo.EvaluateExpression('10*4.5*0.5')
You can use the SQL Stored procedure below to calculate the result of any formula with any number of variables:
I wrote in 2012 a solution which can evaluate any type of Mathematical formula using SQL SERVER. The solution can handle any formula with N variables :
I was asked to find a way to evaluate the value given by a Formula which is filled by the user.
The Formula contains mathematical operations (addition, multiplication, division and subtractions)
The parameters used to calculate the formula are stored in the SQL server DATA BASE.
The solution I found by myself was as follows:
Suppose I have n parameters used to calculate the formula, each of these parameters is stored in one row in one data table.
The data table containing the n rows to use in the formula is called tab_value
I have to store the n values found in n rows (in tab_values) in one single row in one new Table, using SQL cursor,
for that I create a new table called tab_formula
In the cursor, I will add a new column for each value, the column name will be Id1,Id2,Id3 etc.
Then I construct a SQL script containing the formula to evaluate the formula
Here after the complete script, I hope you find it useful, you are welcome to ask me about it.
The procedure uses as input:
-The formula
-A table containing the values used to calculate the formula
if exists(select 1 from sysobjects where name='usp_evaluate_formula' and xtype='p')
drop proc usp_evaluate_formula
go
create type type_tab as table(id int identity(1,1),val decimal(10,2))
go
create proc usp_evaluate_formula(#formula as nvarchar(100),#values as type_tab readonly)
as begin
declare #tab_values table (id int, val decimal(10,2))
insert into #tab_values(id,val) select * from #values
declare #id as int declare #val as decimal(10,2)
if not exists(select 1 from sysobjects where name ='tab_formula')
create table tab_formula(id int identity(1,1), formula nvarchar(1000))
if not exists(select 1 from tab_formula where formula=#formula)
insert into tab_formula(formula) values(#formula)
declare c cursor for select id,val from #tab_values
declare #script as nvarchar(4000)
open c
fetch c into #id,#val
while ##fetch_status=0
begin
set #script = 'if not exists(select 1 from syscolumns c inner join sysobjects o on c.id=o.id where o.name=''tab_formula'' and c.name=''id'+
convert(nvarchar(3),#id)+ ''')
alter table tab_formula add id'+convert(nvarchar(3),#id)+ ' decimal(10,2)'
print #script
exec(#script)
set #script='update tab_formula set id'+convert(nvarchar(3),#id)+'='+convert(nvarchar(10),#val)+' where formula='''+#formula+'''' print #script exec(#script) fetch c into #id,#val end close c deallocate c
set #script='select *,convert(decimal(10,2),'+#formula+') "Result" from tab_formula where formula='''+#formula+''''
print #script
exec(#script)
end
go
declare #mytab as type_tab
insert into #mytab(val) values(1.56),(1.5) ,(2.5) ,(32),(1.7) ,(3.3) ,(3.9)
exec usp_evaluate_formula'2*cos(id1)+cos(id2)+cos(id3)+3*cos(id4)+cos(id5)+cos(id6)+cos(id7)/2*cos(Id6)',#mytab
go
drop proc usp_evaluate_formula
drop type type_tab
drop table tab_formula

Resources