Sql Query to calculate Distance between 2 latitudes and Longitudes - sql-server

This question was posted many a times, but again i had to post the same due to incorrect result what am getting. Can any one help me what am doing wrong.
What i need is the nearest warehouse name for the given customer in Cust_Master and Distance between WH and Customer
I have 2 tables as below.
WH_Master
WH_Name Latitude Longitude
----------- --------- ---------
Horamavu 13.02457 77.65723
White Field 12.985278 77.729899
Hennur 13.030672 77.634034
Cust_Master
Cust_ID Latitude Longitude
------- --------- ---------
Cust-1 13.025579 77.6515
I have tried the below Option and it gives me a wrong distance and location. For the current customer in the example Horamavu is the nearest warehouse and as per the google the distance is 1.8 KM. But am getting 0.751 which is Wrong.
The Query i used is below.
SELECT Top 1 WH_Name, (( 6367.45 * acos( cos( radians(13.025579) ) * cos( radians( Latitude ) ) * cos( radians( Longitude ) - radians(77.6515) ) + sin( radians(13.025579) ) * sin( radians( Latitude ) ) ) )) AS distance_KM FROM WH_Master
Unfortunately this is getting me the same WH_Name and the distance am getting is also wrong. Can you please let me know the correct query. Am using MS SQL Server as my database.

If you are using SQL 2008 or later, you should use a geography data type, and the STDistance function.
eg:
declare #t table (wh_name nvarchar(50), p geography)
insert #t values
('horamavu',geography::STGeomFromText('POINT(13.02457 77.65723)', 4326)),
('white field',geography::STGeomFromText('POINT(12.985278 77.729899)', 4326)),
('hennur', geography::STGeomFromText('POINT(13.030672 77.634034)', 4326))
select wh_name,
p.STDistance(geography::STGeomFromText('POINT(13.025579 77.6515)', 4326)) distance_m
from #t
order by distance_m
Are you sure the google distance is the crow flies distance, and not the via road distance?
As for your original query, if you want TOP you need to specify the ORDER BY

Related

How to show SQL result as percentage

I want to pull a report from the database and currently there is a column which displays the scores. How can I convert the score into % number?
In theory it would be easy but as I'm not an SQL export it's not.
I tried
select [userid], [QuestionsTotal] / 100 * [QuestionsCorrect] as 'score'
FROM myTable
and I also tried
select [userid], [QuestionsTotal] / [QuestionsCorrect] * 100.0 as 'score'
FROM myTable
Could anyone give me some hints on how I can solve my problem?
I'm assuming your Correct and Total are INTs. Notice the *100.0 -- The .0 is important to convert at least one of your factors to something other than INT.
select [userid]
, ([QuestionsCorrect]*100.0) / [QuestionsTotal] as 'score'
FROM myTable
Here is another way to achieve this result that I used (can be used for SUM,COUNT, AVG..):
Select Category as 'Category', COUNT(Category) as 'Count' ,
CAST(COUNT(Category) as DECIMAL(10,4)) / SUM(COUNT(Category) ) OVER () * 100 '% Allocation'
from Category

Complicated SQL selection Statement in Big Query

