MySQL GROUP BY - returning wrong dates - mysql

table: chat_thread
+-----------+----------+--------------------+
| id | title | date |
+-----------+----------+--------------------+
| 1 | Thread 1 | 2012-02-16 01:12:40|
| 2 | Thread 2 | 2012-02-17 02:32:44|
+-----------+----------+--------------------+
table: chat_comment
+-----------+----------+--------------------+
| id | t_id | date |
+-----------+----------+--------------------+
| 1 | 1 | 2012-02-19 19:45:32|
| 2 | 1 | 2012-02-15 22:29:20|
+-----------+----------+--------------------+
Here's my situation, I creating a basic forum with threads and comments. I want to order by the last comment date, and if there is no comments, then the thread start date. The problem I'm having is trying to echo out the last reply date in my while loop.
mysql_query("
SELECT *,
chat_comment.date AS commentDate,
chat_thread.date AS threadDate
FROM chat_thread
LEFT JOIN chat_comment ON chat_thread.id = chat_comment.t_id
GROUP BY chat_thread.id
ORDER BY commentDate DESC, threadDate DESC");
My issue isn't the correct order of the threads, but the comment dates. In my tables above, if I'm trying to echo out the date, I'm currently getting 2012-02-15 22:29:20 instead of the more recent 2012-02-19 19:45:32.
Is there something I'm doing wrong?
I think it has something to do with the GROUP BY, because if I change it to GROUP BY chat_comment.date then the dates in the while loop are accurate, but there are duplicates, because I am not grouping by the chat_thread.id

Instead of selecting the last chat_comment.date, you are selecting an arbitrary one. (See MySQL Reference Manual, ยง11.6.3: GROUP BY and HAVING with Hidden Columns for a detailed explanation of this.) You need to use MAX(chat_comment.date) instead of just chat_comment.date.

You have to use MAX() if you want a maximum value, and then you have to GROUP by all the other columns, like so:
mysql_query("
SELECT chat_thread.id, chat_thread.title, chat_thread.date,
MAX(chat_comment.date) AS commentDate
FROM chat_thread
LEFT JOIN chat_comment ON chat_thread.id = chat_comment.t_id
GROUP BY chat_thread.id, chat_thread.title, chat_thread.date
ORDER BY MAX(chat_comment.date) DESC, chat_thread.date DESC");

Related

Need some help optimising an SQL query

my client was given the following code and he uses it daily to count the messages sent to businesses on his website. I have looked at the MYSQL.SLOW.LOG and it has the following stats for this query, which indicates to me it needs optimising.
Count: 183 Time=44.12s (8073s) Lock=0.00s (0s)
Rows_sent=17337923391683297280.0 (-1), Rows_examined=382885.7
(70068089), Rows_affected=0.0 (0), thewedd1[thewedd1]#localhost
The query is:
SELECT
businesses.name AS BusinessName,
messages.created AS DateSent,
messages.guest_sender AS EnquirersEmail,
strip_tags(messages.message) AS Message,
users.name AS BusinessName
FROM
messages
JOIN users ON messages.from_to = users.id
JOIN businesses ON users.business_id = businesses.id
My SQL is not very good but would a LEFT JOIN rather than a JOIN help to reduce the number or rows returned? Ive have run an EXPLAIN query and it seems to make no difference between the LEFT JOIN and the JOIN..
Basically I think it would be good to reduce the number of rows returned, as it is absurdly big..
Short answer: There is nothing "wrong" with your query, other than the duplicate BusinessName alias.
Long answer: You can add indexes to the foreign / primary keys to speed up searching which will do more than changing the query.
If you're using SSMS (SQL management studio) you can right click on indexes for a table and use the wizard.
Just don't be tempted to index all the columns as that may slow down any inserts you do in future, stick to the ids and _ids unless you know what you're doing.
he uses it daily to count the messages sent to businesses
If this is done per day, why not limit this to messages sent in specific recent days?
As an example: To count messages sent per business per day, for just a few recent days (example: 3 or 4 days), try this:
SELECT businesses.name AS BusinessName
, messages.created AS DateSent
, COUNT(*) AS n
FROM messages
JOIN users ON messages.from_to = users.id
JOIN businesses ON users.business_id = businesses.id
WHERE messages.created BETWEEN current_date - INTERVAL '3' DAY AND current_date
GROUP BY businesses.id
, DateSent
ORDER BY DateSent DESC
, n DESC
, businesses.id
;
Note: businesses.name is functionally dependent on businesses.id (in the GROUP BY terms), which is the primary key of businesses.
Example result:
+--------------+------------+---+
| BusinessName | DateSent | n |
+--------------+------------+---+
| business1 | 2021-09-05 | 3 |
| business2 | 2021-09-05 | 1 |
| business2 | 2021-09-04 | 1 |
| business2 | 2021-09-03 | 1 |
| business3 | 2021-09-02 | 5 |
| business1 | 2021-09-02 | 1 |
| business2 | 2021-09-02 | 1 |
+--------------+------------+---+
7 rows in set
This assumes your basic join logic is correct, which might not be true.
Other data could be returned as aggregated results, if necessary, and the fact that this is now limited to just recent data, the amount of rows examined should be much more reasonable.

MYSQL - MAX() returns wrong date

I have a mysql table like this
+----------+----------+-------------------+
| entrykey | user_key | validfrom |
+----------+----------+-------------------+
| 1 | 3 | 2016-4-1 0:0:0.0 |
| 2 | 3 | 2016-12-1 0:0:0.0 |
| 3 | 2 | 2016-12-1 0:0:0.0 |
| 4 | 2 | 2016-3-1 0:0:0.0 |
+----------+----------+-------------------+
now I am trying to get only the row for each user where the validfrom is the newest. So I am doing a query like this:
SELECT entrykey, user_key, max(validfrom)
FROM table
Group by user_key;
It is working fine for almost all of my data, just this two examples I posted here in the table select the wrong row which is older. So for user_key 3 it selects entrykey 1 and for the user_key 2 it selects entrykey 4.
What am I doing wrong?
I guess the validform should be 2016-03-01 instead of 2016-3-01 because it is not converted into date before compare.
I am totally agree with the point made in the accepted answer (+1 for that). But, even if op somehow convert validfrom from string to DateTime, his attempt won't give him the desired result.
Let's examine the query given in question:
SELECT entrykey, user_key, MAX(STR_TO_DATE(validfrom, '%Y-%c-%e')) as date1
FROM table
Group by user_key;
Now, this query will return user_key with maximum value of validfrom for that particular user_key. But the entrykey won't be the entrykey with max validfrom. (Check the first result in demo link)
In order to achieve above task, following query will work just fine!
SELECT t1.entrykey, t1.user_key, t2.maxdate as MaxDate
FROM t t1
inner join (select user_key,MAX(STR_TO_DATE(validfrom, '%Y-%c-%e')) as maxdate
from t
Group by user_key
) t2
on t1.user_key = t2.user_key and t1.validfrom = t2.maxdate;
Click here for Demo with DateTime as datatype of validfrom
Click here for Demo with STR_TO_DATE() function
Hope it helps!

mysql using limit in a left join not working properly

I have two tables looking like this
Patient (table 1)
id | name
------------
1 | robel
2 | dave
Patient_followup (table 2)
id | Patient_id | date_created
-----------------------
1 | ---- 1 -- | 01/01/2015
2 | -----1 -- | 01/07/2016
I want to display all the patients with their perspective latest followup data. so i tried using this query
Select * from patient
left join Patient_followup pf on pf.Patient_id = patient.id
order by pf.date_created
Limit 1
but this is giving me only the first patient robel. i tryed removing the limit and its giving me two records of robel and one record of dave because robel has two followup data. so what should i do to get only one record of each patient ?
Try this:
Select
*
from
patient
left join
(SELECT
id as pf_id,
MAX(date_created) as latest_followup_date,
Patient_id
FROM
Patient_followup
GROUP BY
Patient_id) as pf
ON pf.Patient_id = patient.id
As mentioned by anton in the first comment, you need to use aggregation to get one record per patient.
Select patient.*,MAX(pf.date_created) as followupdate,group_concat(pf.date_created) from patient
left join Patient_followup pf on pf.Patient_id = p.patient.id
group by patient.id
order by pf.date_created
Here, you will get your values comma separated.
1) "Limit 1" will only return the first result. Typically this is used if the query will result in a very large result set and you only want the first few results.
Ex:
"LIMIT 30" will show the first 30 rows of the query.
2) I would change to setup of the tables so the query is smoother. Right now, you create a new line for each follow-up date even if the patient is already created. You could add another column in the table named "FollowUpDate". That way each patient record has the table id, patient id, creation date and followup date in the same row. That way, each patient has only one row.
EX:
Patient (table 1)
id | name | created_date | next_followup_date |
1 | Robel | 01/01/2015 | 01/01/2016 |
2 | Dave |[created_date]| [next_follup_date] |
Patient_followup (table 2)
id | Patient_id | date_created | followUpDate |
1 | 1 | 01/01/2015 | 06/01/2016 | // example date
2 | 1 | 01/01/2015 | 01/01/2016 |
3 | 2 |[date created]| [FollowUpDate] |
3) Change query to:
Use this select statement to get all patient records.
Select * from patient
left join Patient_followup pf on pf.Patient_id = patient.id
order by pf.Patient_id
Use this select statement to get the specific patient record information.
Select * from patient
inner join Patient_followup pf on pf.Patient_id = patient.id
where patient.id = 1 //to get robel. Edit this line as necessary, perhaps by user input...
order by pf.followUpDate
NOTE: When you insert a new record in Patient_followup, make sure you update Patient.next_followup_date.
I hope this helps!

