Speeding up mySQL SPATIAL select based on distance to coordinates - mysql

I am trying to move our site over from using a Lat/Lng field (CHAR), when doing distance. This is how we currently do it:
SELECT ID,( 6371 * acos( cos( radians(52.35462) ) * cos( radians( glinks_Links.Latitude ) ) * cos( radians( glinks_Links.Longitude ) - radians(4.88227) ) + sin( radians(52.35462) ) * sin( radians( glinks_Links.Latitude ) ) ) ) AS distance FROM glinks_Links
WHERE
(
((Latitude BETWEEN (52.35462 - 40/69.0) AND (52.35462 + 40/69.0)) )
AND
(Longitude BETWEEN (4.88227 - 40/42.5) AND (4.88227 + 40/42.5))
)
HAVING distance < 40 ORDER BY distance
...and this comes back as
Showing rows 0 - 24 (1855 total, Query took 0.0288 seconds.)
Then another version that actually gives the "distance" back (as we want to sort by that, and only include the closest);
SELECT ID,st_distance_sphere(POINT(4.88227,52.35462), point_test) / 1000 AS distance FROM glinks_Links WHERE ( ((Latitude BETWEEN (52.35462 - 40/69.0) AND (52.35462 + 40/69.0)) ) AND (Longitude BETWEEN (4.88227 - 40/42.5) AND (4.88227 + 40/42.5)) ) HAVING DISTANCE < 100 ORDER BY distance LIMIT 100
Gives:
Showing rows 0 - 99 (100 total, Query took 0.0237 seconds.)
Then what seems to be the best:
SELECT *
FROM glinks_Links
where st_distance_sphere(POINT(4.88227,52.35462), point_test)/1000 <= 100
Showing rows 0 - 24 (3439 total, Query took 0.0015 seconds.)
The issue with that though - is that it doesn't provide me with the distance! What I want to do is query and grab the 100 closest. Is there a way I can do this, without compramising on the speed?

be sure you have proper composite index on
create index my_idx ON glinks_Links (Latitude, Longitude, ID)
And you don't need unuseful () for where condition (using AND)
SELECT ID,( 6371 * acos( cos( radians(52.35462) ) * cos( radians( glinks_Links.Latitude ) )
* cos( radians( glinks_Links.Longitude ) - radians(4.88227) ) + sin( radians(52.35462) )
* sin( radians( glinks_Links.Latitude ) ) ) ) AS distance
FROM glinks_Links
WHERE Latitude BETWEEN (52.35462 - 40/69.0) AND (52.35462 + 40/69.0)
AND Longitude BETWEEN (4.88227 - 40/42.5) AND (4.88227 + 40/42.5)
HAVING distance < 40
ORDER BY distance
for the second version
SELECT ID, st_distance_sphere(POINT(4.88227,52.35462), point_test) / 1000 AS distance
FROM glinks_Links
WHERE Latitude BETWEEN (52.35462 - 40/69.0) AND (52.35462 + 40/69.0)
AND Longitude BETWEEN (4.88227 - 40/42.5) AND (4.88227 + 40/42.5)
HAVING DISTANCE < 100
ORDER BY distance
LIMIT 100
you should use a composite index on
create index myidx ON glinks_Links (Latitude, Longitude, point_test, ID )

Related

How to get distance in kilometer when pass in 2 value (langtitude and longitude) in mysql?

I have a table in mysql call BRANCH
===============================
Branch_id latitude longitude
===============================
1 3.109421 101.622913
2 3.101121 101.644913
How can i select kilometer calculation from this table when I pass in my current location latitude / longitude?
Example:
If I pass in my current location = 3.122221 101.343913
============================================
Branch_id latitude longitude distance(km)
============================================
1 3.109421 101.622913 0.4
2 3.101121 101.644913 0.6
Edited (Solved):
SELECT p.title,p.subtitle,p.desc,p.image,p.promotion_id,p.merchant_id,p.date_from,p.date_to,m.merchant_name,p.view,mb.latitude,mb.longitude,
(6371 * acos (cos( radians(3.158704)) * cos( radians(mb.latitude))
* cos( radians(mb.longitude) - radians(101.713963)) + sin(radians(3.158704))
* sin( radians(mb.latitude)))) AS distance
FROM merchant_branch as mb
left join merchant m on mb.merchant_id = m.merchant_id
left join promotion p on p.merchant_id = m.merchant_id
where p.promotion_id is not null order by distance asc
From: https://developers.google.com/maps/solutions/store-locator/clothing-store-locator
Here's the SQL statement that finds the closest 20 locations within a
radius of 25 miles to the -33, 151 coordinate. It calculates the
distance based on the latitude/longitude of that row and the target
latitude/longitude, and then asks for only rows where the distance
value is less than 25, orders the whole query by distance, and limits
it to 20 results. To search by kilometers instead of miles, replace
3959 with 6371.
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;