I have a table with 4 columns, latLng, dataTime, stage and index . I want to query the table in a way that the result would be
Within a time range
no duplicate of latlng, returning the most recent latLng which is of the nature "lat,lng" eg. 23.123,1344
ordered by stage and then index.
within the specified radius of the latLng.
Don't know how to achieve this in sql statement yet, but big query is making matter worse coz statement like distinct is not supported. My options so far just to achieve the first 2 on the list has really being challenging.
SELECT * FROM data.example
WHERE timeCollected IN
(SELECT max(timeCollected) FROM data.example GROUP BY latlng) order by col1,col2,col3
In what way can i achive this, Thanks.
Update
with this statement, i am able to query data within a range and specified time. but still unable to select duplicate rows with most recent latlng (if more than one row has same latlng, it should pick the most recent).
SELECT *, ( 3959 * acos( cos( radians(12.18663) ) * cos( radians( lat ) ) * cos( radians( long) - radians(6.65604) ) + sin( radians(12.18663) ) * sin( radians( lat ) ) ) ) AS distance FROM data.example WHERE TIMESTAMP(timeCollected) <= DATE_ADD(USEC_TO_TIMESTAMP(NOW()), 60, 'minute') HAVING distance < 25 ORDER BY
distance ASC
was able to run do it after some long hours. Don't know how efficient this statement might be but here it is:
SELECT latlng, max(TIMESTAMP(timeCollected)) as timeCollected,first(sessionKey) as session,first(stage) as stage,first(index) as index,
( 3959 * acos( cos( radians(9.0071) ) * cos( radians( lat ) ) * cos( radians( long) - radians(7.56511) ) + sin( radians(9.0071) ) * sin( radians( lat ) ) ) ) AS distance
FROM opendata.openQueryData WHERE TIMESTAMP(timeCollected) > DATE_ADD(USEC_TO_TIMESTAMP(NOW()), -60, 'minute') GROUP BY latlng,distance HAVING distance < 25
order by session,stage,index ASC

Getting Largest Distance from group of GPS Coordinates

So I have a database with multiple rows of GPS coordinates. I know how to calculate the distance from a given lat/lng from any one of them in the database, but what I want to do basically is look at the coordinates of a set of rows and get the two rows that are farthest apart. I'd love it if I could do this in SQL, but if I have to do it in my application code that would work to. Here is what I am doing to calculate the distance between two points:
ROUND(( 3960 * acos( cos( radians( :lat ) ) *
cos( radians( p.latitude ) ) * cos( radians( p.longitude ) - radians( :lng ) ) +
sin( radians( :lat ) ) * sin( radians( p.latitude ) ) ) ),1) AS distance
What we are trying to do is, look at GPS data for a specific user and make sure they aren't moving wildly all over the country. All the coordinates for a user should be within a couple miles at most of each other. A flag that there is malicious activity in our system is if the coordinates are all over the country. So I'd like to be able to quickly run through the data for a spcicic user and know what is the max distance they have been.
I thought about just running a Max/Min on the lat and lng separately and set an internal threshold for what is acceptable. And maybe that is easier, but if what I asked in the first part is possible, that would be best.
If you have SQL Server 2008 or later then you can use GEOGRAPHY to calculate the distance, e.g.:
DECLARE #lat1 DECIMAL(19,6) = 44.968046;
DECLARE #lon1 DECIMAL(19,6) = -94.420307;
DECLARE #lat2 DECIMAL(19,6) = 44.33328;
DECLARE #lon2 DECIMAL(19,6) = -89.132008;
SELECT GEOGRAPHY::Point(#lat1, #lon1, 4326).STDistance(GEOGRAPHY::Point(#lat2, #lon2, 4326));
This makes the problem pretty trivial?
For a set of lats/ longs for a user you would need to calculate the distance between each set and then return the highest distance. Putting this all together, you could probably do something like this:
DECLARE #UserGPS TABLE (
UserId INT, --the user
GPSId INT, --the incrementing unique id associated with this GPS reading (could link to a table with more details, e.g. time, date)
Lat DECIMAL(19,6), --lattitude
Lon DECIMAL(19,6)); --longitude
INSERT INTO #UserGPS SELECT 1, 1, 44.968046, -94.420307; --User #1 goes on a very long journey
INSERT INTO #UserGPS SELECT 1, 2, 44.33328, -89.132008;
INSERT INTO #UserGPS SELECT 1, 3, 34.12345, -92.21369;
INSERT INTO #UserGPS SELECT 1, 4, 44.978046, -94.430307;
INSERT INTO #UserGPS SELECT 2, 1, 44.968046, -94.420307; --User #2 doesn't get far
INSERT INTO #UserGPS SELECT 2, 2, 44.978046, -94.430307;
--Make a working table to store the distances between each set of co-ordinates
--This isn't strictly necessary; we could change this into a common-table expression
DECLARE #WorkTable TABLE (
UserId INT, --the user
GPSIdFrom INT, --the id of the first set of co-ordinates
GPSIdTo INT, --the id of the second set of co-ordinates being compared
Distance NUMERIC(19,6)); --the distance
--Get the distance between each and every combination of co-ordinates for each user
INSERT INTO
#WorkTable
SELECT
c1.UserId,
c1.GPSId,
c2.GPSId,
GEOGRAPHY::Point(c1.Lat, c1.Lon, 4326).STDistance(GEOGRAPHY::Point(c2.Lat, c2.Lon, 4326))
FROM
#UserGPS c1
INNER JOIN #UserGPS c2 ON c2.UserId = c1.UserId AND c2.GPSId > c1.GPSId;
--Note this is a self-join, but single-tailed. So we compare each set of co-ordinates to each other set of co-ordinates for a user
--This is handled by the "c2.GPSID > c1.GPSId" in the JOIN clause
--As an example, say we have three sets of co-ordinates for a user
--We would compare set #1 to set #2
--We would compare set #1 to set #3
--We would compare set #2 to set #3
--We wouldn't compare set #3 to anything (as we already did this)
--Determine the maximum distance between all the GPS co-ordinates per user
WITH MaxDistance AS (
SELECT
UserId,
MAX(Distance) AS Distance
FROM
#WorkTable
GROUP BY
UserId)
--Report the results
SELECT
w.UserId,
g1.GPSId,
g1.Lat,
g1.Lon,
g2.GPSId,
g2.Lat,
g2.Lon,
md.Distance AS MaxDistance
FROM
MaxDistance md
INNER JOIN #WorkTable w ON w.UserId = md.UserId AND w.Distance = md.Distance
INNER JOIN #UserGPS g1 ON g1.UserId = md.UserId AND g1.GPSId = w.GPSIdFrom
INNER JOIN #UserGPS g2 ON g2.UserId = md.UserId AND g2.GPSId = w.GPSIdTo;
Results are:
UserId GPSId Lat Lon GPSId Lat Lon MaxDistance
1 3 34.123450 -92.213690 4 44.978046 -94.430307 1219979.460185
2 1 44.968046 -94.420307 2 44.978046 -94.430307 1362.820895
Now I made a LOT of assumptions about what data you are holding as there was no information about the detail of this in your question. You would probably need to adapt this to some degree?

