group by order by max() - mysql

I'm using MySQL.
The result I want, is to display the row with the highest 'time' where 'res' = 'hans', and group the 'frm'.
I am trying to fiddle around with GROUP BY, ORDER BY, MAX(time) - and I'm going no where.
My table: 'messages'
| frm | res | time | msg | opnd |
| poul | hans | 0916 | hi there | 1 |
| john | hans | 1033 | waz up | 1 |
| hans | john | 1140 | new text | 0 |
| poul | john | 1219 | message | 0 |
| poul | hans | 1405 | respond | 0 |
| john | hans | 1544 | write | 0 |
The result I want:
poul - hans - 1405 - respond - 0
john - hans - 1544 - write - 0
The result I get:
poul - hans - 1405 - hi there - 1
john - hans - 1544 - waz up - 1
I'm getting the correct 'time' but the wrong 'msg' and 'opnd'.
My code:
SELECT frm, res, MAX(time), msg, opnd
FROM messages
WHERE res = 'hans'
GROUP BY frm
ORDER BY time DESC

There are a couple ways to do this. One is to use a subquery and join back to the original table:
SELECT m.*
FROM messages m
JOIN (
SELECT frm, res, MAX(time) maxtime
FROM messages
WHERE res = 'hans'
GROUP BY frm, res) m2 on m.frm = m2.frm
and m.res = m2.res
and m.time = m2.maxtime
ORDER BY m.time DESC
Mysql allows you to omit fields from the group by clause that are not used in aggregation (a mistake imo -- most other databases do not allow this behavior). By allowing it, it just returns a random result though which is what you're experiencing.
Here'a another approach using an outer join, but I think the previous is easier to understand:
select m.*
from messages m
left join messages m2 on m.frm = m2.frm
and m.res = m2.res
and m2.time > m.time
where m2.frm is null
and m.res = 'hans'
order by m.time desc
Fiddle Demo With Both

your problem is that you are grouping by one column, but you select several columns with it. As a result, for the other non-grouped by columns you will get just one of the results, not nececarily the one which belongs to the max(time) value.
you need something like:
select a.frm, a.res, b.max_time, a.msg, a.opnd from
messages as a inner join
(SELECT frm, MAX(time) as max_time
FROM messages
WHERE res = 'hans'
GROUP BY frm) on a.frm = b.frm and a.time = b.max_time
ORDER BY time DESC

Related

Laravel Selection Query with left join

Table - tbl_user_details
UserId | Username
------------------
1 | jijo
2 | libin
Table - tbl_user_followups
FollowupId | UserId | Status
---------------------------------
1 | 1 | Negative
2 | 1 | Neutral
3 | 1 | Positive
My Controller is
$result= DB::table('tbl_user_details')
->leftjoin('tbl_user_followups','tbl_user_details.UserId','=','tbl_user_followups.UserId')
->select('tbl_user_details.*','tbl_user_followups.*')
->orderBy('tbl_user_followups.FollowupId','DESC')
->groupBy('tbl_user_details.UserId')
->get();
I want to get the output like as below
UserId | Username | FollowupId | Status
----------------------------------------
1 | jijo | 3 | Positive
2 | libin
Anyone can you please suggest an edit in my controller ???
Not sure if I understood question right: You want list of users with last "followup"? If it is so, then:
SELECT d.userid,
d.username,
det.followupid,
det.status
FROM tbl_user_details d
LEFT JOIN (SELECT userid,
followupid,
status
FROM tbl_user_followups f
WHERE followupid IN (SELECT Max(followupid)
FROM tbl_user_followups
GROUP BY userid)) det
ON d.userid = det.userid
ORDER BY det.followupid DESC;
order by det.followupid desc;
Please note that "order by det.followupid" will not sort them correctly, as we expect for some users to have "null" followupid.

Unknown column when subquery refer to outer table used at FROM clause

