This question already has answers here:
Retrieving the last record in each group - MySQL
(33 answers)
Closed 2 years ago.
My sample table:
ID | Post_id | Score
1 | 1 | 33
2 | 1 | 43
3 | 1 | 27
4 | 1 | 66
I want to get rows with the lowest value (Score). In this case it is:
ID | Post_id | Score
3 | 1 | 27
My query:
SELECT * FROM table WHERE post_id = '1' GROUP BY post_id ORDER BY Score ASC
But that doesn't work because it returns me: Score: 33
How to fix it? What if I have thousands of rows and want post_id to be unique for the lowest values?
You must use subquery selecting min values for each post_id.
SELECT a.* FROM records a
JOIN
( SELECT post_id, MIN(score) as min_score
FROM records GROUP BY post_id
) b
ON a.score=b.min_score;
Output
| id | post_id | score |
| --- | ------- | ----- |
| 3 | 1 | 27 |
| 5 | 2 | 20 |
View on DB Fiddle
For a single id, just remove the group by and use limit:
SELECT *
FROM table
WHERE post_id = 1
ORDER BY Score ASC
LIMIT 1;
I assume that post_id is a number. Compare numbers to numbers, not to strings.
EDIT:
If you want this per post_id, then just use a correlated subquery:
select t.*
from t
where t.score = (select min(t2.score) from t t2 where t2.post_id = t.post_id);
If you may have multiple rows with the lowest score, you can do it with a sub-query :
SELECT *
FROM test
WHERE post_id = 1
AND score = (
SELECT MIN(score)
FROM test
WHERE post_id = 1
)
Fiddle : https://www.db-fiddle.com/f/3ppntnA77HFpKRU82h32Gv/1
IF you're using MySQL v8.0, you can use the ROW_NUMBER() function to order the result. That way you can choose the row with the lower score and return everything from it:
select
sq.id, sq.post_id, sq.score
from
(select id, post_id, score
, row_number() over (partition by post_id order by score) RowNo
from test) sq
where sq.RowNo = 1
Here is a Fiddle to test the code: https://www.db-fiddle.com/#&togetherjs=8dHSCs50Iq
I also included another post_id beside your sample data, to demonstrate how it reacts to multiple post_id's
The below should do the trick:
Select
id,
score,
Post_id,
min(score)
from
table
where
score = min(score);
Related
I'm trying to select the row that contains the largest number and have accomplished it using this fairly simple query:
SELECT s1.score, name
FROM scores s1
JOIN (
SELECT id, MAX(score) score
FROM scores
GROUP BY id
) s2 ON s1.score = s2.score
All it does (If im not wrong), is just checking if the score field is equal the the MAX(score). So why can't we just do it using one single SELECT statement ?. Something like this:
SELECT id, score
FROM scores
GROUP BY id
HAVING MAX(score) = score
*The code above does not work, I want to ask why it is not working, because its essentially doing the same thing as the previous code I posted
Also here's the data I'm working with:
The problem in your second query is the fact that the GROUP BY clause requires all non-aggregated fields within its context. In your case you are dealing with three fields (namely "id", "score" and "MAX(score)") and you're referencing only one (namely "id") inside the GROUP BY clause.
Fixing that would require you to add the non-aggregated "score" field inside your GROUP BY clause, as follows:
SELECT id, score
FROM scores
GROUP BY id, score
HAVING MAX(score) = score
Though this would lead to a wrong aggregation and output, because it would attempt to get the maximum score for each combination of (id, score).
And if you'd attempt to remove the "score" field from both the SELECT and GROUP BY clauses, to solve the non-aggregated columns issue, as follows:
SELECT id
FROM scores
GROUP BY id
HAVING MAX(score) = score
Then the HAVING clause would complain as long it references the "score" field but it is found neither within the SELECT clause nor within the GROUP BY clause.
There's really no way for you to use that kind of notation, as it either violates the full GROUP_BY mode, or it just returns the wrong output.
It returns all persons with same score which the score is the max:
WITH CTE AS (
SELECT *, ROW_NUMBER() OVER(ORDER BY score desc) RN
FROM scores
)
SELECT * FROM CTE
WHERE CTE.RN = 1
Here's what your queries return
DROP table if exists t;
create table t
(id int,score int);
insert into t values
(1,10),(2,20),(3,20);
SELECT s1.id,s1.score
FROM t s1
JOIN (
SELECT id, MAX(score) score
FROM t
GROUP BY id
) s2 ON s1.score = s2.score ;
+------+-------+
| id | score |
+------+-------+
| 1 | 10 |
| 2 | 20 |
| 2 | 20 |
| 3 | 20 |
| 3 | 20 |
+------+-------+
5 rows in set (0.001 sec)
SELECT id, score,max(score)
FROM t
GROUP BY id
HAVING MAX(score) = score
+------+-------+------------+
| id | score | max(score) |
+------+-------+------------+
| 1 | 10 | 10 |
| 2 | 20 | 20 |
| 3 | 20 | 20 |
+------+-------+------------+
3 rows in set (0.001 sec)
Neither result seems to be what you are looking for. You could clarify by posting sample data and desired outcome.
Lets say we have a table that looks like this:
+---------------+----------------+-------------------+
| ID | random_string | time |
+---------------+----------------+-------------------+
| 2 | K2K3KD9AJ |2022-07-21 20:41:15|
| 1 | SJQJ8JD0W |2022-07-17 23:46:13|
| 1 | JSDOAJD8 |2022-07-11 02:52:21|
| 3 | KPWJOFPSS |2022-07-11 02:51:57|
| 1 | DA8HWD8HHD |2022-07-11 02:51:49|
------------------------------------------------------
I want to select the last 3 entries into the table, however they must all have separate ID's.
Expected Result:
+---------------+----------------+-------------------+
| ID | random_string | time |
+---------------+----------------+-------------------+
| 2 | K2K3KD9AJ |2022-07-21 20:41:15|
| 1 | SJQJ8JD0W |2022-07-17 23:46:13|
| 3 | KPWJOFPSS |2022-07-11 02:51:57|
------------------------------------------------------
I have already tried:
SELECT DISTINCT id FROM table ORDER BY time DESC LIMIT 3;
And:
SELECT MIN(id) as id FROM table GROUP BY time DESC LIMIT 3;
If you're not on MySQL 8, then I have two suggestions.
Using EXISTS:
SELECT m1.ID,
m1.random_string,
m1.time
FROM mytable m1
WHERE EXISTS
(SELECT ID
FROM mytable AS m2
GROUP BY ID
HAVING m1.ID=m2.ID
AND m1.time= MAX(time)
)
Using JOIN:
SELECT m1.ID,
m1.random_string,
m1.time
FROM mytable m1
JOIN
(SELECT ID, MAX(time) AS mxtime
FROM mytable
GROUP BY ID) AS m2
ON m1.ID=m2.ID
AND m1.time=m2.mxtime
I've not test in large data so don't know which will perform better (speed) however this should return the same result:
Here's a fiddle
Of course, this is considering that there will be no duplicate of exact same ID and time value; which seems to be very unlikely but still it's possible.
Using MySql 8 an easy solution is to assign a row number using a window:
select Id, random_string, time
from (
select *, Row_Number() over(partition by id order by time desc) rn
from t
)t
where rn = 1
order by time desc
limit 3;
See Demo
I am doing the next query:
SELECT id, name, keyt
FROM table
WHERE id = (SELECT t2.id FROM table t2 WHERE t2.keyt=21 ORDER BY RAND() LIMIT 1)
Supposing table is like this:
| id | name | keyt |
+ ------------------------- +
| 1 | Hello | 21 |
| 3 | Katzet | 1 |
| 1 | Welcome | 1 |
| 2 | Two | 21 |
| 2 | Other | 1 |
It should return one of this pairs:
Hello | Welcome (id 1 in common)
Two | Other (id 2 in common)
So, the idea is:
Get one id, which has the keyt value set to 21
Then, get all the rows with this selected id (independently of all the other keyt values)
If I do as you suggested... I would get mixed id values, and all result rows must have the same id.
SELECT x.*
FROM my_table x
JOIN
( SELECT id
FROM my_table
WHERE keyt = 21
ORDER
BY RAND() LIMIT 1
) y
ON y.id = x.id;
The subquery in this query
SELECT id, name, keyt
FROM table
WHERE id = (SELECT t2.id FROM table t2 WHERE t2.keyt=21 ORDER BY RAND() LIMIT 1)
would return only one record as it has LIMIT 1 added at the end.
Also, in your question, the table contains only 1 record for which
value of keyt = 21, due to which you're getting only one record.
If you want more records, you should remove the LIMIT. In that case you may rephrase your query as:
SELECT id, name, keyt
FROM table
WHERE id IN (SELECT t2.id FROM table t2 WHERE t2.keyt=21 ORDER BY RAND())
Hope this is what you expected. As your actual goal is not very clear from the question.
Your table has two 21 in the keyt column so your subquery in the where clause returns 2 values if id that is 1 and 2.So what you need to do is instead of using an equal to operator "=" use IN operator in the where clause.
SELECT id, name, keyt FROM table WHERE id IN (SELECT t2.id FROM table t2 WHERE t2.keyt=21 ORDER BY RAND())
I have read that grouping happens before ordering, is there any way that I can order first before grouping without having to wrap my whole query around another query just to do this?
Let's say I have this data:
id | user_id | date_recorded
1 | 1 | 2011-11-07
2 | 1 | 2011-11-05
3 | 1 | 2011-11-06
4 | 2 | 2011-11-03
5 | 2 | 2011-11-06
Normally, I'd have to do this query in order to get what I want:
SELECT
*
FROM (
SELECT * FROM table ORDER BY date_recorded DESC
) t1
GROUP BY t1.user_id
But I'm wondering if there's a better solution.
Your question is somewhat unclear but I have a suspicion what you really want is not any GROUP aggregates at all, but rather ordering by date first, then user ID:
SELECT
id,
user_id,
date_recorded
FROM tbl
ORDER BY date_recorded DESC, user_id ASC
Here would be the result. Note reordering by date_recorded from your original example
id | user_id | date_recorded
1 | 1 | 2011-11-07
3 | 1 | 2011-11-06
2 | 1 | 2011-11-05
5 | 2 | 2011-11-06
4 | 2 | 2011-11-03
Update
To retrieve the full latest record per user_id, a JOIN is needed. The subquery (mx) locates the latest date_recorded per user_id, and that result is joined to the full table to retrieve the remaining columns.
SELECT
mx.user_id,
mx.maxdate,
t.id
FROM (
SELECT
user_id,
MAX(date_recorded) AS maxdate
FROM tbl
GROUP BY user_id
) mx JOIN tbl t ON mx.user_id = t.user_id AND mx.date_recorded = t.date_recorded
Iam just using the technique
"Using order clause before group by inserting it in group_concat clause"
SELECT SUBSTRING_INDEX(group_concat(cast(id as char)
ORDER BY date_recorded desc),',',1),
user_id,
SUBSTRING_INDEX(group_concat(cast(`date_recorded` as char)
ORDER BY `date_recorded` desc),',',1)
FROM data
GROUP BY user_id
i have table structure like this
sn | person_id | image_name |
1 | 1 | abc1.jpb
2 | 1 | aa11.jpg
3 | 11 | dsv.jpg
4 | 11 | dssd.jpg
5 | 11 | sdf.jpg
I need distinct person_id newest row as following
2 | 1 | aa11.jjpb
5 | 11 | sdf.jpg
IT is possible ?
SELECT * FROM yourtable GROUP BY person_id ORDER BY sn DESC
Essentially you want to select all records from your table. Then it is grouped by the person_id (limiting the result to 1 per person id)... Ordering by SN decending means that it will return the most recent (highest) sn
Update: (and verified)
SELECT * FROM (SELECT * FROM stackoverflow ORDER BY sn DESC) a GROUP BY person_id ORDER BY sn
SELECT * FROM table GROUP BY person_id HAVING MAX(sn)
EDIT
SELECT f.*
FROM (
SELECT person_id, MAX(sn) as maxval
FROM table GROUP BY person_id
) AS x INNER JOIN table AS f
ON f.person_id = x.person_id AND f.sn = x.maxval;
where table is your table name.
SELECT * FROM table a WHERE a.`id` = ( SELECT MAX(`id`) FROM table b WHERE b.`person_id` = a.`person_id` );
What you are doing inside the parenthesis is selecting the max id for the rows that have that distinct person_id. So for each unique person_id you are getting the most recent entry.