Divide the coordinates into longtitue and latitude - sql-server

I have coordinates in my database stored as (55.573012889640765, 9.72362365248182). I want to make a function that will get this value and put them on #latitude = 55.573012889640765 and
#long=9.72362365248182.
So practically the function gets the coordinates and returns me the two points separately . I want to get this so I can calculate the distance between two points later on with a function like this one:
CREATE FUNCTION dbo.fnCalcDistanceKM(#lat1 FLOAT, #lon1 FLOAT, #lat2 FLOAT, #lon2 FLOAT)
RETURNS FLOAT
AS
BEGIN
RETURN ACOS(SIN(PI()*#lat1/180.0)*SIN(PI()*#lat2/180.0)+COS(PI()*#lat1/180.0)*COS(PI()*#lat2/180.0)*COS(PI()*#lon2/180.0-PI()*#lon1/180.0))*6371
END
As you see this function requires the points to be separated and in my database I have them as one .
Can you please le me know how to divide the parts or modify the above function to fit my code.
Thank you in advance

You can save yourself some effort by using SQL's geometry functions.
declare #coords1 nvarchar(64) = '(55.573012889640765, 9.72362365248182)'
, #coords2 nvarchar(64) = '(56.573012889640765, 9.72362365248182)'
declare #g1 geometry = geometry::STGeomFromText('POINT' + replace(#coords1,',',' '), 0)
, #g2 geometry = geometry::STGeomFromText('POINT' + replace(#coords2,',',' '), 0)
SELECT #g1.STDistance(#g2);
More info here: http://msdn.microsoft.com/en-us/library/bb933952.aspx
Alternatively, if you're just looking to split the string around the comma, take a look at this example: How do I split a string so I can access item x?

I prefer JonLBevan's answer but this is literally what was asked for:
DECLARE #string nvarchar(max) = '(55.573012889640765, 9.72362365248182)';
SELECT #string;
DECLARE #latitude float;
DECLARE #long float;
SELECT #latitude = CONVERT(float, SUBSTRING(#string, 2, charindex(',', #string)-2)),
#long = CONVERT(float, SUBSTRING(#string, charindex(',', #string)+1, LEN(#string)-charindex(',', #string)-1));
SELECT #latitude, #long;
It is worth pointing out though, that it would be better not to store the values in this way as you are unable to take any advantage of indexing when doing range searches due to the functions involved.

Related

SQL Server Geography data type and distances between all combinations of points

I'm new to SQL Server's capabilities regarding the Geography data type and how it can be used to calculate distances between two points. However, with the help of a good YouTube video, I was able to quickly write a query to find all the rows in a Locations table that are within a defined radius of specific lat/long point.
DECLARE #Lat DECIMAL(12,9)
DECLARE #Long DECIMAL(12,9)
DECLARE #Miles INT
DECLARE #Meters FLOAT
-- Coordinates set to a reference point
SET #Lat = 29.761209
SET #Long = -95.383513
SET #Miles = 15
SET #Meters = #Miles * 1609.34
DECLARE #Orig GEOGRAPHY = GEOGRAPHY::Point(#Lat, #Long, 4326)
SELECT
#Orig.STDistance(GEOGRAPHY::Point(latitude, longitude, 4326)) / 1609.34 As MilesDistance,
*
FROM Locations
WHERE #Orig.STDistance(GEOGRAPHY::Point(latitude, longitude, 4326)) <= #Meters
AND latitude IS NOT NULL AND longitude IS NOT NULL
ORDER BY 1
But I need to take this a step further now. Rather than comparing the Locations to a single reference point, I need to compare them to a set of multiple points. This will basically be a cartesian join returning distances for all combinations of my Location records and lat/long values that I pull from another table, which will presumably take the place of my #Orig variable that was created from a single point.
OK, so I was able to come up with an effective solution. I first created a SQL function:
ALTER FUNCTION [dbo].[MilesBetweenTwoPoints]
(
#LatA DECIMAL(10,6),
#LongA DECIMAL(10,6),
#LatB DECIMAL(10,6),
#LongB DECIMAL(10,6)
)
RETURNS DECIMAL(10,6) AS
BEGIN
DECLARE #Miles DECIMAL(10,6)
DECLARE #PointA GEOGRAPHY = GEOGRAPHY::Point(#LatA, #LongA, 4326)
DECLARE #PointB GEOGRAPHY = GEOGRAPHY::Point(#LatB, #LongB, 4326)
SELECT #Miles = #PointA.STDistance(#PointB) / 1609.34
RETURN #Miles
END
And then I modified my query to look something like this:
SELECT L1.latitude, L1.longitude, L2.latitude, L2.longitude,
dbo.MilesBetweenTwoPoints(L1.latitude, L1.longitude, L2.latitude, L2.longitude) As DistanceMiles
FROM Locations L1
INNER JOIN OtherLocations L2 ON 1=1
WHERE dbo.MilesBetweenTwoPoints(L1.latitude, L1.longitude, L2.latitude, L2.longitude) < #Miles

What is the encode(<columnName>, 'escape') PostgreSQL equivalent in SQL Server?

In the same vein as this question, what is the equivalent in SQL Server to the following Postgres statement?
select encode(some_field, 'escape') from only some_table
As you were told already, SQL-Server is not the best with such issues.
The most important advise to avoid such issues is: Use the appropriate data type to store your values. Storing binary data as a HEX-string is running against this best practice. But there are some workarounds:
I use the HEX-string taken from the linked question:
DECLARE #str VARCHAR(100)='0x61736461640061736461736400';
--here I use dynamically created SQL to get the HEX-string as a real binary:
DECLARE #convBin VARBINARY(MAX);
DECLARE #cmd NVARCHAR(MAX)=N'SELECT #bin=' + #str;
EXEC sp_executeSql #cmd
,N'#bin VARBINARY(MAX) OUTPUT'
,#bin=#convBin OUTPUT;
--This real binary can be converted to a VARCHAR(MAX).
--Be aware, that in this case the input contains 00 as this is an array.
--It is possible to split the input at the 00s, but this is going to far...
SELECT #convBin AS HexStringAsRealBinary
,CAST(#convBin AS VARCHAR(MAX)) AS CastedToString; --You will see the first "asda" only
--If your HEX-string is not longer than 10 bytes there is an undocumented function:
--You'll see, that the final AA is cut away, while a shorter string would be filled with zeros.
SELECT sys.fn_cdc_hexstrtobin('0x00112233445566778899AA')
SELECT CAST(sys.fn_cdc_hexstrtobin(#str) AS VARCHAR(100));
UPDATE: An inlinable approach
The following recursive CTE will read the HEX-string character by character.
Furthermore it will group the result and return two rows in this case.
This solution is very specific to the given input.
DECLARE #str VARCHAR(100)='0x61736461640061736461736400';
WITH recCTE AS
(
SELECT 1 AS position
,1 AS GroupingKey
,SUBSTRING(#str,3,2) AS HEXCode
,CHAR(SUBSTRING(sys.fn_cdc_hexstrtobin('0x' + SUBSTRING(#str,3,2)),1,1)) AS TheLetter
UNION ALL
SELECT r.position+1
,r.GroupingKey + CASE WHEN SUBSTRING(#str,2+(r.position)*2+1,2)='00' THEN 1 ELSE 0 END
,SUBSTRING(#str,2+(r.position)*2+1,2)
,CHAR(SUBSTRING(sys.fn_cdc_hexstrtobin('0x' + SUBSTRING(#str,2+(r.position)*2+1,2)),1,1)) AS TheLetter
FROM recCTE r
WHERE position<LEN(#str)/2
)
SELECT r.GroupingKey
,(
SELECT x.TheLetter AS [*]
FROM recCTE x
WHERE x.GroupingKey=r.GroupingKey
AND x.HEXCode<>'00'
AND LEN(x.HEXCode)>0
ORDER BY x.position
FOR XML PATH(''),TYPE
).value('.','varchar(max)')
FROM recCTE r
GROUP BY r.GroupingKey;
The result
1 asdad
2 asdasd
Hint: Starting with SQL Server 2017 there is STRING_AGG(), which would reduce the final SELECT...
If you need this functionality, it's going to be up to you to implement it. Assuming you just need the escape variant, you can try to implement it as a T-SQL UDF. But pulling strings apart, working character by character and building up a new string just isn't a T-SQL strength. You'd be looking at a WHILE loop to count over the length of the input byte length, SUBSTRING to extract the individual bytes, and CHAR to directly convert the bytes that don't need to be octal encoded.1
If you're going to start down this route (and especially if you want to support the other formats), I'd be looking at using the CLR support in SQL Server, to create the function in a .NET language (C# usually preferred) and use the richer string manipulation functionality there.
Both of the above assume that what you're really wanting is to replicate the escape format of encode. If you just want "take this binary data and give me a safe string to represent it", just use CONVERT to get the binary hex encoded.
1Here's my attempt at it. I'd suggest a lot of testing and tweaking before you use it in anger:
create function Postgresql_encode_escape (#input varbinary(max))
returns varchar(max)
as
begin
declare #i int
declare #len int
declare #out varchar(max)
declare #chr int
select #i = 1, #out = '',#len = DATALENGTH(#input)
while #i <= #len
begin
set #chr = SUBSTRING(#input,#i,1)
if #chr > 31 and #chr < 128
begin
set #out = #out + CHAR(#chr)
end
else
begin
set #out = #out + '\' +
RIGHT('000' + CONVERT(varchar(3),
(#chr / 64)*100 +
((#chr / 8)%8)*10 +
(#chr % 8))
,3)
end
set #i = #i + 1
end
return #out
end

Format a numeric field as text with fixed pattern sql server 2008

I have a numeric field (field1) that has numeric values 1.2, 23.72, 14.02 etc.
I need to present this as a fixed text field format 13 characters as 000000000.000 e.g. 23.72 must display 000000023.720 (9 digits with 3 decimals)Server 2008
What is the best way to do this ?
You can use the following query:
SELECT FORMAT(ColumnName, '000000000.000') FROM TableName
I'm not saying this is the best way, as the final presentation layer (your webpage) might still render it as a numeric value that will follow the format defined on that layer.
You can use FORMAT(#input, '000000000.000') but that won't give you intended output for longer numbers.
You can make your own function which pads zeros ahead and otherwise stops your execution in case of longer numbers.
DECLARE #input NUMERIC(20,7);
DECLARE #number NUMERIC(12,3);
DECLARE #output VARCHAR(13)
SET #input = 123620;
SET #number = CONVERT(NUMERIC(12,3), #input)
SET #output = CONVERT(CHAR(13), #number)
SET #output = REPLICATE('0', 10 - CHARINDEX('.',#output)) + #output
try this,
Declare #i float=23.72
declare #j decimal(18,3)=#i
select #j
select replicate('0',13-len(#j))+cast(#j as varchar)
and tell that for which data it do not work.
Or try this one
SELECT LEFT(RIGHT(REPLICATE('0',12) + CAST('23.72' AS VARCHAR(10)),12) + REPLICATE('0',3),13)
output
000000023.720

Extract a number after a number pattern in SQL

I am working in SQL server. I have some number like 130-0029. I need to get the integer after the - delimiter out of it. So in this example I need to return 29. These are the following scenarios I have with this,
The pattern may differ like 130-00000029 or 130-0029.
If there are all 0's then print 0, else get actual number like 29.
Please advise/suggest me on this.
Here is an example:
declare #s varchar(100) = '130-0029'
select cast(substring(#s, patindex('%[-]%', #s) + 1, len(#s)) as int)
You may need to cast to some numeric if the number overflows ineteger type.
Try this:
DECLARE #num varchar(50) = '130-0029'
SELECT CAST(SUBSTRING(#num, CHARINDEX('-', #num) + 1, LEN(#num)) AS INT)
Here is a fiddle.
This also works if the - is missing. But if what follows is not a number it will give an error.
declare #string as varchar(100)
set #string = '130-0029'
select convert(int,Right(#string, LEN(#string)-CHARINDEX('-', #string)))
Probably not so different than other answers, but this works using STUFF too:
DECLARE #String VARCHAR(10) = '130-0029';
SELECT CONVERT(INT, STUFF(#String, 1, CHARINDEX('-', #String), ''));
See this running in Query Stack Exchange.

How do I write an any-number-of-params function in SQL Server

Coalesce seems to work with any number of parameters and return the first one that is not null. How can I write a function like that? One that does not have a fixed number of parameters?
An example of the usage of a function fMax:
select Length = dbo.fMax(box.Height, box.Width, box.Depth)
from dbo.tBox box
where box.ShipmentId = 1234
With such a function I would not have to write something like this:
select Length = (
select MAX(side)
from (values (box.Height), (box.Width), (box.Depth)) as sides(side))
from dbo.tBox box
where box.ShipmentId = 1234
If you use SQL Server 2008 and above you can use the Table-Valued Parameters.
Unfortunately, after trying that out, I am pretty sure that you cannot write COALESCE-like functions yourself in T-SQL.
I was pretty sure that you can use socalled CLR-Functions that you can code for example in C# to make up for some lacking features in SQL-Server.
However I have to agree with the comment below, that this does not free you from the need to provide a parameter list in sql to introduce the new function in which you still would have that restriction.
So in short, you cannot code such functions yourself for T-SQL.
As the other respondants say, I don't think you can do exactly what you ask for.
However I think you could make a reasonable approximation using default parameters. If you know a reasonable upper limit, the you could define a function something like this:
--Edit
Turns out you can't have default values on a UDF. Or rather you can define them, but you still have to specify the values when calling the function
That means the best you can do is to have function with the maximum number of parameters:
CREATE FUNCTION dbo.fMax
(
#Value1 float,
#Value2 float,
#Value3 float,
#Value4 float
)
RETURNS float
AS
BEGIN
DECLARE #Result float
SELECT #Value1 = COALESCE(#Value1, #Value2, #Value3, #Value4)
SELECT #Value2 = COALESCE(#Value2, #Value1, #Value3, #Value4)
SELECT #Value3 = COALESCE(#Value3, #Value1, #Value2, #Value4)
SELECT #Value4 = COALESCE(#Value4, #Value1, #Value2, #Value3)
SELECT #Result = #Value1
IF (#Value2 > #Result)
SELECT #Result = #Value2
IF (#Value3 > #Result)
SELECT #Result = #Value3
IF (#Value4 > #Result)
SELECT #Result = #Value4
RETURN #Result
END
and call it like this:
SELECT dbo.fMax(1, 5, 4, null)

Resources