I have this table:
person table
| id | name |
| 1 | person1 |
| 2 | person2 |
person_grade table
| id | person_id | grade | grade_date |
| 1 | 1 | grade1 | 2010-01-01 |
| 2 | 1 | grade2 | 2012-01-01 |
| 3 | 2 | grade3 | 2010-05-05 |
| 4 | 2 | grade4 | 2012-03-03 |
I want to know person grade at a specific time, say 2012-02-02. How to achieve this?
The closest I got was, with this query:
SELECT t1.id, t1.name,
(SELECT grade FROM (
(SELECT s1.grade, s1.grade_date FROM person_grade AS s1
WHERE s1.grade_date >= '2012-02-01' AND s1.person_id = t1.id
ORDER BY s1.grade_date LIMIT 1)
UNION ALL
(SELECT s1.grade, s1.grade_date FROM person_grade AS s1
WHERE s1.grade_date <= '2012-02-01' AND s1.person_id = t1.id
ORDER BY s1.grade_date DESC LIMIT 1)
) AS ss ORDER BY grade_date LIMIT 1) AS grade_person
FROM person AS t1
But at MySQL that give me an error
"Unknown column 't1.id' in 'where clause'".
Please advise.
TIA
SELECT name,grade FROM person p
INNER JOIN person_grade pg
ON p.id=pg.person_id
WHERE pg.grade_date='2012-02-02'
I dont know about the syntax about the MySql but u can do something like this
SELECT GRADE FROM person_grade WHERE DATE(GRADE_DATE,YEAR) = DATE(SEARCH_DATE,YEAR) AND DATE(GRADE_DATE,MONTH) = DATE(SEARCH_DATE,MONTH) AND DATE(GRADE_DATE,DAY) = DATE(SEARCH_DATE,DAY)
SELECT p.name
, pg.grade
FROM person p
INNER JOIN person_grade pg ON p.id = pg.person_id
WHERE DATE(pg.grade_date) = '2012-02-02'
If this works where #mhasan's answer did not, then it's most likely because of the data type of the grade_date table. If it's DATETIME, then it stores the time element of the date and that makes searching on that field bothersome.
If you don't need the time element, try changing the data type of the column to DATE. It should use less space and make searching easier.
EDIT: Wait, I just read that question again... you don't want records where the date MATCHES the query, you want something slightly trickier. Let me think on this one...
Thanks for the replay everyone, I think I found the solution, move subquery to WHERE clause and use MIN function at date diff. Here is the query :
SELECT p.id, p.name, pg.grade, pg.grade_date
FROM person AS p
LEFT JOIN person_grade AS pg ON p.id = pg.person_id
WHERE DATEDIFF ( '2012-02-02', pg.grade_date ) =
( SELECT MIN ( DATEDIFF ( '2012-02-02', spg.grade_date ) )
FROM person AS sp
LEFT JOIN person_grade AS spg ON sp.id = spg.person_id
WHERE DATEDIFF ( '2012-02-02', spg.grade_date ) > 0 AND sp.id = p.id )
Result:
| id | name | grade | grade_date |
| 1 | person1 | grade2 | 2012-01-01 |
| 2 | person2 | grade3 | 2010-05-05 |
Seems reference to outer table doesn't work under FROM clause, but work elsewhere (at least at MySQL).
Thanks for the hint from other question: Retrieve maximal / minimal record.

How to get the value of a row with Max aggregation function?

