How to make sure HAVING happens before GROUP BY - mysql

I'm grabing a list of banks that are a certain distance from a point
ICBC 6805 119.86727673154
Bank of Shanghai 7693 372.999006839511
Bank of Ningbo 7626 379.19406334356
ICBC 6790 399.580754911156
Minsheng Bank 8102 485.904900718796
Standard Chartered Bank 8205 551.038506011767
Guangdong Development Bank 8048 563.713291030103
Bank of Shanghai 7688 575.327270234431
Bank of Nanjing 7622 622.249663674778
however I just want to grab 1 venue of each chain.
The query so far
SELECT name, id , (
GLength( LineStringFromWKB( LineString( `lnglat` , POINT( 121.437478728836, 31.182877821277 ) ) ) )
) *95000 AS `distance`
FROM `banks`
WHERE (
lnglat != ""
)
AND (
published =1
)
HAVING (
distance <700
)
ORDER BY `distance` ASC
using group by name doesn't work because it evaluates then the distance does not fall into the range. In other words if there is an ICBC over 700 m away with a lower id, then ICBC will not appear in the results even though two ICBC are withing 700 m. So I suspect this happens because group by happens before having
Or maybe there is a different solution?
I could not move the distance check to the where as it is not a real column #1054 - Unknown column 'distance' in 'where clause'

Select your entire query as a table and do then do the Group By on that.
E.g.
Select * FROM
(SELECT name, id , (
GLength( LineStringFromWKB( LineString( `lnglat` , POINT( 121.437478728836, 31.182877821277 ) ) ) )
) *95000 AS `distance`
FROM `banks`
WHERE (
lnglat != ""
)
AND (
published =1
)
HAVING (
distance <700
)
ORDER BY `distance` ASC) t
GROUP BY t.name

