Measuring distance between two Lat/Lng points - sql

I am having issues trying to return the closest location to the user via an SQL statement. To test I am using the exact same coordinates, here is what I have:
SQL:
SELECT `companies`.`customerName` ,
(3959 *
ACOS(
COS(
RADIANS(37.367485) * COS(RADIANS(`locations`.`gps_lat`)) *
COS(
RADIANS(`locations`.`gps_lng`) - RADIANS(-77.399994) +
SIN(RADIANS(37.367485)) * SIN(RADIANS(`locations`.`gps_lat`))
)
)
)
)
AS `distance`
FROM `locations`
JOIN `companies` ON `locations`.`co_id`
HAVING `distance` > 25
ORDER BY distance
LIMIT 0 , 10
Results:
| customerName | distance |
| SOME COMPANY | 1914.41747964854 |
locations table values:
gps_lat | gps_lng
37.367485 | -77.399994
I used the example from Google, I checked the formula a couple times and I can't seem to come up with where I went wrong. Any help is appreciated.
EDIT:
Since there seems to be some confusion on me knowing what I am doing:
The > was substituted to yield a result, 1914 is obviously greater then 25.
The application of this is passing user coordinates from an Android app to our web server. The Latitude and Longitude will be pulled from the $_GET values and cross-referenced from companies in our web servers MYSQL database.
The syntax above is just me checking my SQL statement in Mysql Workbench. Obviously I am looking for a zero value result.

