MySQL - Filtering results by other tables - mysql

I have a procedure that has a main purpose of locating 9 users closest to you based on a longitude and latitude coordinate. However, I need to filter these results (and still return 9) based on values of other tables.
Here is the procedure that I'm currently using that doesn't have any filtering.
BEGIN
DECLARE lat FLOAT(11,8);
DECLARE lon FLOAT(11,8);
DECLARE dist INT(11);
SELECT latitude, longitude, search_radius
INTO lat, lon, dist
FROM accounts
INNER JOIN account_settings
ON accounts.account_id = account_settings.account_id
WHERE accounts.account_id = account_in;
SELECT account_id, first_name, bio, birthdate
, ( 3959 * acos( cos( radians(lat) ) * cos( radians(latitude) ) * cos( radians( longitude ) - radians(lon) ) + sin( radians(lat) ) * sin(radians( latitude )) ) )
AS distance
FROM `accounts`
WHERE account_id != account_in
HAVING distance < dist
ORDER BY distance ASC LIMIT 9;
END
However, I need to add some conditions to the select statement at the bottom, making it so it will still return 9 (if available) based on filtering from the following tables.
blocked_users
completed_test
failed_test
The three tables above have the same table structure of [ account_id, other_id ].
How would I make sure that none of the users returned in the last select statement are present in the other_id field of any of the tables listed, while matched with the corresponding account_id passed through the account_in variable.
I've been racking my brain for about 6 hours and I just can't seem to come up with a good conclusion. This query is much higher level (or I'm overthinking it a lot) than I'm used to dealing with. As i only have basic MySQL knowledge.

Related

select items from one table with sum of records matching HAVING in subquery

I have a table of cities with, among other things, population, latitude and longitude. I also have a table of airports with various info including latitude and longitude.
A query like this roughly obtains the population of all towns within 100 km of a given latitude and longitude:
SELECT SUM(cty_population) as cty_population_total FROM
(SELECT
cty_population, (
6371 * acos (
cos ( radians(37.61899948) )
* cos( radians( cty_latitude ) )
* cos( radians( cty_longitude ) - radians(-122.37500000) )
+ sin ( radians(37.61899948) )
* sin( radians( cty_latitude ) )
)
) AS cty_distance
FROM cities
HAVING cty_distance < 100) cty_population_alias
This will give a result like this:
cty_population_total
6541221
In the above query, 37.61899948 is the latitude and -122.37500000 is the longitude.
My question is: can I select an arbitrary number of airports from the airports table, pass their longitudes and latitudes into this subquery in place of the above numbers, and find the city population within 100 km of each airport. Ideally I would have results like this:
airport_name airport_pop
Boston Logan 6654901
London Heathrow 11345690
...etc.
I could do this with scripting, but I am wondering if it can be done with SQL alone? The database engine is MySQL.
You have a very procedural way of thinking this which would not work well in SQL. You can think of it as joining the city with all its nearby airports.
The following may work:
SELECT a.name, SUM(c.cty_population)
FROM cities c JOIN airports a ON (
6371 * acos (
cos ( radians(a.latitude) )
* cos( radians( c.cty_latitude ) )
* cos( radians( c.cty_longitude ) - radians(a.longitude) )
+ sin ( radians(a.latitude) )
* sin( radians( c.cty_latitude ) )
) < 100
WHERE (filter airports or something else here)
GROUP BY a.airport_id, a.name
Also, I suggest you migrate to MySQL 5.7 which includes spatial functions and data types built in.

Searching within a distance in SQL

I have database with a list of contacts with puesdo coordinates of where they live. Here's a sample:
name e_point n_point
David 102 345
James 174 746
Ali 460 584
Kevin 364 479
Mark 385 274
I was wondering is it possible to create a query that can search within a distance of the two coordinates? E.g., I want a list of people who live within a 20 sqr mile radius of James.
What functions can help me do this?
Mysql syntax:
SELECT name FROM `table` WHERE SQRT(
POW(e_point - (SELECT e_point FROM `table` WHERE name='james'), 2) +
POW(n_point - (SELECT n_point FROM `table` WHERE name='james'), 2)) < 20
AND name <> 'james'
Notice:
you need to change 'name' in 3 places.
subqueries added for let you run one query with just one variable (name). If you remove subqueries, you need to run 2 queries (first query retrieving coord, and second query searching near people)
equation is:
Where p1=(p1x, p1y) and p2=(p2x, p2y)
To calculate the distance between 2 coordinates you need to get the Great-circle distance, as the earth is rounded and measurement of distance is affected by this fact.
Doing this with SQL would be something like this according to the Google Maps API docs:
SELECT id, ( 3959 * acos( cos( radians(37) ) * cos( radians( lat ) ) * cos( radians( lng ) - radians(-122) ) + sin( radians(37) ) * sin( radians( lat ) ) ) )
AS distance
FROM markers
HAVING distance < 25
ORDER BY distance
LIMIT 0 , 20;
if your coordinates represent like x and y coordinates you can use something like this (just replaces occurrences of contacts with your actual table name) distance returned is the distance from 'James':
SELECT T2.name,
SQRT(POWER(T2.e_point-T1.e_point,2)+
POWER(T2.n_point-T1.n_point,2)) as distance
FROM contacts T1
INNER JOIN contacts T2 ON
SQRT(POWER(T2.e_point-T1.e_point,2)+
POWER(T2.n_point-T1.n_point,2)) <= 20
WHERE T1.name = 'James'
AND T1.name != T2.name
sqlfiddle

Find distance between two points using latitude and longitude in mysql

Hi I have the following table
--------------------------------------------
| id | city | Latitude | Longitude |
--------------------------------------------
| 1 | 3 | 34.44444 | 84.3434 |
--------------------------------------------
| 2 | 4 | 42.4666667 | 1.4666667 |
--------------------------------------------
| 3 | 5 | 32.534167 | 66.078056 |
--------------------------------------------
| 4 | 6 | 36.948889 | 66.328611 |
--------------------------------------------
| 5 | 7 | 35.088056 | 69.046389 |
--------------------------------------------
| 6 | 8 | 36.083056 | 69.0525 |
--------------------------------------------
| 7 | 9 | 31.015833 | 61.860278 |
--------------------------------------------
Now I want to get distance between two points. Say a user is having a city 3 and a user is having a city 7. My scenario is one user having a city and latitue and longtitude is searching other users distance from his city. For example user having city 3 is searching. He wants to get distance of user of any other city say it is 7. I have searched and found following query
SELECT `locations`.`city`, ( 3959 * acos ( cos ( radians(31.589167) ) * cos( radians( Latitude ) ) * cos( radians( Longitude ) - radians(64.363333) ) + sin ( radians(31.589167) ) * sin( radians( Latitude ) ) ) ) AS `distance` FROM `locations` HAVING (distance < 50)
As for as I know this query finds distance from one point to all other points. Now I want to get distance from one point to other point.
Any guide line will be much appreciated.
I think your question says you have the city values for the two cities between which you wish to compute the distance.
This query will do the job for you, yielding the distance in km. It uses the spherical cosine law formula.
Notice that you join the table to itself so you can retrieve two coordinate pairs for the computation.
SELECT a.city AS from_city, b.city AS to_city,
111.111 *
DEGREES(ACOS(LEAST(1.0, COS(RADIANS(a.Latitude))
* COS(RADIANS(b.Latitude))
* COS(RADIANS(a.Longitude - b.Longitude))
+ SIN(RADIANS(a.Latitude))
* SIN(RADIANS(b.Latitude))))) AS distance_in_km
FROM city AS a
JOIN city AS b ON a.id <> b.id
WHERE a.city = 3 AND b.city = 7
Notice that the constant 111.1111 is the number of kilometres per degree of latitude, based on the old Napoleonic definition of the metre as one ten-thousandth of the distance from the equator to the pole. That definition is close enough for location-finder work.
If you want statute miles instead of kilometres, use 69.0 instead.
http://sqlfiddle.com/#!9/21e06/412/0
If you're looking for nearby points you may be tempted to use a clause something like this:
HAVING distance_in_km < 10.0 /* slow ! */
ORDER BY distance_in_km DESC
That is (as we say near Boston MA USA) wicked slow.
In that case you need to use a bounding box computation. See this writeup about how to do that. http://www.plumislandmedia.net/mysql/haversine-mysql-nearest-loc/
The formula contains a LEAST() function. Why? Because the ACOS() function throws an error if its argument is even slightly greater than 1. When the two points in question are very close together, the expression with the COS() and SIN() computations can sometimes yield a value slightly greater than 1 due to floating-point epsilon (inaccuracy). The LEAST(1.0, dirty-great-expression) call copes with that problem.
There's a better way, a formula by Thaddeus Vincenty. It uses ATAN2() rather than ACOS() so it's less susceptible to epsilon problems.
Edit 2022 (by Alexio Vay):
As of today the modern solution should be the following short code:
select ST_Distance_Sphere(
point(-87.6770458, 41.9631174),
point(-73.9898293, 40.7628267))
Please check out the answer of Naresh Kumar.
You can use the ST_Distance_Sphere() MySQL built-in function, supported since MySQL 5.7 version and above.
It computes the distance in meters more efficiently.
select ST_Distance_Sphere(point(lng, lat), point(lng,lat))
i.e.
select ST_Distance_Sphere(
point(-87.6770458, 41.9631174),
point(-73.9898293, 40.7628267)
)
Referred from Calculating distance using MySQL
Heres is MySQL query and function which use to get distance between two latitude and longitude and distance will return in KM.
Mysql Query :-
SELECT (6371 * acos(
cos( radians(lat2) )
* cos( radians( lat1 ) )
* cos( radians( lng1 ) - radians(lng2) )
+ sin( radians(lat2) )
* sin( radians( lat1 ) )
) ) as distance
FROM your_table;
Mysql Function :-
DELIMITER $$
CREATE FUNCTION `getDistance`(`lat1` VARCHAR(200), `lng1` VARCHAR(200), `lat2` VARCHAR(200), `lng2` VARCHAR(200)) RETURNS varchar(10) CHARSET utf8
begin
declare distance varchar(10);
set distance = (select (6371 * acos(
cos( radians(lat2) )
* cos( radians( lat1 ) )
* cos( radians( lng1 ) - radians(lng2) )
+ sin( radians(lat2) )
* sin( radians( lat1 ) )
) ) as distance);
if(distance is null)
then
return '';
else
return distance;
end if;
end$$
DELIMITER ;
How to use in your PHP Code
SELECT getDistance($lat1,$lng1,$lat2,$lng2) as distance
FROM your_table.
Here's a MySQL function that will take two latitude/longitude pairs, and give you the distance in degrees between the two points. It uses the Haversine formula to calculate the distance. Since the Earth is not a perfect sphere, there is some error near the poles and the equator.
To convert to miles, multiply by 3961.
To convert to kilometers, multiply by 6373.
To convert to meters, multiply by 6373000.
To convert to feet, multiply by (3961 * 5280) 20914080.
DELIMITER $$
CREATE FUNCTION \`haversine\`(
lat1 FLOAT, lon1 FLOAT,
lat2 FLOAT, lon2 FLOAT
) RETURNS float
NO SQL
DETERMINISTIC
COMMENT 'Returns the distance in degrees on the Earth between two known points of latitude and longitude. To get miles, multiply by 3961, and km by 6373'
BEGIN
RETURN DEGREES(ACOS(
COS(RADIANS(lat1)) *
COS(RADIANS(lat2)) *
COS(RADIANS(lon2) - RADIANS(lon1)) +
SIN(RADIANS(lat1)) * SIN(RADIANS(lat2))
));
END;
DELIMITER;
Not sure how your distance calculation is going on but you need to do a self join your table and perform the calculation accordingly. Something like this probably
select t1.id as userfrom,
t2.id as userto,
( 3959 * acos ( cos ( radians(31.589167) ) * cos( radians( t1.Latitude ) ) *
cos( radians( t1.Longitude ) - radians(64.363333) ) + sin ( radians(31.589167) ) *
sin( radians( t2.Latitude ) ) ) ) AS `distance`
from table1 t1
inner join table1 t2 on t2.city > t1.city
IMPORTANT! Anyone using or copying these calculations MAKE SURE to use least(1.0, (...)) when passing the calculation to the acos() function. The acos() function will NOT take a value above 1 and I have found when comparing lat/lng values that are identical there are times when the calculations come out to something like 1.000002. This will produce a distance of NULL instead of 0 and may not return results you're looking for depending on how your query is structured!
This is CORRECT:
select round(
( 3959 * acos( least(1.0,
cos( radians(28.4597) )
* cos( radians(lat) )
* cos( radians(lng) - radians(77.0282) )
+ sin( radians(28.4597) )
* sin( radians(lat)
) ) )
), 1) as distance
from locations having distance <= 60 order by distance
This is WRONG:
select round(
( 3959 * acos(
cos( radians(28.4597) )
* cos( radians(lat) )
* cos( radians(lng) - radians(77.0282) )
+ sin( radians(28.4597) )
* sin( radians(lat)
) )
), 1) as distance
from locations having distance <= 60 order by distance
The highest rated answer also talks about this, but I wanted to make sure this was very clear since I just found a long standing bug in my query.
Here's a formula I converted from https://www.geodatasource.com/developers/javascript
It's a nice clean function that calculates the distance in KM
DELIMITER $$
CREATE DEFINER=`root`#`localhost` FUNCTION `FN_GET_DISTANCE`(
lat1 DOUBLE, lng1 DOUBLE, lat2 DOUBLE, lng2 DOUBLE
) RETURNS double
BEGIN
DECLARE radlat1 DOUBLE;
DECLARE radlat2 DOUBLE;
DECLARE theta DOUBLE;
DECLARE radtheta DOUBLE;
DECLARE dist DOUBLE;
SET radlat1 = PI() * lat1 / 180;
SET radlat2 = PI() * lat2 / 180;
SET theta = lng1 - lng2;
SET radtheta = PI() * theta / 180;
SET dist = sin(radlat1) * sin(radlat2) + cos(radlat1) * cos(radlat2) * cos(radtheta);
SET dist = acos(dist);
SET dist = dist * 180 / PI();
SET dist = dist * 60 * 1.1515;
SET dist = dist * 1.609344;
RETURN dist;
END$$
DELIMITER ;
You'll also find the same function in different languages on the site;
Maybe someone will come in handy, I managed to implement my task through the FN_GET_DISTANCE function:
SELECT SUM (t.distance) as Distance FROM
(SELECT (CASE WHEN (FN_GET_DISTANCE (Latitude, Longitude, #OLDLatitude, #OLDLongitude)) BETWEEN 0.01 AND 2 THEN
FN_GET_DISTANCE (Latitude, Longitude, #OLDLatitude, #OLDLongitude) ELSE 0 END) AS distance,
IF (#OLDLatitude IS NOT NULL, #OLDLatitude: = Latitude, 0),
IF (#OLDLongitude IS NOT NULL, #OLDLongitude: = Longitude, 0)
FROM `data`, (SELECT #OLDLatitude: = 0) var0, (SELECT #OLDLongitude: = 0) var1
WHERE ID_Dev = 1
AND DateTime BETWEEN '2021-05-23 08:00:00' AND '2021-05-23 20:00:00'
ORDER BY ID DESC) t;

Processor issues with LEFT JOIN query

This may or may not be a simple question.
I have a query which selects all locations within a mile radius around a provided latitude and longitude. That part works perfectly, but I have additional information inside of another table that I would like to match to it. I've tried LEFT JOIN, but it's timing out.
SELECT *,
( 3959 * acos( cos( radians(40.7143528) ) * cos( radians( lat ) )
* cos( radians( lon ) - radians(-74.0059731) ) + sin( radians(40.7143528) )
* sin( radians( lat ) ) ) ) AS distance
FROM locations
LEFT JOIN informations ON
locations.name = informations.name
HAVING distance < 1
Here is what I would like this query to do:
Provide all matching locations within a mile radius (works)
Obtain the name of those stores (works)
Match those names against the names of each store in the "informations" table
Join all of the information in the matching rows together, where "locations.name" and "informations.name" match
The above query seems like it wants to work. I don't get any errors and it shows as valid in any MySQL formatter I use. However, I think I'm making an error somewhere which causes my tiny server to max processor usage.
To a more experienced set of eyes, would you see a reason why this would occur? Other than my server being near useless, of which I'm aware.
Make your initial query a sub-query of the main query, and left join that resulting relation to the new table (with additional information). That should give you performance back
The result will look like this:
SELECT *
FROM(
SELECT *,
( 3959 * acos( cos( radians(40.7143528) ) * cos( radians( lat ) )
* cos( radians( lon ) - radians(-74.0059731) ) + sin( radians(40.7143528) )
* sin( radians( lat ) ) ) ) AS distance
FROM locations
) locations
LEFT JOIN informations
ON location.name =informations.name
The HAVING clause is of course part of query #1.
I use SQL SERVER typically, so the syntax may not be quite right for MYSQL.

using ORDER BY in a mysql statement and also grouping the results

I'm doing a query on a set of users that returns the users within a specific radius. The users belong to 1 of 3 different types of user groups. I need to sort the return list by showing the users from group 1 (sorted by distance) and then the users from group 2 & 3 combined (sorted by distance). I could easily use an ORDER BY clause, but I don't know how to be sure that groups 2 & 3 are combined together in the results.
Here is the statement that returns the users first by group 1, then 2, then 3 and sorted by distance inside those groups. I need to return group 1, then 2&3, sorted by distance.
Also I'm not sure if I'm using the INNER JOIN properly, should this be a LEFT JOIN?
SELECT
SQL_CALC_FOUND_ROWS
a.*,
b.group_id,
ROUND(( 3959 * acos( cos( radians( $lat ) ) * cos( radians( a.latitude ) ) * cos( radians( a.longitude ) - radians( $lon ) ) + sin( radians( $lat ) ) * sin( radians( a.latitude ) ) ) ), 1) AS distance
FROM `users` AS a
INNER JOIN `user_group_map` AS b
ON a.`id` = b.`user_id`
HAVING distance <= $radius
ORDER BY
b.`group_id` DESC,
distance ASC
You can use an ORDER BY CASE to conditionally apply the order forcing group 1 first. I think this should do the job:
ORDER BY
CASE WHEN b.group_id = 1 THEN 0 ELSE 1 END,
distance ASC
The CASE statement returns 0 for group_id = 1 and 1 for any other value of group_id. The 0 sorts ahead of the 1, and these two subgroups (0 & 1) are then subordered by distance ASC so group_id 2 & 3 sort together.