MYSQL - Group BY with MAX issue - mysql

I have the following table
Date, TelephoneNumber, Type
02/02/12, 123456, b
04/02/12, 123456, b
07/02/12, 123456, a
03/02/12, 789999, a
15/02/12, 789999, b
When running the following SQL
select TelephoneNumber, max(Date) as datetime, Type
from Table1
where Date > '2012-03-25 00:00'
group by TelephoneNumber
order by date desc;
I noted that the Type does not match its related Date. For example I am getting
07/02/12, 123456, b
15/02/12, 789999, a
It seems that it is taking the first record in Type .... even when I sorted in the other way round. Can someone please help me how I can solve this problem? I am using MySQL
Thanks in advance.
sandro

This question seems to pop up quite often.
Here's my solution:
SELECT TelephoneNumber, Date AS datetime, Type
from ( SELECT *
FROM Table1
WHERE Date > '2012-03-25 00:00'
ORDER BY Date DESC) AS h
GROUP BY TelephoneNumber
ORDER BY date DESC;
Check explanation here

This is because, as §11.16.3 "GROUP BY and HAVING with Hidden Columns" in the MySQL 5.6 Reference Manual puts it:
MySQL extends the use of GROUP BY so that the
select list can refer to nonaggregated columns not named in the
GROUP BY clause. This means that the
preceding query is legal in MySQL. You can use this feature to
get better performance by avoiding unnecessary column sorting
and grouping. However, this is useful primarily when all values
in each nonaggregated column not named in the GROUP
BY are the same for each group. The server is free to
choose any value from each group, so unless they are the same,
the values chosen are indeterminate. Furthermore, the selection
of values from each group cannot be influenced by adding an
ORDER BY clause. Sorting of the result set
occurs after values have been chosen, and ORDER
BY does not affect which values the server chooses.
[emphasis mine]
Instead, you need to write something like this:
select t1a.TelephoneNumber, t1a.Date, t1a.Type
from Table1 as t1a
left
join Table1 as t1b
on t1b.TelephoneNumber = t1a.TelephoneNumber
and t1b.Date > t1a.Date
where t1a.Date > '2012-03-25 00:00'
and t1b.TelephoneNumber IS NULL -- i.e., the join failed
;
to find the record with the greatest Date for each value of TelephoneNumber.

Order is applied after grouping. The value of max(Date) has already been computed, you're not sorting it in any way. Also, what is msisdn? It's impossible to tell what's going on with your query because what you're actually grouping on is opaque.

Related

MySQL return summed values and a virtual column as (count - sum)

I have a table as follows:
log (log_id, log_success (bool), log_created)
I would like to SELECT and return 3 columns date success and no_success, where the former does not exist in table and finally aggregate them by day.
I have created this query:
SELECT
log_created as 'date'
COUNT(*) AS 'count',
SUM(log_success) AS 'success'
SUM('count' - 'success') AS 'no_success'
FROM send_log
GROUP BY DATE_FORMAT(log_created, '%Y-%m-%d');
Would I be able to achieve it with this query? Is my syntax correct?
Thanks.
You can't reuse an alias defined in the select within the same select clause. The reason for this is that it might not even have been defined when you go to access it. But, you easily enough can repeat the logic:
SELECT
log_created AS date,
SUM(log_success) AS success,
COUNT(*) - SUM(log_success) AS no_success,
FROM send_log
GROUP BY
log_created;
I don't know why you are calling DATE_FORMAT in the group by clause of your query. DATE_FORMAT is usually a presentation layer function, which you call because you want to view a date formatted a certain way. Since it appears that log_created is already a date, there is no need to call DATE_FORMAT on it when aggregating. You also should not even need in the select clause, because the default format for a MySQL date is already Y-m-d.
You must select DATE_FORMAT(log_created, '%Y-%m-%d') if you want to group by this.
Also you can get the no_success counter with SUM(abs(log_success - 1))
SELECT
DATE_FORMAT(log_created, '%Y-%m-%d') date,
SUM(log_success) log_success,
SUM(abs(log_success - 1)) no_success
FROM send_log
GROUP BY date;
See the demo

