I'm trying to get the minimum and maximum price on a mysql(MyISAM) query.
I'm using this query for:
SELECT MAX(price_feed) as max,
MIN(price_feed) as min,
SQRT( POW(69.1 * (latitude_feed - 51.542980), 2) + POW(69.1 * (-0.149323 - longitude_feed ) * COS(latitude_feed / 57.3), 2)) AS distance
FROM feed
WHERE listing_type_feed = 'rental'
and property_type_feed IN ("Flat", "Apartament", "Penthouse", "Studio")
HAVING distance < 2
but it returns nothing, while when i try
SELECT price_feed as max,
price_feed as min,
SQRT( POW(69.1 * (latitude_feed - 51.542980), 2) + POW(69.1 * (-0.149323 - longitude_feed ) * COS(latitude_feed / 57.3), 2)) AS distance
FROM feed
WHERE listing_type_feed = 'rental'
and property_type_feed IN ("Flat", "Apartament", "Penthouse", "Studio")
HAVING distance < 2
It returns 2600 rows.
Thanks
You need a nested query or CTE depending on your RDBMS
First you calculate what property are in a 2km radius and then you calculate the max/min prices from that result, also you dont need having instead you use the whereclausule
SELECT MAX(price_feed) as max, MIN(price_feed) as min
FROM (
SELECT price_feed
FROM feed
WHERE
listing_type_feed = 'rental'
and property_type_feed IN ("Flat", "Apartament", "Penthouse", "Studio")
and SQRT( POW(69.1 * (latitude_feed - 51.542980), 2) + POW(69.1 * (-0.149323 - longitude_feed ) * COS(latitude_feed / 57.3), 2)) < 2
) as filter_properties
In pure SQL it is a mistake to select fields that are not aggregate functions nor group by fields, in a query that uses aggregates or group by.
In mysql you can select any field, but if it is not a group by field or an aggregate function the value returned may be any value from the resultset. So it is undefined unless you group by a key.
In your first query there is no group by clause, so the aggregates use all rows as one group. And the value of distance is the value of a row (any).
Related
Given an example query like
SELECT IF(
ABS(sum(grades) / count(*) * 100) < 1,
ROUND(sum(grades) / count(*) * 100, 2),
IF(ABS(sum(grades) / count(*) * 100) < 10,
ROUND(sum(grades) / count(*) * 100, 1),
ROUND(sum(grades) / count(*) * 100, 0)
)
)
from important_table
group by timestamp
is it possible to not have to calculate the sum 3 times every grouping, but instead store the sum(grades) / count(*) * 100 in a variable beforehand, still grouped by timestamp, and use that in the query? This looks a bit dirty and repeating to me.
Is that a real optimization possibility? Or does mysql cache it anyway when executing the query?
I wouldn't worry about recalculating the values. The SQL optimizer can decide to calculate them only once if it wants (althoughI don't think the MySQL optimizer does this).
However, assuming that grades is not NULL, you can simplify your expressions by using avg():
select (case when ABS(avg(grades) * 100) < 1
then ROUND(avg(grades) * 100, 2)
when ABS(avg(grades) * 100) < 10
then ROUND(avg(grades) * 100, 1)
else ROUND(avg(grades) * 100, 0)
end)
from important_table
group by timestamp;
I'm not particularly knowledgeable about MYSQL queries and optimising them, so I require a bit of help on this one. I'm checking a table of international cities to find the 10 nearest cities based on the longitude and latitude values in the table.
The query I'm using for this is as follows:
SELECT City as city,
SQRT(POW(69.1 * (Latitude - 51.5073509), 2) +
POW(69.1 * (-0.1277583 - Longitude) * COS(Latitude / 57.3), 2)) AS distance
from `cities`
group by `City`
having distance < 50
order by `distance` asc
limit 10
(The longitude & latitude values are obviously placed dynamically in my code)
sometimes this can take around 3-4 mintues of my development environment to complete.
Have I made any classic mistakes here, or is there a much better query I should be using to retrieve this data?
Any help woould be greatly appreciated.
Assuming City is unique and you are abusing GROUP BY and HAVING in order to get a cleaner code
SELECT City as city,
SQRT(POW(69.1 * (Latitude - 51.5073509), 2) +
POW(69.1 * (-0.1277583 - Longitude) * COS(Latitude / 57.3), 2)) AS distance
from `cities`
where SQRT(POW(69.1 * (Latitude - 51.5073509), 2) +
POW(69.1 * (-0.1277583 - Longitude) * COS(Latitude / 57.3), 2)) < 50
order by `distance` asc
limit 10
If City is unique then the aggregation is done on single rows.
MySQL uses sort operation to implement GROUP BY.
Sort complexity is O(n*log(n)), so without indexes this is going to complexity of GROUP BY.
If City is not unique than the filtering in the HAVING CLAUSE is done on one arbitrary row which is for sure not what the OP intended.
The case where HAVING and WHERE are both relevant for filtering and HAVING has an performance advantage is where the filtering is done on the aggregated column, there are some heavy calculations and the GROUP BY operation significantly reduce the number of rows
select x,... from ... group by x having ... some heavy calculations on x ...
I was trying to convert this sql query into django orm query but I got stuck how do I perform sqrt and other mathematical calculation in a single ORM query.
SELECT latitude, longitude, SQRT(
POW(69.1 * (latitude - [input_lat]), 2) +
POW(69.1 * ([input_lng] - longitude) * COS(latitude / 57.3), 2)) AS distance
FROM MyTable HAVING distance < 25 ORDER BY distance
What I tried -
MyTable.objects.extra(select={"distance": X}).extra(where=["distance > 25"]).order_by('distance')
What should I write in place of X? am I going in right direction ?
if I write this
MyTable.objects.extra(select={"distance": "SQRT(POW(69.1 * (latitude - 37), 2) + POW(69.1 * (-122 - longitude) * COS(latitude / 573), 2))"}).extra(where=["distance > 25"]).order_by('distance')
it throws an error, OperationalError: no such function: SQRT. which I guess my DB(sqlite3) doesn't support this function. how to overcome this problem?
MyTable has 5 columns including latitude and longitude.
we will get the value of input_lat and input_lng from enduser.
I would like to know is there a way to select randomly generated number between 100 and 500 along with a select query.
Eg: SELECT name, address, random_number FROM users
I dont have to store this number in db and only to use it to display purpose.
I tried it something like this, but it can't get to work..
SELECT name, address, FLOOR(RAND() * 500) AS random_number FROM users
Hope someone help me out.
Thank you
This should give what you want:
FLOOR(RAND() * 401) + 100
Generically, FLOOR(RAND() * (<max> - <min> + 1)) + <min> generates a number between <min> and <max> inclusive.
Update
This full statement should work:
SELECT name, address, FLOOR(RAND() * 401) + 100 AS `random_number`
FROM users
As RAND produces a number 0 <= v < 1.0 (see documentation) you need to use ROUND to ensure that you can get the upper bound (500 in this case) and the lower bound (100 in this case)
So to produce the range you need:
SELECT name, address, ROUND(100.0 + 400.0 * RAND()) AS random_number
FROM users
Additional to this answer, create a function like
CREATE FUNCTION myrandom(
pmin INTEGER,
pmax INTEGER
)
RETURNS INTEGER(11)
DETERMINISTIC
NO SQL
SQL SECURITY DEFINER
BEGIN
RETURN floor(pmin+RAND()*(pmax-pmin));
END;
and call like
SELECT myrandom(100,300);
This gives you random number between 100 and 300
You could create a random number using FLOOR(RAND() * n) as randnum (n is an integer), however if you do not need the same random number to be repeated then you will have to somewhat store in a temp table. So you can check it against with where randnum not in (select * from temptable)...
these both are working nicely:
select round(<maxNumber>*rand())
FLOOR(RAND() * (<max> - <min> + 1)) + <min> // generates a number
between <min> and <max> inclusive.
This is correct formula to find integers from i to j where i <= R <= j
FLOOR(min+RAND()*(max-min))
I'm using following sql code to find out 'ALL' poi closest to the set coordinates, but I would want to find out specific poi instead of all of them. When I try to use the where clause I get an error and it doesn't work and this is where I'm currently stuck, since I only use one table for all the coordinates off all poi's.
SET #orig_lat=55.4058;
SET #orig_lon=13.7907;
SET #dist=10;
SELECT
*,
3956 * 2 * ASIN(SQRT(POWER(SIN((#orig_lat -abs(latitude)) * pi()/180 / 2), 2)
+ COS(#orig_lat * pi()/180 ) * COS(abs(latitude) * pi()/180)
* POWER(SIN((#orig_lon - longitude) * pi()/180 / 2), 2) )) as distance
FROM geo_kulplex.sweden_bobo
HAVING distance < #dist
ORDER BY distance limit 10;
The problem is that you can not reference an aliased column (distancein this case) in a select or where clause. For example, you can't do this:
select a, b, a + b as NewCol, NewCol + 1 as AnotherCol from table
where NewCol = 2
This will fail in both: the select statement when trying to process NewCol + 1 and also in the where statement when trying to process NewCol = 2.
There are two ways to solve this:
1) Replace the reference by the calculated value itself. Example:
select a, b, a + b as NewCol, a + b + 1 as AnotherCol from table
where a + b = 2
2) Use an outer select statement:
select a, b, NewCol, NewCol + 1 as AnotherCol from (
select a, b, a + b as NewCol from table
) as S
where NewCol = 2
Now, given your HUGE and not very human-friendly calculated column :) I think you should go for the last option to improve readibility:
SET #orig_lat=55.4058;
SET #orig_lon=13.7907;
SET #dist=10;
SELECT * FROM (
SELECT
*,
3956 * 2 * ASIN(SQRT(POWER(SIN((#orig_lat -abs(latitude)) * pi()/180 / 2), 2)
+ COS(#orig_lat * pi()/180 ) * COS(abs(latitude) * pi()/180)
* POWER(SIN((#orig_lon - longitude) * pi()/180 / 2), 2) )) as distance
FROM geo_kulplex.sweden_bobo
) AS S
WHERE distance < #dist
ORDER BY distance limit 10;
Edit: As #Kaii mentioned below this will result in a full table scan. Depending on the amount of data you will be processing you might want to avoid that and go for the first option, which should perform faster.
The reason why you cant use your alias in the WHERE clause is the order in which MySQL executes things:
FROM
WHERE
GROUP BY
HAVING
SELECT
ORDER BY
When executing your WHERE clause, the value for your column alias is not yet calculated. This is a good thing, because it would waste a lot of performance. Imagine many (1,000,000) rows -- to use your calculation in the WHERE clause, each of those 1,000,000 would first have to be fetched and calculated so the WHERE condition can compare the calculation results to your expectation.
You can do this explicitly by either
using HAVING (thats the reason why HAVING has another name as WHERE - its a different thing)
using a subquery as illustrated by #MostyMostacho (will effectively do the same with some overhead)
put the complex calculation in the WHERE clause (will effectively give the same performance result as HAVING)
All those will perform almost equally bad: each row is fetched first, the distance calculated and finally filtered by distance before sending the result to the client.
You can gain much (!) better performance by mixing a simple WHERE clause for distance approximation (filtering rows to fetch first) with the more precise euclidian formula in a HAVING clause.
find rows that could match the #distance = 10 condition using a WHERE clause based on simple X and Y distance (bounding box) -- this is a cheap operation.
filter those results using the formula for euclidian distance in a HAVING clause -- this is an expensive operation.
Look at this query to understand what i mean:
SET #orig_lat=55.4058;
SET #orig_lon=13.7907;
SET #dist=10;
SELECT
*,
3956 * 2 * ASIN(SQRT(POWER(SIN((#orig_lat -abs(latitude)) * pi()/180 / 2), 2)
+ COS(#orig_lat * pi()/180 ) * COS(abs(latitude) * pi()/180)
* POWER(SIN((#orig_lon - longitude) * pi()/180 / 2), 2) )) as distance
FROM geo_kulplex.sweden_bobo
/* WHERE clause to pre-filter by distance approximation .. filter results
later with precise euclidian calculation. can use indexes. */
WHERE
/* i'm unsure about geo stuff ... i dont think you want a
distance of 10° here, please adjust this properly!! */
latitude BETWEEN (#orig_lat - #dist) AND (#orig_lat + #dist)
AND longitude BETWEEN (#orig_lon - #dist) AND (#orig_lon + #dist)
/* HAVING clause to filter result using the more precise euclidian distance */
HAVING distance < #dist
ORDER BY distance limit 10;
For those who are interested in the constant:
3956 is the radius of the earth in miles, so the resulting distance is measured in miles
6371 is the radius of the earth in kilometers, so use this constant to measure distance in kilometers
Find more information in the wiki about the Haversine formula