ORDER BY does not work if COUNT is used

I have a table with following content
loan_application
+----+---------+
| id | user_id |
+----+---------+
| 1 | 10 |
| 2 | 10 |
| 3 | 10 |
+----+---------+
I want to fetch 3rd record only if there are 3 records available, in this case i want id 3 and total count must be 3, here is what i expect
+--------------+----+
| COUNT(la.id) | id |
+--------------+----+
| 3 | 3 |
+--------------+----+
Here is the query i tried.
SELECT COUNT(la.id), la.id FROM loan_application la HAVING COUNT(la.id) = 3 ORDER BY la.id DESC;
However this gives me following result
+--------------+----+
| COUNT(la.id) | id |
+--------------+----+
| 3 | 1 |
+--------------+----+
The problem is that it returns id 1 even if i use order by id descending, whereas i am expecting the id to have value of 3, where am i going wrong ?
Thanks.
In your case u can use this query:
SELECT COUNT(la.id), max(la.id) FROM loan_application la
GROUP BY user_id
I try your table in my db MySQL
When you have a group by function (in this instance count()) in the select list without a group by clause, then mysql will return a single record only with the function applied to the whole table.
Mysql under certain configuration settings allow you to include fields in the select loist which are not in the group by clause, nor are aggregated. Mysql pretty much picks up the 1st value it encounters while scanning the data as a value for such fields, in your case the value 1 for id.
If you want to fetch the record where id=count of records within the table, then I would use the following query:
select *
from loan_application
join (select count(*) as numrows from loan_application) t
where id=t.numrows and t.numrows=3
However, this implies that the values within the id field are continuous and there are no gaps.
You are selecting la.id along with an aggregated function (COUNT). So after iterating the first record the la.id is selected but the count goes on. So in this case you will get the first la.id not the last. In order to get the last la.id you need to use the max function on that field.
Here's the updated query:
SELECT
COUNT(la.id),
MAX(la.id)
FROM
loan_application la
GROUP BY user_id
HAVING
COUNT(la.id) = 3
N:B: You are using COUNT without a GROUP BY Function. So this particular aggregated function is applied to the whole table.