Multiple Order by (date and time ) not working in MYSQL

I am combining multiple tables, filtering and then ordering by certain format. Every time I try to order by both date and time, it messes up the output.
SELECT
t1.empid,t2.lastName,t2.firstName
,t1.date as "OVERBOOKED DATE",t1.sfrom,t1.dept as "dname"
,t3.manager as "MANAGER"
from schedule_2 as t1
INNER JOIN employees_2 as t2 ON t1.empid = t2.empid
INNER JOIN dept_heads as t3 ON t1.dept = t3.Dept_name
JOIN ( SELECT empid,date from schedule_2
GROUP BY empid,date
having count(empid)>1
) inr on inr.empid=t1.empid and inr.date=t1.date
ORDER BY empid ASC, "OVERBOOKED DATE" ASC, sfrom ASC;
Im attaching the snippet of the output and the snippet of expected output. Im new to this, so would appreciate any help!
[
[
Your problem is that the values in your sfrom column are not valid inputs to the DATE function (as you are using it in your "Current Output" query). You need to use STR_TO_DATE to convert them to a valid value for ordering i.e. replace sfrom (or DATE(sfrom)) in your ORDER BY clause with
ORDER BY empid ASC, "OVERBOOKED DATE" ASC, STR_TO_DATE(sfrom, '%h%p') ASC
Edit
As has been pointed out by #TimBiegeleisen, "OVERBOOKED DATE" is just a string literal and is not valid to sort by, either the raw column name (t1.date), a regular name alias (OVERBOOKED_DATE) or the alias name in backticks should be used e.g.
ORDER BY empid ASC, `OVERBOOKED DATE` ASC, STR_TO_DATE(sfrom, '%h%p') ASC
You are storing date and time separately, which isn't usually a good idea, because it often leads to problems exactly like the one you are facing now. Given that the hour portion of your time string is fixed width, and that AM/PM sort correctly in ascending order, we can try the following:
ORDER BY
empid,
OVERBOOKED_DATE, -- DON'T use aliases with spaces, if you plan to order by them
RIGHT(sfrom, 2),
LEFT(sfrom, 2);
Note: Doing ORDER BY "OVERBOOKED DATE" appears to be ordering by a string literal, which is really no ordering at all. Instead, use the following alias:
OVERBOOKED_DATE
And then order by this single term alias.

MySQL ONLY_FULL_GROUP_BY enabled with CONCAT

I have a table in a database mysql (5.7.21) like this:
+----------+--------------+-----------+-----------+
| id_price | id_reference | price_usd | unix_time |
+----------+--------------+-----------+-----------+
And I need to extract the average price (price_usd) grouped by week of year, or month (unix_time).
I prepare this query:
SELECT CONCAT(WEEKOFYEAR(FROM_UNIXTIME(unix_time)),
'-',
YEAR(FROM_UNIXTIME(unix_time))) as date,
AVG(price_usd) AS "model"
FROM price_avg
INNER JOIN reference ON reference.id_reference=price_avg.id_reference
WHERE price_avg.id_reference=1
GROUP BY WEEKOFYEAR(FROM_UNIXTIME(unix_time)),
YEAR(FROM_UNIXTIME(unix_time)),
price_avg.id_reference
ORDER BY unix_time ASC
The inner join is useful to get the name of the product having the the id.
I get this error:
#1055 - Expression #1 of SELECT list is not in GROUP BY clause and
contains nonaggregated column 'name_of_db.price_avg.unix_time' which is not
functionally dependent on columns in GROUP BY clause; this is
incompatible with sql_mode=only_full_group_by
I cannot change the settings of MySQL (I can't disable ONLY_FULL_GROUP_BY mode or anything else).
How do I have to change the query to extract the data in MySQL 5.7.21?
Thanks in advance.
You can use sub query so that it will be a full group by.
Select `date`,
AVG(price_usd) AS "model"
From (
SELECT CONCAT(WEEKOFYEAR(FROM_UNIXTIME(unix_time)),
'-',
YEAR(FROM_UNIXTIME(unix_time))) as `date`,
price_usd
FROM price_avg
INNER JOIN reference ON reference.id_reference=price_avg.id_reference
WHERE price_avg.id_reference=1
) t
GROUP BY `date`
ORDER BY substring(`date`, -4), substring(`date`, 1, 2) ASC;
Result:
date model
48-1998 11.99
36-2001 19.99
I solved with this query:
SELECT DATE_FORMAT(FROM_UNIXTIME(unix_time),'%Y-%m') as date, AVG(price_usd) AS model FROM price_avg INNER JOIN reference ON reference.id_reference=price_avg.id_reference WHERE price_avg.id_reference=1
GROUP BY date, price_avg.id_reference ORDER BY date ASC
I have week and year inverted but I can resolve in client enviroment!
Thank you all.
Hit this same problem, reported it as a possible bug on MySQL:
https://bugs.mysql.com/bug.php?id=90792&thanks=4
From the initial response, it sounded like it might be treated as a bug and fixed, but I think the follow-up suggests that GROUP BY expressions (instead of columns) aren't part of the SQL standard, and that determining if a complex expression is completely derived from GROUPed expressions is difficult and is, for now at least, something they decided not to pursue:
https://mysqlserverteam.com/when-only_full_group_by-wont-see-the-query-is-deterministic/
There are some workarounds in the meantime.

A Database Error Occurred Expression #2 of SELECT list is not in GROUP BY clause and contains nonaggregated column

I have this query
SELECT substring(TGLLAHIR, 1, 7) as TGLLAHIR_,
substring(TGLLAHIR, 5, 2) as BULAN, `TGLLAHIR` as `TGL`
FROM `m_pasien`
WHERE substring(TGLLAHIR,1,4) = '2013'
GROUP BY substring(TGLLAHIR, 1, 7)
ORDER BY `TGLLAHIR` ASC
but message error showing
Expression #2 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'rsukemba_kojarsuk.m_pasien.TGLLAHIR' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by
How do I solve this?
You don't need group by, use distinct instead
SELECT distinct
substring(TGLLAHIR, 1, 7) as TGLLAHIR_
,substring(TGLLAHIR, 5, 2) as BULAN
,`TGLLAHIR` as `TGL`
FROM `m_pasien`
WHERE substring(TGLLAHIR,1,4) = '2013'
ORDER BY `TGLLAHIR` ASC
It seems that you are misusing the group by clause in this query. Group by is useful when aggregating data. For example, if you had a table of accounts, dates, and deposit amounts; and you wanted to know the total deposits into each account over all dates, you could write something like
select account, sum(deposits)
from some_table
group by account
It's unclear from you question alone what exactly you're trying to do, and even harder because your strangely-named variables are listed without any context. BUT! it's probably safe to say that you don't need to use a group by here, so just remove it.
P.S. I would be wary of doing a select distinct unless you know what you're throwing away and are certain that you don't need it.
You should use ANY_VALUE() aggregate function to suppress this warning message and make the query runnable. MySQL doesn't know which value of the non-aggregated field it should include in the result set. Every field that is not part of the GROUP BY clause should be wrapped in the aggregate function Here is, how it should look like:
SELECT substring(TGLLAHIR, 1, 7) as TGLLAHIR_,
substring(TGLLAHIR, 5, 2) as BULAN,
ANY_VALUE(`TGLLAHIR`) as `TGL`
FROM `m_pasien`
WHERE substring(TGLLAHIR,1,4) = '2013'
GROUP BY substring(TGLLAHIR, 1, 7)
ORDER BY `TGLLAHIR` ASC
And here is the explanation of this point in MySQL refference: https://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html#function_any-value
I think this problem due to strict mode enabled in your MySQL version. Kindly disable strict mode and try again.
To check whether strict mode is enabled or not run the below sql:
SHOW VARIABLES LIKE 'sql_mode';
If one of the value is STRICT_TRANS_TABLES, then strict mode is enabled.
To disable strict mode run the below sql:
set global sql_mode='';
Try again..

Use the result of the MySQL SELECT query as a WHERE condition in the same query

I'm trying to do this query:
SELECT MAX(`peg_num`)
AS "indicator"
FROM `list`
WHERE `list_id` = 1
AND "indicator" >= 1
But I'm getting the result of NULL. What I should be getting is 99, as the range of peg_num is 00 to 99.
The value checked against "indicator" should actually be a user input, so I want it to be versatile. But, it does give me the correct result if I flip the equality around:
SELECT MAX(`peg_num`)
AS "indicator"
FROM `list`
WHERE `list_id` = 1
AND "indicator" <= 1
Why would it do this?
Edit:
As suggested, I'm using the HAVING clause... but I just ditched the alias for now anyway:
SELECT MAX(`peg_num`) AS "indicator"
FROM `list`
GROUP BY `list_id`
HAVING MAX(`peg_num`) <= 40
Still very stubborn. It gives me 99 now no matter the value in the having clause, regardless of the inequality.
Edit2:
As a clarification:
What I want to happen is the query select the largest value in the range of peg_num, but only if it is larger than a user-given input. So, the max in this case is 99. If the user wants to select a number like 101, he/she can't because it's not in the range.
Because of double quotes, "indicator" in WHERE clause is interpreted as a string. Thus, it evaluates to 0, meaning it is always less than 1. Column names must be escaped in backticks.
Keep in mind that WHERE clause is executed before SELECT an hence aliases defined in SELECT can not be used in WHERE clause.
SELECT MAX(`peg_num`) AS `indicator`
FROM `list`
WHERE `list_id` = 1
HAVING `indicator` >= 1
You might want to check out the link on the answer to another Stack question about not being allowed to use alias in where clause:
Can you use an alias in the WHERE clause in mysql?
Paul Dixon cites:
It is not allowable to refer to a column alias in a WHERE clause,
because the column value might not yet be determined when the WHERE
clause is executed. See Section B.1.5.4, “Problems with Column
Aliases”.
Also:
Standard SQL disallows references to column aliases in a WHERE clause.
The behavior you're seeing in your query when you swap the '<=' and '>=' operators, results from the query comparing the string/varchar 'indicator' to the number 1.
That's why you see the correct answer..when ('indicator' >= 1) which is true, and null when ('indicator' <= 1) which is false.
I don't know, but I'm amazed either of them work at all. WHERE works serially on fields belonging to individual records and I wouldn't expect it to work on "indicator" since that's a group calculation.
Does this do what you want?
SELECT max(`peg_num` ) AS "indicator"
FROM actions
WHERE `peg_num` >=1
AND `list_id` <= 1
WHERE happens before SELECT, and don't know what's "indicator".
You should use HAVING (with GROUP BY) to use the SELECT fields
Here's the documentation for syntax
http://dev.mysql.com/doc/refman/5.5/en/select.html
Something like this is the idea
SELECT MAX(peg_num) AS indicator
FROM list
WHERE list_id = 1
HAVING indicator <= 1
I can't test it and i never met Mysql so just the idea,
You should use HAVING
No quotes in HAVING condition
This must work:
SELECT MAX(peg_num)
AS indicator
FROM list
WHERE list_id = 1
HAVING indicator >= 1
I completely re-invented my query and it worked. The thing is, I had to use a nested query (and I wanted to not do that as much as possible, my professor had always discouraged it).
Anyway, here it is:
SELECT IF(`key` < 900, `key`, null) `key`
FROM (
(
SELECT MAX( `peg_num` ) AS `key`
FROM `list`
WHERE `list_id` =1
) AS `derivedTable`
)