SELECT column but also add AVG column

I'm not quite sure how to expand this query so that I can also have a AVG(price_current) column ... instead of having to calculate this value within PHP once the query comes back.
SELECT
listing_subtype, bedrooms, total_baths, tot_sqft_finished, price_current, latitude, longitude, (
3959 * acos (
cos ( radians(48.639) )
* cos( radians( latitude ) )
* cos( radians( longitude ) - radians(-123.404) )
+ sin ( radians(48.639) )
* sin( radians( latitude ) )
)
) AS distance
FROM rets_property_resi
WHERE listing_subtype = 'Single Family Detached' AND
bedrooms >= 2 AND bedrooms <= 3 AND
total_baths >= 1 AND total_baths <= 2 AND
tot_sqft_finished >= 2000 AND tot_sqft_finished <= 2500
HAVING distance < 5
ORDER BY distance
LIMIT 0, 25;
I'm going to make some assumptions here:
You look for similar houses/homes in a given radius around a requested one. Each of those homes has a primary key in the table you are querying from. Let us call that primKey here.
Additionally, you want the average price of all returned homes which is limited to 0,25, and not the AVG of all records in the table that match you WHERE clause.
All returned rows will have therefore an extra field with the average price and is the same for each row.
You will have to run the same query again as a subquery. However, since you want to limit the AVG you have to run that in a subquery to calculate the correct AVG.
This is really clunky:
SELECT
rets_property_resi.listing_subtype, rets_property_resi.bedrooms, rets_property_resi.total_baths, rets_property_resi.tot_sqft_finished, rets_property_resi.price_current, rets_property_resi.latitude, rets_property_resi.longitude, (
3959 * acos (
cos ( radians(48.639) )
* cos( radians( latitude ) )
* cos( radians( longitude ) - radians(-123.404) )
+ sin ( radians(48.639) )
* sin( radians( latitude ) )
)
) AS distance, outerSubQuery.averagePrice
FROM rets_property_resi
LEFT JOIN
(
SELECT innerSubQuery.primKey AS primKey, AVG(innerSubQuery.price_current) AS averagePrice
FROM
(SELECT
primKey, price_current, (
3959 * acos (
cos ( radians(48.639) )
* cos( radians( latitude ) )
* cos( radians( longitude ) - radians(-123.404) )
+ sin ( radians(48.639) )
* sin( radians( latitude ) )
)
) AS distance
FROM rets_property_resi
WHERE listing_subtype = 'Single Family Detached' AND
bedrooms >= 2 AND bedrooms <= 3 AND
total_baths >= 1 AND total_baths <= 2 AND
tot_sqft_finished >= 2000 AND tot_sqft_finished <= 2500
HAVING distance < 5
ORDER BY distance
LIMIT 0, 25) AS innerSubQuery
GROUP BY innerSubQuery.primKey
) AS outerSubQuery ON (outerSubQuery.primKey = rets_property_resi.primKey)
WHERE listing_subtype = 'Single Family Detached' AND
bedrooms >= 2 AND bedrooms <= 3 AND
total_baths >= 1 AND total_baths <= 2 AND
tot_sqft_finished >= 2000 AND tot_sqft_finished <= 2500
HAVING distance < 5
ORDER BY distance
LIMIT 0, 25;
I bet there is a more elegant version of this though. You might be better off with a temporary table here or keep just calculating in code.

Find by latitude and longitude in mysql

i want to search near by location with given latitude and longitude in mysql
latitude is : 26.902
longitude is : 75.793
and distance is : 30
Query is :
SELECT
id, (
3959 * acos (
cos ( radians(26.902) )
* cos( radians( latitude ) )
* cos( radians( longitude ) - radians(75.793) )
+ sin ( radians(26.902) )
* sin( radians( latitude ) )
)
) AS distance
FROM business
HAVING distance < 30
ORDER BY distance
LIMIT 0 , 20;
Result:
i am getting a record with distance is 3.58,
record lat/long are 26.89 / 75.74
but when i check online on other site i got distance 5.759 miles .
Some code available here as a Stored Function: http://mysql.rjweb.org/doc.php/latlng .