I have a table for comments :
+----------+---------------------+----------+
| match_id | timestampe | comment |
+----------+---------------------+----------+
| 100 | 2014-01-01 01:00:00 | Hi |
| 200 | 2014-01-01 01:10:00 | Hi1 |
| 300 | 2014-01-01 01:20:00 | Hi2 |
| 100 | 2014-01-01 01:01:00 | Hello |
| 100 | 2014-01-01 01:02:00 | Hello1 |
| 200 | 2014-01-01 01:11:00 | hey |
+----------+---------------------+----------+
I want to get the following information from the table
SELECT match_id, max(timestampe) as maxtimestamp, count(match_id) as comments_no
FROM comments
GROUP BY match_id
order by maxtimestamp DESC
The previous explanation is working great but the problem is when I want to get the comment of the maxtimestamp.
How can I get the latest comment of each match (the comment of the maxtimestamp) using the most optimized query?
You can do it this way.
This is pretty optimal too.
SELECT c.comment, m.*
FROM
comments c
JOIN
(
SELECT t.match_id, max(t.timestampe) as maxtimestamp, count(t.match_id) as comments_no
FROM comments t
GROUP BY t.match_id
) m on c.match_id = m.match_id and c.timestampe = m.maxtimestamp
SQL Fiddle
I'm not sure about MySQL but Oracle supports window functions, so I can write something like:
select first_value(comment) over (order by timestamp desc)
from comments
Here's the easy way to do it with mysql:
SELECT * from (
SELECT match_id, timestampe as maxtimestamp, comment
FROM comments
order by maxtimestamp DESC) x
GROUP BY match_id
This exploits the customised way mysql handles group by.
to not use a subquery, you can use this below query
SELECT match_id,timestampe,comment,
IF(#prevMatchId IS NULL OR #prevMatchId != match_id,#row:=1,#row:=#row+1) as row,
#prevMatchId := match_id
FROM comments
HAVING row = 1
ORDER BY match_id,timestampe DESC
try using EXPLAIN and see which queries are more optimal
here's EXPLAIN on two queries. http://sqlfiddle.com/#!2/70efa/9/1 I am not all that familiar with EXPLAIN so maybe some experts can interpret it.
here's an EXPLAIN on two queries. if i added indexes on match_id and timestampe http://sqlfiddle.com/#!2/30266/1/1

Mysql multiple join with optional value in ON clause

Ok, First see this http://www.sqlfiddle.com/#!2/bbc3a/2
Here the query return the count and all, but what i want is that it selects a forum and return the count of topics and post in it then next forum and all topics and post counts from it and so on with forums name, url and desc. I thought doing this in one query instead of many queries in loop would be better. I don't know what i am doing with that mysql query though, i am not very good with mysql and i haven't sleep for so long. Thanks for help.
EDIT:
This is what i expect to see from the data provided in te fiddle above^
| FORUMNAME | FORUMURL | FORUMDESC | FORUMTIME | TOPICCOUNT | POSTCOUNT |
| Forum 1 | Forum-1 | Forum 1 Desc | 343243243 | 1 | 1 |
| Forum 2 | Forum-2 | Forum 2 Desc | 343243243 | 0 | 0 |
You just need to add a GROUP BY clause
SELECT f.Name ForumName,
f.Url ForumUrl,
f.Desc ForumDesc,
f.Time ForumTime,
COUNT(DISTINCT t.ID) TopicCount,
COUNT(DISTINCT p.ID) PostCount
FROM name_forums f
LEFT JOIN name_topics t ON t.ForumId = f.ID
LEFT JOIN name_posts p ON p.TopicId = t.ID
WHERE f.CategoryId = 1
GROUP BY f.name, f.url, f.desc
Here is SQLFiddle demo

Groupwise maximum

I have a table from which I am trying to retrieve the latest position for each security:
The Table:
My query to create the table: SELECT id, security, buy_date FROM positions WHERE client_id = 4
+-------+----------+------------+
| id | security | buy_date |
+-------+----------+------------+
| 26 | PCS | 2012-02-08 |
| 27 | PCS | 2013-01-19 |
| 28 | RDN | 2012-04-17 |
| 29 | RDN | 2012-05-19 |
| 30 | RDN | 2012-08-18 |
| 31 | RDN | 2012-09-19 |
| 32 | HK | 2012-09-25 |
| 33 | HK | 2012-11-13 |
| 34 | HK | 2013-01-19 |
| 35 | SGI | 2013-01-17 |
| 36 | SGI | 2013-02-16 |
| 18084 | KERX | 2013-02-20 |
| 18249 | KERX | 0000-00-00 |
+-------+----------+------------+
I have been messing with versions of queries based on this page, but I cannot seem to get the result I'm looking for.
Here is what I've been trying:
SELECT t1.id, t1.security, t1.buy_date
FROM positions t1
WHERE buy_date = (SELECT MAX(t2.buy_date)
FROM positions t2
WHERE t1.security = t2.security)
But this just returns me:
+-------+----------+------------+
| id | security | buy_date |
+-------+----------+------------+
| 27 | PCS | 2013-01-19 |
+-------+----------+------------+
I'm trying to get the maximum/latest buy date for each security, so the results would have one row for each security with the most recent buy date. Any help is greatly appreciated.
EDIT: The position's id must be returned with the max buy date.
You can use this query. You can achieve results in 75% less time. I checked with more data set. Sub-Queries takes more time.
SELECT p1.id,
p1.security,
p1.buy_date
FROM positions p1
left join
positions p2
on p1.security = p2.security
and p1.buy_date < p2.buy_date
where
p2.id is null;
SQL-Fiddle link
You can use a subquery to get the result:
SELECT p1.id,
p1.security,
p1.buy_date
FROM positions p1
inner join
(
SELECT MAX(buy_date) MaxDate, security
FROM positions
group by security
) p2
on p1.buy_date = p2.MaxDate
and p1.security = p2.security
See SQL Fiddle with Demo
Or you can use the following in with a WHERE clause:
SELECT t1.id, t1.security, t1.buy_date
FROM positions t1
WHERE buy_date = (SELECT MAX(t2.buy_date)
FROM positions t2
WHERE t1.security = t2.security
group by t2.security)
See SQL Fiddle with Demo
This is done with a simple group by. You want to group by the securities and get the max of buy_date. The SQL:
SELECT security, max(buy_date)
from positions
group by security
Note, this is faster than bluefeet's answer but does not display the ID.
The answer by #bluefeet has two more ways to get the results you want - and the first will probably be more efficient than your query.
What I don't understand is why you say that your query doesn't work. It seems pretty fine and returns the expected result. Tested at SQL-Fiddle
SELECT t1.id, t1.security, t1.buy_date
FROM positions t1
WHERE buy_date = ( SELECT MAX(t2.buy_date)
FROM positions t2
WHERE t1.security = t2.security ) ;
If the problems appears when you add the client_id = 4 condition, then it's because you add it only in one WHERE clause while you have to add it in both:
SELECT t1.id, t1.security, t1.buy_date
FROM positions t1
WHERE client_id = 4
AND buy_date = ( SELECT MAX(t2.buy_date)
FROM positions t2
WHERE client_id = 4
AND t1.security = t2.security ) ;
select security, max(buy_date) group by security from positions;
is all you need to get max buy date for each security (when you say out loud what you want from a query and you include the phrase "for each x", you probably want a group by on x)
When you use a group by, all columns in your select must either be columns that have been grouped by or aggregates, so if, for example, you wanted to include id, you'd probably have to use a subquery similar to what you had before, since there doesn't seem to be any aggregate you can reasonably use on the ids, and another group by would give you too many rows.