How to do a batch STDistance using a Table Type with Lat Longs?

I am trying to pass a Table Type into a stored procedure and would like the sproc to look up each row of lat/longs and return to me the nearest point for that row.
Type:
CREATE TYPE dbo.LatLongRoadLinkType AS TABLE
(
Id INT NOT NULL,
Latitude FLOAT NOT NULL,
Longitude FLOAT NOT NULL
);
Stored Proc:
ALTER PROCEDURE [dbo].[BatchNearestRoadNodes]
#Input dbo.LatLongRoadLinkType READONLY
AS
BEGIN
-- do stuff here
-- return a table of id from input, nodeid and distance
END
It needs to do for the whole table what is done here for a single lat/long:
DECLARE #g geography = 'POINT(13.5333414077759 54.549524307251)';
DECLARE #region geography = #g.STBuffer(5000)
SELECT TOP 1 NodeID, Point.STDistance(#g) as 'Distance'
FROM Location
WHERE Point.Filter(#region) = 1
ORDER BY Point.STDistance(#g)
The Location table has the important column Point of type Geography, which is spatially indexed and is what the comparisons are done against.I am sending the table of lat/longs from code into the sproc, and the code is expecting a return of :
Id (original point passed in)
NodeID (of nearest point in location table)
Distance
How should I approach this? To perhaps make it a bit easier I could simply pass in a SqlGeography from my code into the sproc instead of Lat/Long, however that would kill the performance since its very expensive to convert to that.
EDIT:
This works, don't know if its the most optimal solution however.
ALTER PROCEDURE [dbo].[BatchNearestRoadNodes]
#Input dbo.LatLongRoadLinkType READONLY
AS
BEGIN
SELECT x.Id, x.LocationName, x.NodeID, x.Distance
FROM (SELECT I.Id,
L.LocationName,
L.NodeId,
L.Point.STDistance(geography::Point(I.Latitude, I.Longitude, 4326)) AS Distance,
ROW_NUMBER () OVER (PARTITION BY I.Id ORDER BY L.Point.STDistance(geography::Point(I.Latitude, I.Longitude, 4326)) ASC) AS Ranking
FROM #Input AS I
JOIN Location AS L
ON L.Point.STIntersects(geography::Point(I.Latitude, I.Longitude, 4326).STBuffer(5000)) = 1
) AS x WHERE Ranking = 1
END
Performance - V1 vs Jon's Edit
V1
============
original:643 found:627 in:1361 ms
original:1018 found:999 in:1700 ms
original:1801 found:1758 in:2628 ms
original:4098 found:3973 in:5271 ms
original:16388 found:15948 in:19624 ms
Jon's Edit
==========
original:643 found:627 in:1333 ms
original:1018 found:999 in:1689 ms
original:1801 found:1758 in:2559 ms
original:4098 found:3973 in:5114 ms
original:16388 found:15948 in:19054 ms
The difference is minimal. Need to get the last figure down.
Try something like this to get partial results:
WITH PreQuery AS
(
I.Id,
GEOGRAPHY::STPointFromText(I.PointAsWKT).STBuffer(5000) AS Geog,
L.NodeId,
L.Point
FROM
#Input AS I
JOIN
Location AS L ON L.Point.STIntersects(I.Geog) = 1
)
SELECT
P.Id,
P.NodeId,
P.Geog.STDistance(P.Point) AS Distance
FROM
PreQuery P
I've written it from off the head and without any test data so there may be small bugs but in the main it will give you every node and it's distance (within 5000 metres) from every point. You'll still need to filter them to get only the one with the minimum distance for each id - shouldn't be too hard ;-)
Hope it helps somewhat, even if not complete.
EDIT (2nd Dec)
I already see the problem with my first solution, you can't get the distance because it's pre-buffered (to note the main thing). However, this amalgamation should be the most efficient combination of both attempts.
WITH PreQuery AS
(
SELECT
I.Id,
geography::Point(I.Latitude, I.Longitude, 4326) AS InputGeography
FROM
#input AS I
)
SELECT x.Id, x.LocationName, x.NodeId, x.Distance
FROM
(
SELECT
PQ.Id,
L.LocationName,
L.NodeId,
L.Point.STDistance(PQ.InputGeography) AS Distance,
ROWNUMBER() OVER (PARTITION BY I.Id ORDER BY L.Point.Distance(PQ.InputGeography) ASC) AS Ranking
FROM
Prequery AS PQ
JOIN
Location AS L
-- ON L.Point.STIntersects(PQ.InputGeography.STBuffer(5000)) = 1 -- Slower
ON L.Point.STDistance(PQ.InputGeography) <= 5000 -- Faster
) AS X WHERE Ranking = 1
This way, you pre-create the input geography only once, not three times as per your attempt. Again this is untested but should prove the most efficient.

sort by distance using latitude and longitude

I am trying to build a store locator, and am having trouble forming my sql statements. I have the following so far:
SELECT TOP 3 Custno
, ( 3959
* acos( cos( radians(36) )
* cos( radians( Latitude ) )
* cos( radians( Longitude ) - radians(120) )
+ sin( radians(120) ) * sin( radians( Latitude ) )
)
) AS distance
FROM Customers
ORDER BY distance
When I run that statement I get:
Msg 0, Level 11, State 0, Line 0
A severe error occurred on the current command.
The results, if any, should be discarded.
However the query works when I remove the order by clause and when I change the order by clause to use Custno. What is causing this error and how can I avoid it?
Starting with SQL Server 2008 there's a Geography data type which is designed for things like this. Here's a couple links:
http://msdn.microsoft.com/en-us/library/ff929109.aspx
http://blogs.msdn.com/b/isaac/archive/2008/10/23/nearest-neighbors.aspx

Resources