Find distance between two points with longitude and latitude - sql

I got an answer to this question from this website however the answer I get is wrong.
DECLARE #orig_lat DECIMAL
DECLARE #orig_lng DECIMAL
SET #orig_lat=52.676 set #orig_lng=-1.6193
DECLARE #orig geography = geography::Point(#orig_lat, #orig_lng, 4326);
SELECT #orig.STDistance(geography::Point(Latitude, longitude, 4326)) AS distance
From ...
However I get the wrong answer
e.g. distance 234229 latitude 55.0853 and longitude -1.595
I have to admit I just copied the code and Don't understand it. The answer should be 166 miles which is 267 km.
Any ideas?

Short answer:
Make #orig_lat and #orig_lng of type decimal(19,6) (for example) or float.
Long answer:
The issue is because your decimals have no actual decimals (you are using the default precision, which is decimal(18,0)), so they end up being 53 and -2. You should define their precision (e.g. decimal(19,6)) or just use float, which is the type the function expects anyway. If you simply change this, it works fine:
DECLARE #orig_lat float
DECLARE #orig_lng float
SET #orig_lat=52.676 set #orig_lng=-1.6193
DECLARE #orig geography = geography::Point(#orig_lat, #orig_lng, 4326);
DECLARE #dest geography = geography::Point(55.0853, -1.595, 4326);
select #orig.STDistance(#dest)
This returns 268166.415685712.
I discovered this by simply printing the varchar equivalent of the geography:
DECLARE #orig_lat decimal
DECLARE #orig_lng decimal
SET #orig_lat=52.676 set #orig_lng=-1.6193
DECLARE #orig geography = geography::Point(#orig_lat, #orig_lng, 4326);
select cast(#orig as varchar)
That will print POINT (-2 53), which also gives you another piece of information besides the rounding: that varchar format uses longitude-latitude instead of latitude-longitude. So if you wanted to create those points the other way, you should use:
DECLARE #orig geography = 'POINT(-1.6193 52.676)'
DECLARE #dest geography = 'POINT(-1.595 55.0853)'
SELECT #orig.STDistance(#dest)

The answer is in meters, divide by 1000 and you have km.
I try to break it down to you:
-- I assume you already know that this part is only declaring variables
-- and setting them.
-- In this case they are the variables for your starting coordinates.
DECLARE #orig_lat DECIMAL
DECLARE #orig_lng DECIMAL
SET #orig_lat=52.676 set #orig_lng=-1.6193
-- MS SQL has built in geography functions
-- like a function to determine the distance between 2 points.
-- But it uses an own unit instead of longitude/latitude: point.
-- So you have to use the geography::Point-function to convert
-- a lat/lon coordinate into a point value:
DECLARE #orig geography = geography::Point(#orig_lat, #orig_lng, 4326);
-- The rest is basically the same again with the target coordinates,
-- namely this part: geography::Point(Latitude, longitude, 4326)
-- embedded in the STDistance function. This function calculates the distance
-- between 2 point values.
SELECT #orig.STDistance(geography::Point(Latitude, Longitude, 4326)) AS distance
From ...
-- where Latitude and Longitude would be your destination coordinates.
If you want you can write your own statement, the mathematical background of this is the Haversine formula basically measuring the distance between 2 points on a sphere.

A more simplified way is this:
DECLARE #source geography = 'POINT(52.676 -1.6193)'
DECLARE #target geography = 'POINT(55.0853 -1.595)'
SELECT #source.STDistance(#target)

Related

Sql server rounding issue down to 2 decimal places

I am having a problem getting my "Rounding to 2 decimal places" to behave as I would expect.
Try this trivial example
declare #num numeric (18,2)
declare #vat numeric (18,2)
set #num = 11729.83
set #vat = 1.14
select round(#num/#vat,2)
I am getting an answer of 10289.320000 but I should be getting 10289.33 . The full un rounded number is 10289.324561403508771929824561404 (unless my maths is completely off)
Try this
select cast(round(#num/#vat,3) as decimal(18,2))
Round can either return a value lower than the original, or a value upper than the original. In fact it returns the value closest to the original.
If you want to systematically round a number to its lower or upper value, you could then use FLOOR or CEILING (Thanks #GarethD for refreshing my memory on CEILING...)
select round(floor(100*#num/#vat)/100,2) -> lower value
select round(ceiling(100*#num/#vat)/100,2) -> upper value
Otherwise round will indeed return 10289.32 when the value is strictly lower than 10289.325 (which is the case here)
Try converting to a decimal:
select cast(round(#num / #vat, 2) as numeric(18, 2))
I advocate round() to be explicit about the conversion method.
Try this
declare #num numeric (18,2)
declare #vat numeric (18,2)
set #num = 11729.83
set #vat = 1.14
select round(Convert(Decimal(18,3),(#num/#vat)),2)
I am not sure if I understand correctly. But maybe you've been looking for something like this:
select round(round(round(round(#num / #vat, 5), 4), 3), 2)
You will need to convert it to the appropriate decimal type again:
SELECT CONVERT(DECIMAL(18,2), ROUND(#NUM/#VAT,2))

SQL points around line between A and B

In SQL Server 2014, I have a database with Geometry points - City
Driving from City A to City B gives me a line (we take an airplane).
I need to find points in my database - which are in certain distance (10 miles) "off-track" of this line.
I know how to find the closest points around a single point, how to calculate the distance between them - but - how can I search along this line? Like POI in your Navi...
DECLARE #g geography
SELECT #g = Geo_LatLong_deg
FROM airports
WHERE iata_code = 'MyAirportCode' -- radius 100km
SELECT *
FROM airports
WHERE #g.STDistance(Geo_LatLong_deg) <= 100000
Use the STBuffer method. Assuming that you've got some way to determine your path as a geography instance, it's as simple as:
declare #distance float = 16.09344 --10 miles in km
select *
from airports
where #path.STBuffer(#distance).STIntersects(Geo_LatLong_deg) = 1
By way of explanation, the STBuffer() method creates a region that is the set of points within 10 miles of your path. Then, we select all points from your table that intersect with that region with STIntersects().
Thank you for your help. I mixed up Long/Lat sequence in string... now I get the results as expected.
here the code - if others want to see how to combine two or more points - together with the area around the line(s).
DECLARE #BuildString NVARCHAR(MAX)
SELECT #BuildString = COALESCE(#BuildString + ',', '') + CAST(longitude_deg AS NVARCHAR(50)) + ' ' + CAST(latitude_deg AS NVARCHAR(50))
FROM dbo.airports where iata_code='RLG' or iata_code='FRA'
ORDER BY ID
SET #BuildString = 'LINESTRING(' + #BuildString + ')';
DECLARE #LineFromPoints geography = geography::STLineFromText(#BuildString, 4326);
declare #distance float = 50000
select *
from airports
where #LineFromPoints.STBuffer(#distance).STIntersects(airports.GEO_LatLong_deg) = 1 and type<>'heliport'

SQL Server 2012 Spatial Index Nearest Neighbor query

I have created a table containing US zipcodes along with the latitude and longitude of these zip codes. From the latitude and longitude I created a geography column. I created a spatial index on that geography column. I want to return the closest 100 zip codes to a latitude and longitude so I created the following stored procedure
CREATE PROCEDURE [dbo].[Geo_Locate]
#Latitude DECIMAL,
#Longitude DECIMAL
AS
BEGIN
SET NOCOUNT ON;
DECLARE #GeogTemp NVARCHAR(200)
DECLARE #Geog GEOGRAPHY
-- Create a geography type variable from the passed in latitude and longitude. This will be used to find the closest point in the database
SET #GeogTemp = 'POINT(' + convert(NVARCHAR(100), #Longitude) + ' ' + convert(NVARCHAR(100), #Latitude) + ')'
SET #Geog = geography::STGeomFromText(#GeogTemp, 4326)
-- Run the main query
SELECT TOP 100
[Id],
[Country],
[ZipPostalCode],
[City],
[County],
[Latitude],
[Longitude],
[GeographyCol],
GeographyCol.STDistance(#Geog) AS Distance
FROM
[dbo].[ZipCode] WITH(INDEX(ZipCode_SpatialIndex))
WHERE
GeographyCol.STDistance(#Geog) < 100000 -- 100 KM
ORDER BY
GeographyCol.STDistance(#Geog) ASC
END
However when I pass the latitude = 35.48330 and longitude = -97.17340 to this stored procedure I get the following returned at item 55
869531 US 73045 Harrah Oklahoma OK 35.48330 -97.17340 0xE6100000010C12143FC6DCBD4140174850FC184B58C0 55894.2236191955
The last column is the distance. Basically the query is saying that this record is nearly 56KM from the entered point but the latitude and longitude are the same.
I've read MSDN and my query looks correct. Can anyone help me please?
This one was a doozy and I'm not sure I got it. I started with trying to replicate in purest terms what you were seeing: incorrect STDistance() between two geography points.
DECLARE #zipcode geography
DECLARE #searchpoint geography
SET #zipcode = geography::STPointFromText('POINT(-97.17340 35.48330)', 4326);
SET #searchpoint = geography::STPointFromText('POINT(-97.17340 35.48330)', 4326);
SELECT #zipcode.STDistance(#searchpoint)
The results is, correctly, 0 meters (SRID 4326 uses meters as the distance unit), so I kept scratching my head. The only thing I noticed that I can't replicate is this: you flipped between geography and geometry (your code from question):
SET #Geog = geography::STGeomFromText(#GeogTemp, 4326)
When I plopped that into my code I still got 0 for distance. So my only question, and perhaps you could post the T-SQL, is about your spatial index. I think the STDistance() calculation is going to use the index. So without seeing the index and knowing that you hopped between Geography and Geometry at least in this instance, I'd wonder if the odd results are in there since I can't replicate it with my above code.

Is there any simple way to format decimals in T-SQL?

I know it could be done trivially in a non-SQL environment [post-data processing, frontend, what have you], but that's not possible at the moment. Is there a way to take a decimal(5,2) and convert it to a varchar without the trailing zeroes/decimal points? For example:
declare #number decimal(5,2)
set #number = 123.00
select cast(#number as varchar) as FormattedNumber
And the result is '123.00'. Is there a (simple) way to get '123' instead? And likewise, instead of '123.30', '123.3'? Could do it by figuring out whether or not the hundredths/tenths places were 0 and manually trimming characters, but I wanted to know if there was a more elegant solution.
What about:
SELECT CAST(CAST(#number AS float) AS varchar(10))
However you may want to test this carefully with your raw data first.
This way is pretty simple:
DECLARE #Number DECIMAL(5,2)
SELECT #Number = 123.65
SELECT FormattedNumber = CAST(CAST(#Number AS DECIMAL(3,0)) AS VARCHAR(4))
Returns '124'.
The only thing to consider is whether you want to round up/down, or just strip the zeroes and decimal points without rounding; you'd cast the DECIMAL as an INT in the second case.
For controlled formatting of numbers in T-SQL you should use the FORMAT() function. For example:
DECLARE #number DECIMAL(9,2); SET #number = 1234567.12;
DECLARE #formatted VARCHAR(MAX); SET #formatted = FORMAT(#number, 'N0', 'en-AU');
PRINT #formatted;
The result will be:
1,234,567
The arguments to the FORMAT() function are:
FORMAT(value, format [, culture])
The value argument is your number. The format argument is a CLR type formatting string (in this example, I specified "normal number, zero precision"). The optional culture argument allows you to override the server culture setting to format the number as per a desired culture.
See also the MSDN ref page for FORMAT().
The Convert function may do what you want to do.
ms-help://MS.SQLCC.v9/MS.SQLSVR.v9.en/tsqlref9/html/a87d0850-c670-4720-9ad5-6f5a22343ea8.htm
Let me try this again....
CREATE FUNCTION saneDecimal(#input decimal(5,2)) returns varchar(10)
AS
BEGIN
DECLARE #output varchar(10)
SET #output = CAST(#input AS varchar(10))
DECLARE #trimmable table (trimval char(1))
INSERT #trimmable VALUES ('0')
INSERT #trimmable VALUES ('.')
WHILE EXISTS (SELECT * FROM #trimmable WHERE trimval = CAST(SUBSTRING(#output, LEN(#output), 1) AS char(1)))
SET #output = LEFT(#output, LEN(#output) - 1)
RETURN #output
END
GO
SELECT dbo.saneDecimal(1.00)
You could strip the trailing zeroes in a while loop:
declare #number decimal(5,2)
declare #str varchar(100)
set #number = 123.00
set #str = #number
while substring(#str,len(#str),1) in ('0','.',',')
set #str = substring(#str,1,len(#str)-1)
But as AdaTheDev commented, this is more easily done client-side.
Simple and elegant? Not so much...but that's T-SQL for you:
DECLARE #number decimal(5,2) = 123.00
DECLARE #formatted varchar(5) = CAST(#number as varchar)
SELECT
LEFT(
#formatted,
LEN(#formatted)
- PATINDEX('%[^0.]%', REVERSE(#formatted))
+ 1
)
Use the Format(value,format string,culture) function in SQL Server 2012+
If you have SQL Server 2012 or Greater you can use the format function like this:
select format(#number,'0') as FormattedNumber
Of course the format function will return an nvarchar, and not a varchar. You can cast to get a specific type.
Also, take a look at the T-SQL STR function in Books Online; this can be used for formatting floats and might work for your case. For some reason it doesn't come up in Google searches relating to this problem.

SQL Server, division returns zero

Here is the code I'm using in the example:
PRINT #set1
PRINT #set2
SET #weight= #set1 / #set2;
PRINT #weight
Here is the result:
47
638
0
I would like to know why it's returning 0 instead of 0,073667712
Either declare set1 and set2 as floats instead of integers or cast them to floats as part of the calculation:
SET #weight= CAST(#set1 AS float) / CAST(#set2 AS float);
When you use only integers in a division, you will get integer division. When you use (at least one) double or float, you will get floating point division (and the answer you want to get).
So you can
declare one or both of the variables as float/double
cast one or both of the variables to float/double.
Do not just cast the result of the integer division to double: the division was already performed as integer division, so the numbers behind the decimal are already lost.
Simply mutiply the bottom of the division by 1.0 (or as many decimal places as you want)
PRINT #set1
PRINT #set2
SET #weight= #set1 / #set2 *1.00000;
PRINT #weight
Because it's an integer. You need to declare them as floating point numbers or decimals, or cast to such in the calculation.
if you declare it as float or any decimal format it will display
0
only
E.g :
declare #weight float;
SET #weight= 47 / 638; PRINT #weight
Output : 0
If you want the output as
0.073667712
E.g
declare #weight float;
SET #weight= 47.000000000 / 638.000000000; PRINT #weight
In SQL Server direct division of two integer returns integer even if the result should be the float. There is an example below to get it across:
--1--
declare #weird_number_float float
set #weird_number_float=22/7
select #weird_number_float
--2--
declare #weird_number_decimal decimal(18,10)
set #weird_number_decimal=22/7
select #weird_number_decimal
--3--
declare #weird_number_numeric numeric
set #weird_number_numeric=22/7
select #weird_number_numeric
--Right way
declare #weird_number float
set #weird_number=cast(22 as float)/cast(7 as float)
select #weird_number
Just last block will return the 3,14285714285714. In spite of the second block defined with right precision the result will be 3.00000.