Get the MAX date between a range

I was searching for querys but i cant find an answer that helps me or if exit a similar question.
i need to get the info of the customers that made their last purchase between two dates
+--------+------------+------------+
| client | amt | date |
+--------+------------+------------+
| 1 | 2440.9100 | 2014-02-05 |
| 1 | 21640.4600 | 2014-03-11 |
| 2 | 6782.5000 | 2014-03-12 |
| 2 | 1324.6600 | 2014-05-28 |
+--------+------------+------------+
for example if i want to know all the cust who make the last purchase between
2014-02-11 and 2014-03-16, in that case the result must be
+--------+------------+------------+
| client | amt | date |
+--------+------------+------------+
| 1 | 21640.4600 | 2014-03-11 |
+--------+------------+------------+
cant be the client number 2 cause have a purchease on 2014-05-28,
i try to make a
SELECT MAX(date)
FROM table
GROUP BY client
but that only get the max of all dates,
i dont know if exist a function or something that can help, thanks.
well i dont know how to mark this question as resolved but this work for me
to complete the original query
SELECT client, MAX(date)
FROM table
GROUP BY client
HAVING MAX(date) BETWEEN date1 AND date2
thanks to all that took a minute to help me with my problem,
special thanks to Ollie Jones and Peter Pei Guo
Something in this format, replace date1 and date 2 with the real values.
SELECT client, max(date)
from table
group by client
having max(date) between date1 AND date2
There is more than one way to do this. Here is one of them.
select * from
(
select client, max(date) maxdate
from table
group by client ) temp
where maxdate between '2014-02-11' and '2014-03-06'
This will allow you to grab the amount column of the applicable rows as well:
select t.*
from tbl t
join (select client, max(date) as last_date
from tbl
group by client
having max(date) between date1 and date2) v
on t.client = v.client
and t.date = v.last_date
I had to change the field "Date" to "TheDate" since date is a reserved word. I assume you are using SQL? My table name is Table1. You need to group records:
SELECT Table1.Client, Sum(Table1.Amt) AS SumOfAmt, Table1.TheDate
FROM Table1
GROUP BY Table1.Client, Table1.TheDate
HAVING (((Table1.TheDate) Between #2/11/2014# And #3/16/2014#));
Query Results:
Client SumOfAmt TheDate
1 21640 03/11/14
2 6792 03/12/14
You may want to get yourself a copy of MS Access. You can generate SQL statements using their query builder which I used to generate this SQL. When I make a post here I will always test it first to make sure it works! I have never written even 1 line of SQL code, but have executed thousands of them from within MS Access.
Good luck,
Dan