I think I have solved this issue by changing from Googles example to the formula provided by "Adventures of an adopted yooper". Using his/her version of the Haversine Formula.
Note: The Google example above is using Haversine as well.
SQL:
SELECT `companies`.`customerName` ,
(2 * (3959 * ATAN2(
SQRT(
POWER(SIN((RADIANS(37.367485 - `locations`.`gps_lat` ) ) / 2 ), 2 ) +
COS(RADIANS(`locations`.`gps_lat`)) *
COS(RADIANS(37.367485 )) *
POWER(SIN((RADIANS(-77.399994 - `locations`.`gps_lng` ) ) / 2 ), 2 )
),
SQRT(1-(
POWER(SIN((RADIANS(37.367485 - `locations`.`gps_lat` ) ) / 2 ), 2 ) +
COS(RADIANS(`locations`.`gps_lat`)) *
COS(RADIANS(37.367485)) *
POWER(SIN((RADIANS(-77.399994 - `locations`.`gps_lng` ) ) / 2 ), 2 )
))
)
))
AS 'distance'
FROM `locations`
JOIN `companies` ON `locations`.`co_id`
HAVING distance < 25
ORDER BY distance
LIMIT 0 , 10
For those curious on my application, Final PHP:
GET Value: ?lat=37.367485&lng=-77.399994
$earth_radius_miles = 3959; // Earth radius in miles
$earth_radius_kilometers = 6371; // Earth radius in kilometers
$result_radius = 10000; // Maximum distance in either miles or kilometers
$get_lat = $_GET["lat"];
$get_lng = $_GET["lng"];
$dbSelect = mysql_query("SELECT `companies`.`customerName`,
(2 * (".$earth_radius_miles." * ATAN2(
SQRT(
POWER(SIN((RADIANS(".$get_lat." - `locations`.`gps_lat` ) ) / 2 ), 2 ) +
COS(RADIANS(`locations`.`gps_lat`)) *
COS(RADIANS(".$get_lat.")) *
POWER(SIN((RADIANS(".$get_lng." - `locations`.`gps_lng` ) ) / 2 ), 2 )
),
SQRT(1-(
POWER(SIN((RADIANS(".$get_lat." - `locations`.`gps_lat` ) ) / 2 ), 2 ) +
COS(RADIANS(`locations`.`gps_lat`)) *
COS(RADIANS(".$get_lat.")) *
POWER(SIN((RADIANS(".$get_lng." - `locations`.`gps_lng` ) ) / 2 ), 2 )
))
)
))
AS 'distance'
FROM `locations`
JOIN `companies` ON `locations`.`co_id`
HAVING distance < ".$result_radius."
ORDER BY distance
LIMIT 0 , 10")
or die(mysql_error());
Results:
[{"customerName":"SOME COMPANY","distance":"0"}]
I have tested these results with other coordinates and seems to be working perfectly. Haven't tested anything with great distance between the two points but my application doesn't require me to do so.

Ashok, you can calculate the distance between two cordinates by using the below formula:
where
R = radius of the earth (6,371 km)
Δlat = |lat2- lat1|
Δlong = |long2- long1|

Related

How can I Roundoff in SQL with a sum function

In My SQL Code I am trying to round the value to 2 decimal point with sum
select ((SUM(Round((CAST(PE.GstTotal as float) * PE.Quantity) / 2 ),2))) FROM [dbo].[PharmacyEntry] PE
But I am getting an error. Could someone correct me on this.
Error
It's sometimes helpful to vertically align all your parenthesis pairs to see where you've got one wrong:
select
(
(
SUM
(
Round
(
(
CAST
(
PE.GstTotal as float
)
*
PE.Quantity
)
/
2
),
2
)
)
)
FROM [dbo].[PharmacyEntry] PE
You're providing 2 as a second parameter to sum instead of round. Try this:
select SUM(Round((CAST(PE.GstTotal as float) * PE.Quantity) / 2 , 2))
FROM [dbo].[PharmacyEntry] PE

Slow query with derived table in FROM clause

I have trying for hours to understand what is going on with this SQL query.
Objective of the query: List all events taking place 6km around a given city.
After all the comments (thanks all) I moved to the query bellow using CTE, removing one useless DISTINCT and using JOINs
BEFORE
SELECT DISTINCT c.id AS cours_id,
GROUP_CONCAT(DISTINCT cl.type) AS type_loc,
GROUP_CONCAT(DISTINCT CONCAT(cl.home,'-',cl.e)) AS home_loc,
MIN(ROUND(pld.distance)) AS distance,
pld.loc2_id AS distance_loc,
GROUP_CONCAT(DISTINCT CONCAT(cl.home,'-',cl.e,'-',ROUND(pld.distance),'-',pld.loc2_id)) AS all_loc
FROM cours2 c,
cours_locations cl,
locations_locations ll,
(SELECT dest.id AS loc2_id, 1000 * 6371.03 * 2 *
ASIN(SQRT( POWER(SIN((orig.latitude - ABS(dest.latitude)) *
PI()/180 / 2),2) + COS(orig.latitude *
PI()/180 ) * COS(ABS(dest.latitude) *
PI()/180) * POWER(SIN((orig.longitude - dest.longitude) *
PI()/180 / 2), 2) )) AS distance
FROM locations orig, locations dest
WHERE orig.id = 14861
AND (dest.type='V' OR dest.type='A')
AND dest.latitude BETWEEN orig.latitude - (6000 / 1000 / 111.045) AND orig.latitude + (6000 / 1000 / 111.045)
AND dest.longitude BETWEEN orig.longitude - (6000 / 1000 / (111.045 * COS(RADIANS(orig.latitude))))
AND orig.longitude + (6000 / 1000 / (111.045 * COS(RADIANS(orig.latitude))))
HAVING distance <= (6000 + 1000)) pld
WHERE
c.active_today = '1' AND
c.id = cl.cours_id AND
cl.domain = '4' AND
c.subject_id = 404 AND
cl.location_id = ll.rel_loc_id AND
ll.location_id = pld.loc2_id
GROUP BY c.id;
AFTER
WITH location_distances (loc_id, distance) AS (
SELECT dest.id AS loc_id,
1000 * 6371.03 * 2 * ASIN(SQRT( POWER(SIN((orig.latitude - ABS(dest.latitude)) * PI()/180 / 2),2) + COS(orig.latitude * PI()/180 ) * COS(ABS(dest.latitude) * PI()/180) * POWER(SIN((orig.longitude - dest.longitude) * PI()/180 / 2), 2) )) AS distance
FROM locations orig,
locations dest
WHERE orig.id = 14861
AND (dest.type='V' OR dest.type='A')
AND dest.latitude BETWEEN orig.latitude - (6000 / 1000 / 111.045) AND orig.latitude + (6000 / 1000 / 111.045)
AND dest.longitude BETWEEN orig.longitude - (6000 / 1000 / (111.045 * COS(RADIANS(orig.latitude)))) AND orig.longitude + (6000 / 1000 / (111.045 * COS(RADIANS(orig.latitude))))
HAVING distance < 7000
)
SELECT c.id AS cours_id,
GROUP_CONCAT(DISTINCT cl.type) AS type_loc,
GROUP_CONCAT(DISTINCT CONCAT(cl.home,'-',cl.e)) AS home_loc,
GROUP_CONCAT(DISTINCT CONCAT(cl.home,'-',cl.e,'-',ROUND(pld.distance),'-',pld.loc_id)) AS all_loc
FROM cours2 c
JOIN cours_locations cl ON cl.domain = '4' AND c.id = cl.cours_id
JOIN locations_locations ll ON cl.location_id = ll.location_id AND ll.up_propagation = 0
JOIN location_distances pld ON ll.rel_loc_id = pld.loc_id
WHERE c.active_today = '1'
AND c.subject_id = 404
GROUP BY c.id;
The exec time has not changed (it has even slightly increased): about 15 seconds.
The CTE part by itself runs in 290ms and returns 4 rows. This part is about finding the cities which are 6km distant from the city id 14681.
Yet, strangely:
if I remove the HAVING clause from the CTE, it still runs in 290ms with 4 rows but this time the global query takes 60 seconds instead of 15.
if I add a LIMIT 5 to the CTE, the global query falls to 300ms instead of 15 seconds.
if I add a LIMIT 15 (while the number of rows is 4), it falls to 60 seconds.
These observations can be reproduced with or without CTE, with or without JOINs.
The DB server version is 10.3.27-MariaDB
Table sizes :
cours2 c (list of events) : 15K
cours_locations cl (areas in which events take place) : 45K
locations_locations ll (hierarchy of areas - moving from a region to a city) : 310K
location_distances pld (computed cities 6km around the input city) : 5
Here is the EXPLAIN result :
|id|select_type|table|type|possible_keys|key|key_len|ref|rows|Extra|
|--|--------------|--------------|--------------|-------------|----------------|--------|------------|---------|---------|
|1|PRIMARY|c|ref|ID,cours2_active_today_id,cours2_active_today_domain_display_home_update_id,cours2_active_today_subject_id_id,cours2_active_today_subject_id_domain_lang_id,cours2_active_today_subject_id_lang_publish_end_id,cours2_active_today_lang_priv_loc_MOVE_subject_id_id,cours2_active_today_lang_subject_id_id,cours2_active_today_lang_priv_loc_ADR_subject_id_id,cours2_active_today_lang_priv_loc_WEBCAM_subject_id_id,cours2_subject_id_lang_id_index|cours2_active_today_subject_id_domain_lang_id|5|const,const|179|Using where; Using index; Using filesort|
|1|PRIMARY|cl|ref|PRIMARY,domain,location_id,cours_locations_domain_home_cours_id_index|PRIMARY|27|db.c.id,const|2|Using where; Using index|
|1|PRIMARY|ll|ref|PRIMARY,rel_loc_id|rel_loc_id|4|db.cl.location_id|7|""|
|1|PRIMARY|<derived2>|ref|key0|key0|4|db.ll.location_id|10|""|
|2|DERIVED|orig|const|PRIMARY|PRIMARY|4|const|1|""|
|2|DERIVED|dest|range|locations_type_D_id,locations_type_longitude_latitude|locations_type_longitude_latitude|9||1174|Using index condition; Using where|
EDIT:
Here is a version with 2 CTE running at 300ms total. This is really helping MariaDB to find the proper JOIn optimisation...
WITH
location_distances (loc_id, distance) AS (
SELECT dest.id AS loc_id,
ROUND(1000 * 6371.03 * 2 * ASIN(SQRT( POWER(SIN((orig.latitude - ABS(dest.latitude)) * PI()/180 / 2),2) + COS(orig.latitude * PI()/180 ) * COS(ABS(dest.latitude) * PI()/180) * POWER(SIN((orig.longitude - dest.longitude) * PI()/180 / 2), 2) ))) AS distance
FROM locations orig,
locations dest
WHERE orig.id = 14861
AND (dest.type='V' OR dest.type='A')
AND dest.latitude BETWEEN orig.latitude - (6000 / 1000 / 111.045) AND orig.latitude + (6000 / 1000 / 111.045)
AND dest.longitude BETWEEN orig.longitude - (6000 / 1000 / (111.045 * COS(RADIANS(orig.latitude)))) AND orig.longitude + (6000 / 1000 / (111.045 * COS(RADIANS(orig.latitude))))
HAVING distance < 7000
),
location_distances_hierarchy (parent_loc_id, loc_id, distance) AS (
SELECT DISTINCT ll.location_id, ld.loc_id, ld.distance
FROM locations_locations ll,
location_distances ld
WHERE ll.rel_loc_id = ld.loc_id
)
SELECT c.id AS cours_id,
GROUP_CONCAT(DISTINCT CONCAT(cl.type,'-',cl.home,'-',cl.e,'-',ldh.distance,'-',ldh.loc_id)) AS all_loc
FROM cours2 c
JOIN cours_locations cl ON cl.domain = '4' AND c.id = cl.cours_id
JOIN location_distances_hierarchy ldh ON cl.location_id = ldh.parent_loc_id
WHERE c.active_today = '1'
AND c.subject_id = 404
GROUP BY c.id;
New EXPLAIN result :
|id|select_type|table|type|possible_keys|key|key_len|ref|rows|Extra|
|1|PRIMARY|ss|ref|PRIMARY,subject_id|PRIMARY|4|const|1|Using index; Using temporary; Using filesort|
|1|PRIMARY|c|ref|ID,cours2_active_today_id,cours2_active_today_domain_display_home_update_id,cours2_active_today_subject_id_id,cours2_active_today_subject_id_domain_lang_id,cours2_active_today_subject_id_lang_publish_end_id,cours2_active_today_lang_priv_loc_MOVE_subject_id_id,cours2_active_today_lang_subject_id_id,cours2_active_today_lang_priv_loc_ADR_subject_id_id,cours2_active_today_lang_priv_loc_WEBCAM_subject_id_id,cours2_subject_id_lang_id_index|cours2_active_today_subject_id_domain_lang_id|5|const,trouver-un-cours.ss.rel_subject_id|30|Using where; Using index|
|1|PRIMARY|caf|eq_ref|PRIMARY|PRIMARY|26|trouver-un-cours.c.id|1|""|
|1|PRIMARY|cl|ref|PRIMARY,domain,location_id,cours_locations_domain_home_cours_id_index|PRIMARY|27|trouver-un-cours.c.id,const|2|Using where; Using index|
|1|PRIMARY|<derived3>|ref|key0|key0|4|trouver-un-cours.cl.location_id|10|Using where|
|3|DERIVED|<derived2>|ALL|||||1174|Using temporary|
|3|DERIVED|ll|ref|rel_loc_id|rel_loc_id|4|ld.loc_id|7|""|
|2|DERIVED|orig|const|PRIMARY|PRIMARY|4|const|1|""|
|2|DERIVED|dest|range|locations_type_D_id,locations_type_longitude_latitude|locations_type_longitude_latitude|9||1174|Using index condition; Using where|

SQL Select based on value from sub-query

I'm trying to select a set of users based on the latitude and longitude of another user for whom I have the ID. I need to do a distance calculation in the SELECT statement of the query and am not sure how to access the latitude and longitude of the user that I'm searching against.
Here is my query so far (which doesn't work)
SELECT
*,
(SELECT u1.latitude, u1.longitude
FROM user u1
WHERE u1.id = '77c3d4e0-37f6-4fae-b8ac-66cffad07179'),
(3959 * acos(cos(radians(u1.latitude))
* cos(radians(latitude))
* cos(radians(longitude) - radians(u1.longitude))
+ sin (radians(u1.latitude))
* sin(radians(latitude)))) AS distance
FROM
user u2
Basically I want to get all of the columns from user and a column called distance, which is the distance from the user with the ID
Move the subquery reference to the from clause:
SELECT u2.*,
(3959 * acos (
cos ( radians(u1.latitude) )
* cos( radians( u2.latitude ) )
* cos( radians( u2.longitude ) - radians(u1.longitude) )
+ sin ( radians(u1.latitude) )
* sin( radians( u2.latitude ) )
)
) AS distance
FROM icebreaker_user u2 JOIN
icebreaker_user u1
ON u1.id = '77c3d4e0-37f6-4fae-b8ac-66cffad07179'

How to write an equation in sql?

I want to implement this equation in sql :
f(x) = f(a) + (f(b)-f(a)) * (x-a)/(b-a)
My input is as following:
What I tried was:
select ((select CosValue from CosineTable where Angle=70) +
((select CosValue from CosineTable where Angle=75) -
(select CosValue from CosineTable where Angle=70)) * (73-70) / (75-70)
from CosineTable;
It's showing me a syntax error.
You are missing one closing bracket )
Try this:
SELECT ( ( SELECT CosValue
FROM CosineTable
WHERE Angle = 70
) + ( ( SELECT CosValue
FROM CosineTable
WHERE Angle = 75
) - ( SELECT CosValue
FROM CosineTable
WHERE Angle = 70
) ) * ( 73 - 70 ) / ( 75 - 70 ) )
FROM CosineTable;
But it only works if you always get a single value back in your Subselects.
Assuming there is only one row per angle in your table, you query can be simplified by using a cross join:
select a70.cosvalue + (a75.cosvalue - a70.cosvalue) * (73-70) / (75-70))
from CosineTable a70
cross join cosinetable a75
where a70.angle = 70
and a75.angle = 75;

Are there any trend line Function in PL SQL?

I need a function to calculate a trend line. I have a query (part of the function):
select round(sum(nvl(vl_indice, vl_meta))/12, 2) from (
SELECT
SUM (vl_indice) vl_indice, SUM (vl_meta) vl_meta
FROM
(SELECT cd_mes, vl_indice, NULL vl_meta, dt.id_tempo,
fi.id_multi_empresa, fi.id_setor, fi.id_indice
FROM dbadw.fa_indice fi , dbadw.di_tempo dt ,
dbadw.di_multi_empresa dme , dbaportal.organizacao o ,
dbadw.di_indice di
WHERE fi.id_tempo = dt.id_tempo
AND DT.CD_MES BETWEEN TO_NUMBER(TO_CHAR(ADD_MONTHS(TO_DATE(TO_CHAR(PCD_MES),'YYYYMM'),- 11),'YYYYMM'))
AND PCD_MES
AND DT.ANO = TO_NUMBER(TO_CHAR(TO_DATE(TO_CHAR(PCD_MES),'YYYYMM'),'YYYY'))
AND fi.id_multi_empresa = dme.id_multi_empresa
AND dme.cd_multi_empresa = NVL(o.cd_multi_empresa_mv2000, o.cd_organizacao)
AND o.cd_organizacao = PCD_ORG
AND fi.id_setor IS NULL
AND fi.id_indice = di.id_indice
AND di.cd_indice = PCD_IVM
UNION ALL
SELECT cd_mes, NULL vl_indice, vl_meta, dt.id_tempo,
fm.id_multi_empresa, fm.id_setor, fm.id_indice
FROM dbadw.fa_meta_indice fm , dbadw.di_tempo dt ,
dbadw.di_multi_empresa dme , dbaportal.organizacao o ,
dbadw.di_indice di
WHERE fm.id_tempo = dt.id_tempo
AND DT.ANO = TO_NUMBER(TO_CHAR(TO_DATE(TO_CHAR(PCD_MES),'YYYYMM'),'YYYY'))
AND fm.id_multi_empresa = dme.id_multi_empresa
AND dme.cd_multi_empresa = NVL(o.cd_multi_empresa_mv2000, o.cd_organizacao)
AND o.cd_organizacao = PCD_ORG
AND fm.id_setor IS NULL
AND fm.id_indice = di.id_indice
AND di.cd_indice = PCD_IVM
)
GROUP BY cd_mes, id_tempo, id_multi_empresa, id_setor, id_indice
ORDER BY cd_mes);
Where I tried to calculate the trend line on the first line, but is not correct. Please, Can anybody help me?
Its very difficult to work out from a query what you want to fit a "trend line" to - by which I assume you mean to use least square linear regression to find a best fit to the data.
So an example with test data:
Oracle Setup:
CREATE TABLE data ( x, y ) AS
SELECT LEVEL,
230 + DBMS_RANDOM.VALUE(-5,5) - 3.14159 * DBMS_RANDOM.VALUE( 0.95, 1.05 ) * LEVEL
FROM DUAL
CONNECT BY LEVEL <= 1000;
As you can see the data is random but its approximately y = -3.14159x + 230
Query - Get the Least Square Regression y-intercept and gradient:
SELECT REGR_INTERCEPT( y, x ) AS best_fit_y_intercept,
REGR_SLOPE( y, x ) AS best_fit_gradient
FROM data
This will get something like:
best_fit_y_intercept best_fit_gradient
-------------------- -----------------
230.531799878168 -3.143190435415
Query - Get the y co-ordinate of the line of best fit:
SELECT x,
y,
REGR_INTERCEPT( y, x ) OVER () + x * REGR_SLOPE( y, x ) OVER () AS best_fit_y
FROM data
The solution is:
SELECT valor, mes,
((mes * SLOPE) + INTERCEPT) TENDENCIA, SLOPE, INTERCEPT from
( select valor, mes, ROUND(REGR_SLOPE(valor,mes) over (partition by id_multi_empresa),4)SLOPE,
ROUND(REGR_INTERCEPT(valor,mes) over (PARTITION by id_multi_empresa),4) INTERCEPT from( --the initial select