How to improve this huge SQL query?

I have a big SQL query (for MySQL) that is slow. It's a union of two select statements. I have tried different things, but any slight variance gives me a different result set from the original. Any help with improving it will be greatly appreciated. Thanks. Here is the SQL:
(SELECT
CONCAT(city_name,', ',region) value,
latitude,
longitude,
id,
population,
( 3959 * acos( cos( radians($latitude) ) * cos( radians( latitude ) ) * cos( radians( longitude ) - radians($longitude) ) + sin( radians($latitude) ) * sin( radians( latitude ) ) ) )
AS distance,
CASE region
WHEN '$region' THEN 1
ELSE 0
END AS region_match
FROM `cities`
$where and foo_count > 5
ORDER BY region_match desc, foo_count desc
limit 0, 11)
UNION
(SELECT
CONCAT(city_name,', ',region) value,
latitude,
longitude,
id,
population,
( 3959 * acos( cos( radians($latitude) ) * cos( radians( latitude ) ) * cos( radians( longitude ) - radians($longitude) ) + sin( radians($latitude) ) * sin( radians( latitude ) ) ) )
AS distance,
CASE region
WHEN '$region' THEN 1
ELSE 0
END AS region_match
FROM `cities`
$where
ORDER BY region_match desc, population desc, distance asc
limit 0, 11)
limit 0, 11
The SQL does take some interpolated values (prefixed with the dollar sign($)).
The following might give the same result (I'm not sure about how the maximum/minimum functions are called in SQL, but you should get an idea -- you need two fields derived from foo_count which separate the items of the first part of your UNION from those of the second one and allow ordering within the first part without disturbing the order in the second part) -- of course, you later need a second query to throw the additional fields out again:
SELECT
CONCAT(city_name,', ',region) value,
latitude,
longitude,
id,
population,
( 3959 * acos( cos( radians($latitude) ) * cos( radians( latitude ) ) * cos( radians( longitude ) - radians($longitude) ) + sin( radians($latitude) ) * sin( radians( latitude ) ) ) )
AS distance,
min ( 6, max (foo_count, 5)) AS group_discriminator,
max ( 6, foo_count) AS rank_for_use_in_first_group,
CASE region
WHEN '$region' THEN 1
ELSE 0
END AS region_match
FROM `cities`
$where
ORDER BY group_discriminator desc, region_match desc, rank_for_use_in_first_group desc, population desc, distance asc
limit 0, 11
EDIT: Improvements

Mysql Recursive Query / Spherical Law of Cosines

I want to search nearest locations from coordinates (lat/lng) into mysql database, I use spherical law of cosines to searching these places :
SELECT onlycoord.id, locations.name, locations.ascii,
locations.latitude, locations.longitude,
locations.country, locations.elevation,
locations.gtopo30, locations.timezone,
(6371 * ACOS(
COS( RADIANS( 48.48 ) ) *
COS( RADIANS( onlycoord.latitude ) ) *
COS(
RADIANS( onlycoord.longitude ) -
RADIANS( 2.20 )
) +
SIN( RADIANS( 48.48 ) ) *
SIN( RADIANS( onlycoord.latitude ) )
)) AS distance
FROM onlycoord USE INDEX (coordinate)
LEFT JOIN locations USE INDEX (id)
ON (onlycoord.id = locations.id)
WHERE onlycoord.latitude BETWEEN (
48.48 - ( 5 / 111 )
) AND (
48.48 + ( 5 / 111 )
) AND onlycoord.longitude BETWEEN (
2.20 - ( 5 / ABS( COS(
RADIANS( 48.48 )
) * 111 ) )
) AND (
2.20 + ( 5 / ABS( COS(
RADIANS( 48.48 )
) * 111 ) )
)
ORDER BY distance ASC
LIMIT 0, 20
Where 6371 is earth radius (km), 111 (km) is 1° of latitude, cos(latitude) * 111 (km) is 1° of longitude and 5 (km) is search radius.
Problem : I want a minimum of 8 cities found, but 5 kms radius is small but fast for condensed zone (many cities), so if I use a search radius too big for condensed zone, query is slow (many results : order by), but for non-condensed the search radius is too small to find at least 8 cities...
How can I make a recursive query which increases automatically the search radius (x2) if the count of found cities < 8 (using mysql only) ?
Thanks
MySQL doesn't support recursive queries.
You have to make new queries depending on the result of the previous query.
By the way, for spatial queries I'd recommend a spatial database.