Are you sure your sample is complete as there is no GROUP BY condition? If you want banks within 700 miles then put that in WHERE condition. If you only want 1 bank reported then put that in the GROUP BY. You might need to repeat the Glenght in the Group By rather than using the alias - depends on your version of SQL. You are not grabbing a list of banks a certain distance from a point - you are grabbing banks with position and calculating the distance from a certain point. You want only banks with 700 of the calculated distance and if a bank is repeated you only want it listed once.
SELECT name, id, (GLength( ...) AS [distance]
FROM [banks]
WHERE [lnglat] != "" ... AND [distance] <700
Group By [name], [id], [distance]
ORDER BY [distance] ASC

I'm not sure if this is what you are looking for, just getting one bank with the least distance.
SELECT banks.name, banks.id, banks_with_least_distance.distance
FROM banks JOIN
(
SELECT name, min(
GLength( LineStringFromWKB( LineString( `lnglat` , POINT( 121.437478728836, 31.182877821277 ) ) ) )
*95000) AS `distance`
FROM `banks`
WHERE (lnglat != "") AND (published =1) AND (GLength( LineStringFromWKB( LineString( `lnglat` , POINT( 121.437478728836, 31.182877821277 ) ) ) ) *95000 < 700)
GROUP BY `name`
) AS banks_with_least_distance ON banks.name = banks_with_least_distance.name
ORDER BY banks_with_least_distance.distance DESC
Edited: changed the distance in the where clause to the actual formula.

Related

MYSQL, SELECT syntax problem, how do I select rows of a table based on a requirement in another table

I have solved 90% of this problem except for one command and am having trouble on the syntax. I have a Customer table and a Rep table, and I want to select customers who:
are closest to a lat lng point (which I have figured out)
exist within a Rep's boundry (Polygon),
What am I doing wrong here?
SELECT
id,
name,
contact,
address,
location,
X(location) AS "longitude",
Y(location) AS "latitude",
(
GLength(
LineStringFromWKB(
LineString(
location,
GeomFromText('POINT(-41.29463639999999 174.7748175)')
)
)
)
)
AS distance
FROM Customers as Customer
WHERE
location IS NOT NULL
AND
Customer.id NOT IN (SELECT CustomerId from SalesRepCustomer WHERE SalesRepId = 15)
--- CODE BELOW I AM HAVING TROUBLE WITH:
AND
Customer.location IN (
SELECT * from SalesReps as SalesRep
WHERE ST_CONTAINS(SalesRep.bounds, location)
AND SalesRep.id = 15
)
--- CODE ABOVE I AM HAVING TROUBLE WITH:
ORDER BY distance ASC
LIMIT 10;
first, use exists instead of IN
second, use subquery to order by your distance
SELECT * FROM (SELECT
id,
name,
contact,
address,
location,
X(location) AS "longitude",
Y(location) AS "latitude",
(
GLength(
LineStringFromWKB(
LineString(
location,
GeomFromText('POINT(-41.29463639999999 174.7748175)')
)
)
)
)
AS distance
FROM Customers as Customer
WHERE
location IS NOT NULL
AND
Customer.id NOT IN (SELECT CustomerId from SalesRepCustomer WHERE SalesRepId = 15)
AND
Customer.location exists (
SELECT *1 from SalesReps as SalesRep
WHERE ST_CONTAINS(SalesRep.bounds, location)
AND SalesRep.id = 15
)
) t1
ORDER BY t1.distance ASC
LIMIT 10;

MySql sort within sorted results set

I have the following query which queries a table of sports results for the last 20 matches that involved a teams, returning goals conceeded in each of these matches.
SELECT *, `against` AS `goalsF` , `for` AS `goalsA`
FROM `matches` , `teams` , `outcomes`
WHERE (
`home_team_id`=7 AND `matches`.away_team_id = `teams`.team_id
OR
`away_team_id`=7 AND `matches`.home_team_id = `teams`.team_id
)
AND `matches`.score_id = `outcomes`.outcome_id
ORDER BY `against', `date` DESC
LIMIT 0 , 20
I want sort the results by goals conceeded and then within each group of goals conceeded by date so for example.
the first 4 results where goals conceded=1 in date order
then the next 3 might be results where conceded=2 in date order
I have tried ORDER by date,against - this gives me a strict date order
I have tried ORDER by against,date - this gives me matches beyond the last 20
Is it possible to do what I want to do?
Thanks everyone, I found this worked. This solution was posted by another user but then was removed, not sure why?
SELECT * FROM (
SELECT *, `against` AS `goalsF` , `for` AS `goalsA`
FROM `matches` , `teams` , `outcomes`
WHERE (
`home_team_id`=7 AND `matches`.away_team_id = `teams`.team_id
OR
`away_team_id`=7 AND `matches`.home_team_id = `teams`.team_id
)
AND `matches`.score_id = `outcomes`.outcome_id
ORDER by `goalsF`
LIMIT 0 , 20
) res
ORDER BY `date` DESC
If you want to limit by date, add the date range you are looking for into your WHERE clause and then order by the number of goals conceded.

MySQL get subquery value

I'm trying to calculate the distance of my centroid point, that's calculated through the total number of tags, and sum of the instant time that tags appear. So that's the concept of (tc_sum/cnt).
However the SELECT on the subquery, doesn't allow me to get the centroid point, because the "centr" is not calculated yet, and so i can't get the "distance".
Any help?
SELECT cnt, tc_sum, ROUND(tc_sum/cnt) as centr, distance
FROM (
SELECT SUM(timecode) as tc_sum, count(timecode) as cnt, ABS( centr - '".$timecode."' ) AS distance
FROM dados d
WHERE tag = 'donald'
AND filename = 'donald.mp4'
AND group_id = '1'
) d
SELECT
SUM(timecode) as tc_sum,
SUM(timecode) as cnt,
ABS( SUM(timecode) / SUM(timecode) - '".$timecode."' ) AS distance,
ROUND(SUM(timecode) / SUM(timecode)) AS centr
FROM dados d
WHERE tag = 'donald'
AND filename = 'donald.mp4'
AND group_id = '1'
A query works on row after row and you can't refer to aliases this way. You have to "recalculate" them again. "Recalculate" is not the right word, since the result isn't really calculated multiple times. The optimizer will take care of it being only calculated once. But an alias is only known after the query ran. I'm afraid my english sucks too much to explain it in a good way :)
Try
SELECT cnt, tc_sum, ROUND(tc_sum/cnt) as centr, distance
FROM (
SELECT SUM(timecode) as tc_sum, count(timecode) as cnt, ABS( ROUND(tc_sum/cnt)- '".$timecode."' ) AS distance
FROM dados d
WHERE tag = 'donald'
AND filename = 'donald.mp4'
AND group_id = '1'
) d

MySQL - query to return NULL

I have the following code:
SELECT q25, (
(
AVG( q1 ) + AVG( q2 ) + AVG( q3 ) ) /3 ) AS Overall
FROM t_results
WHERE brand = 'XYZ'
AND DATE = 'MAY2012'
GROUP BY q25
ORDER BY Overall
DESC LIMIT 1
If there is no data found by the query phpmyadmin returns the following message (which is quite correct):
MySQL returned an empty result set (i.e. zero rows). ( Query took 0.0178 sec )
However, what I'd like is to actually return a NULL value, is this possible? I appreciate this might not be best practise but I'm working with inherited code and this might be the simplist and quickest route to a solution.
Thanks as always,
H.
Create a table with exactly one row. Then you can use left join to achieve the desired NULL result.
CREATE TABLE dummy (d TINYINT NOT NULL);
INSERT INTO dummy SET d = 1;
SELECT q25,
( ( AVG( q1 ) + AVG( q2 ) + AVG( q3 ) ) /3 ) AS Overall
FROM dummy LEFT JOIN t_results
ON brand = 'XYZ'
AND DATE = 'MAY2012'
GROUP BY q25
ORDER BY Overall DESC
LIMIT 1
You can also replace the dummy table with a subquery:
SELECT q25,
( ( AVG( q1 ) + AVG( q2 ) + AVG( q3 ) ) /3 ) AS Overall
FROM (SELECT 1) AS dummy LEFT JOIN t_results
ON brand = 'XYZ'
AND DATE = 'MAY2012'
GROUP BY q25
ORDER BY Overall DESC
LIMIT 1
Tested this via sqlfiddle, where you can also experiment with alternatives.
The conditions selecting the result, which used to be in the WHERE clause, now have to go into the ON clause. Otherwise the left join would produce non-NULL rows which would be removed by the WHERE, instead of generating a single NULL row if no matching row could be found. If there were no WHERE conditions in the original query, ON 1 could be used to express any row matches.
You can use a UNION combined with a LIMIT to supply the NULL values:
(SELECT q25,
(AVG(q1) + AVG(q2) + AVG(q3))/3 AS Overall
FROM t_results
WHERE brand = 'XYZ'
AND DATE = 'MAY2012'
GROUP BY q25
ORDER BY Overall DESC
LIMIT 1
)
UNION ALL
(SELECT NULL, NULL)
LIMIT 1;
This only works when you know that the first query will never yield more than one result, though. Which is the case here, so this might be the best solution for you, but the approach given in my other answer is more general.
There is a fiddle for this to experiment with.
The coalesce() function can be used to return the first non-null value from a number of comma separated columns or strings. The values/columns are evaluated left to right, so if you want to pop a string into the arguments that isn't null, make sure you place it to the right of the columns that you are testing against.
select
coalesce(
(
SELECT
q25
FROM
t_results
WHERE
brand = 'XYZ'
AND DATE = 'MAY2012'
GROUP BY
q25
LIMIT 1
), 'null') as q25,
coalesce(
(
SELECT
((AVG( q1 ) + AVG( q2 ) + AVG( q3 ) ) /3 ) AS Overall
FROM t_results
WHERE
brand = 'XYZ'
AND DATE = 'MAY2012'
LIMIT 1
), 'null') as Overall
from
t_results
group by
1, 2;
If you don't have data that matches your where clause, this will return null, null as a row.

MySQL Complex Inner Join

Suppose equity has a column called TickerID. I would like to replace the 111's with equity.TickerID. MySQL can't seem to resolve the scope and returns an unknown column when I try that. This SQL statement works but I need to run it for each ticker. Would be nice if I could get a full table.
SELECT Ticker,
IF(tbl_m200.MA200_Count = 200,tbl_m200.MA200,-1) AS MA200,
IF(tbl_m50.MA50_Count = 50,tbl_m50.MA50,-1) AS MA50,
IF(tbl_m20.MA20_Count = 20,tbl_m20.MA20,-1) AS MA20
FROM equity
INNER JOIN
(SELECT TickerID,AVG(Y.Close) AS MA200,COUNT(Y.Close) AS MA200_Count FROM
(
SELECT Close,TickerID FROM equity_pricehistory_daily
WHERE TickerID = 111
ORDER BY Timestamp DESC LIMIT 0,200
) AS Y
) AS tbl_m200
USING(TickerID)
INNER JOIN
(SELECT TickerID,AVG(Y.Close) AS MA50,COUNT(Y.Close) AS MA50_Count FROM
(
SELECT Close,TickerID FROM equity_pricehistory_daily
WHERE TickerID = 111
ORDER BY Timestamp DESC LIMIT 50
) AS Y
) AS tbl_m50
USING(TickerID)
INNER JOIN
(SELECT TickerID,AVG(Y.Close) AS MA20,COUNT(Y.Close) AS MA20_Count FROM
(
SELECT Close,TickerID FROM equity_pricehistory_daily
WHERE TickerID = 111
ORDER BY Timestamp DESC LIMIT 0,20
) AS Y
) AS tbl_m20
USING(TickerID)
This seems to be some bug or "feature" of MySQL. Many persons seems to have the same problem with outer tables being out of scope.
Anyway... You could create functions that retrieve the information you want:
DROP FUNCTION IF EXISTS AveragePriceHistory_20;
CREATE FUNCTION AveragePriceHistory_20(MyTickerID INT)
RETURNS DECIMAL(9,2) DETERMINISTIC
RETURN (
SELECT AVG(Y.Close)
FROM (
SELECT Z.Close
FROM equity_pricehistory_daily Z
WHERE Z.TickerID = MyTickerID
ORDER BY Timestamp DESC
LIMIT 20
) Y
HAVING COUNT(*) = 20
);
SELECT
E.TickerID,
E.Ticker,
AveragePriceHistory_20(E.TickerID) AS MA20
FROM equity E;
You would get NULL instead of -1. If this is undesirable, you could wrap the function-call with IFNULL(...,-1).
Another way of solving this, would be to select for the time-frame, instead of using LIMIT.
SELECT
E.TickerID,
E.Ticker,
(
SELECT AVG(Y.Close)
FROM equity_pricehistory_daily Y
WHERE Y.TickerID = E.TickerID
AND Y.Timestamp > ADDDATE(CURRENT_TIMESTAMP, INTERVAL -20 DAY)
) AS MA20
FROM equity E;