SELECT column but also add AVG column - mysql

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.

Related

Speeding up mySQL SPATIAL select based on distance to coordinates

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 )

MySQL: How can I calculate the median in a specific radius (longitude / latitude)

I want to calculate the median and average for given coordinates in a specific radius.
The important attributes are:
- latitude
- longitude
- price
The sql command to calculate the average is:
SELECT avg(price) as average
FROM (SELECT r.*,
( 6371 * acos( cos( radians(37.3541079) ) * cos( radians( ANY_VALUE(`latitude` )) ) * cos( radians( ANY_VALUE(`longitude`) ) - radians(-121.9552356) ) + sin( radians(37.3541079) ) * sin( radians( ANY_VALUE(`latitude`) ) ) ) ) AS distance
FROM `Rental` r
) r
WHERE distance <= 20;
My question is how can I calculate the median for the price in the given coordinates and radius. MySQL has no median() function.
EDIT:
Now I have tried the code from Simple way to calculate median with MySQL
SELECT AVG(middle_values) AS 'median' FROM (
SELECT t1.price AS 'middle_values' FROM
(
SELECT #row:=#row+1 as `row`, x.price
FROM rental AS x, (SELECT #row:=0) AS r
WHERE 1
-- put some where clause here
ORDER BY x.price
) AS t1,
(
SELECT COUNT(*) as 'count'
FROM rental x
WHERE 1
-- put same where clause here
) AS t2
-- the following condition will return 1 record for odd number sets, or 2 records for even number sets.
WHERE t1.row >= t2.count/2 and t1.row <= ((t2.count/2) +1)) AS t3;
It works for all the 200'000 records, but when I will add the WHERE distance <= 20 is the mysql - request overloaded.
SELECT AVG(middle_values) AS 'median' FROM (
SELECT t1.price AS 'middle_values' FROM
(
SELECT #row:=#row+1 as `row`, x.price
FROM rental AS x, (SELECT #row:=0) AS r, (SELECT a.*,
( 6371 * acos( cos( radians(37.3541079) ) * cos( radians( ANY_VALUE(`latitude` )) ) * cos( radians( ANY_VALUE(`longitude`) ) - radians(-121.9552356) ) + sin( radians(37.3541079) ) * sin( radians( ANY_VALUE(`latitude`) ) ) ) ) AS distance
FROM `Rental` a
) a
WHERE distance <= 20
-- put some where clause here
ORDER BY x.price
) AS t1,
(
SELECT COUNT(*) as 'count'
FROM rental x, (SELECT a.*,
( 6371 * acos( cos( radians(37.3541079) ) * cos( radians( ANY_VALUE(`latitude` )) ) * cos( radians( ANY_VALUE(`longitude`) ) - radians(-121.9552356) ) + sin( radians(37.3541079) ) * sin( radians( ANY_VALUE(`latitude`) ) ) ) ) AS distance
FROM `Rental` a
) a
WHERE distance <= 20
-- put same where clause here
) AS t2
-- the following condition will return 1 record for odd number sets, or 2 records for even number sets.
WHERE t1.row >= t2.count/2 and t1.row <= ((t2.count/2) +1)) AS t3;
Is there somewhere a misstep?
The problem is with the table scan to compute the distances, not with the median.
Put the data in a TEMPORARY TABLE so you don't have to evaluate it 3 times (avg, count, and median).
Add a "bounding box" to the innermost WHERE to limit the checks to a 20x20 "square".
INDEX(latitude)
Use HAVING distance < 20 instead of needing yet-another subquery.

Muliple Order by MYSql

I have stores records in my table and I want to sort them on the basis of highest rating of store and which has nearest distance to my location.
SELECT rating,
( 3959 * acos( cos( radians(37) )
* cos( radians( lat ) )
* cos( radians( lon )
- radians(-122) )
+ sin( radians(37) )
* sin( radians( lat ) )
)
) AS distance
FROM mystores sr
order by sr.rating desc ,distance asc
It is not giving me my desired results
Table Mystores
id|rating|distance
66 5 55
55 4 56
99 3 60
I assume that you want just the closest store and you want to ignore all other with the same rank but with bigger distance.
The group by raiting allows us to get the minimal distance for each raiting.
select m1.*
from mystores m1
join (
select m.raiting,
min(m.distance) distance
from mystores m
group by m.raiting
) m2
on m2.raiting = m1.raiting and
m2.distance = m1.distance
order by m1.raiting desc, m1.distance asc

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.