Reputation: 1047
Situation
I'm working on a project where my GPS sends latitude and longitude to a SQL Server database. This all works fine. Then I have a report that calls a stored procedure that returns latitude/longitude and other information.
Then using any of my latitude and longitude, I have a CTE that returns the nearest city based on the latitude/longitude.
Difficulty
The difficulty that I am experiencing right now, is that I am stomped for a way to tie in those two queries in such a way that when I'm running my report the name of my city is retrieved as well.
As mentioned the stored procedure returns the Lat/lng and the CTE (maybe it should be a function ) gets me the name from the Lat/lng (right now it's hard coded).
Stored procedure:
ALTER PROCEDURE [dbo].[usp_MovementRepP]
@GPSID nvarchar(50),
@rSdate date,
@rEdate date
AS
SELECT
SUBSTRING(CONVERT(varchar(12), CreateTime, 120), 1, 10) as RecordDate,
SUBSTRING(CONVERT(varchar(12), CreateTime, 114), 0, 13) as RecordTime,
Latitude, Longitude, DeviceId,
*
FROM
locationhistory A
INNER JOIN
devices B ON A.DeviceId = B.id
WHERE
DeviceName = @GPSID
AND SUBSTRING(CONVERT(varchar(8), CreateTime, 112), 1, 10) BETWEEN @rSdate AND @rEdate
CTE that gets me cityname/city ID based on the lat/long it's supplied:
;WITH Distance_CTE ([geonameid], distance) AS
-- Define the CTE query.
(
SELECT
[geonameid],
(3959 * acos(cos(radians(15.312015)) * cos(radians(latitude)) * cos(radians(longitude) - radians(-61.388295)) + sin(radians(15.312015)) * sin(radians(latitude )))) AS distance
FROM
[GeoNames]
)
-- Define the outer query referencing the CTE name.
SELECT TOP 1 [geonameid], distance
FROM Distance_CTE
WHERE DISTANCE < 20
ORDER BY DISTANCE
EDIT
Results from the stored procedure:
ID Deviceid Latitude Longitude Speed Dir CreateTime
505 4 15.31156 -61.3880467 6.52 0 2016-12-19 11:41:59.000
506 4 15.3175567 -61.39057 32.47 0 2016-12-19 11:43:00.000
507 4 15.32653 -61.39533 36.91 0 2016-12-19 11:44:01.000
508 4 15.330445 -61.3912917 13.45 0 2016-12-19 11:45:01.000
509 4 15.334425 -61.39001 20.46 0 2016-12-19 11:46:02.000
510 4 15.334445 -61.389295 14.57 0 2016-12-19 11:47:05.000
Results from CTE
geonameid distance
9063952 0.173565252078886
When I pass in a latitude/longitude to the CTE a geonameid is returned. What I am trying to do is, get this geonameid INTO my stored procedure.
Hope it's a bit clearer
Upvotes: 0
Views: 1543
Reputation: 859
I haven't tested this (update: confirmed to work after removing a semi-colon from function), but I believe it will do what you need -- possibly too slowly though. You may need to fix the parameter types, and just the cross apply line has been added to your stored proc.
CREATE FUNCTION [dbo].[GetCity]
(
@Latitude decimal(9,6), @Longitude decimal(9,6)
)
RETURNS TABLE
AS
RETURN
WITH Distance_CTE ([geonameid], distance)
AS
-- Define the CTE query.
(
SELECT [geonameid], (3959 * acos(cos(radians(@Latitude)) * cos(radians(latitude)) * cos(radians(longitude) - radians(@Longitude)) + sin(radians(@Latitude)) * sin(radians(latitude )))) AS distance
FROM [GeoNames]
)
-- Define the outer query referencing the CTE name.
SELECT top 1 [geonameid], distance
FROM Distance_CTE
WHERE DISTANCE < 20
order by DISTANCE
ALTER PROCEDURE [dbo].[usp_MovementRepP]
@GPSID nvarchar(50),
@rSdate date,
@rEdate date
AS
SELECT SUBSTRING(CONVERT(varchar(12), CreateTime, 120),1,10) as RecordDate,
SUBSTRING(CONVERT(varchar(12), CreateTime, 114),0,13) as RecordTime,Latitude,Longitude,DeviceId,
* FROM locationhistory A
INNER JOIN devices B
ON A.DeviceId=B.id
CROSS APPLY dbo.GetCity(Latitude, Longitude)
WHERE
DeviceName=@GPSID
AND
SUBSTRING(CONVERT(varchar(8), CreateTime, 112),1,10)
BETWEEN @rSdate AND @rEdate
Upvotes: 3
Reputation: 17271
You will need to use CROSS APPLY to get the nearest city. An example would look like:
SELECT SUBSTRING(CONVERT(varchar(12), CreateTime, 120),1,10) as RecordDate,
SUBSTRING(CONVERT(varchar(12), CreateTime, 114),0,13) as RecordTime,Latitude,Longitude,DeviceId,
* FROM locationhistory A
cross apply
(
SELECT top 1 [geonameid], (3959 * acos(cos(radians(A.Latitude)) * cos(radians(g.latitude)) * cos(radians(g.longitude) - radians(A.Longitude)) + sin(radians(A.Latitude)) * sin(radians(g.latitude )))) AS distance
FROM [GeoNames] g
ORDER BY distance
) nearest
INNER JOIN devices B
ON A.DeviceId=B.id
WHERE
DeviceName=@GPSID
AND
SUBSTRING(CONVERT(varchar(8), CreateTime, 112),1,10)
BETWEEN @rSdate AND @rEdate
Default disclaimer, code is untested, use at your own risk.
Upvotes: 1
Reputation: 4036
Use SQL Server built-in geography::Point and STDistance methods:
ALTER PROCEDURE [dbo].[usp_MovementRepP] @GPSID NVARCHAR(50),
@rSdate DATE,
@rEdate DATE
AS
BEGIN
SELECT SUBSTRING(CONVERT( VARCHAR(12), CreateTime, 120), 1, 10) AS RecordDate,
SUBSTRING(CONVERT( VARCHAR(12), CreateTime, 114), 0, 13) AS RecordTime,
Latitude,
Longitude,
DeviceId,
g.geonameid,
geography::Point(g.latitude, g.longitude, 4326).STDistance(geography::Point(A.latitude, A.longitude)) AS Distance
FROM locationhistory A
INNER JOIN devices B ON A.DeviceId = B.id
INNER JOIN [GeoNames] g
ON geography::Point(g.latitude, g.longitude, 4326).STDistance(geography::Point(A.latitude, A.longitude)) < 20
WHERE DeviceName = @GPSID
AND SUBSTRING(CONVERT(VARCHAR(8), CreateTime, 112), 1, 10) BETWEEN @rSdate AND @rEdate;
END
Upvotes: 0