Get the row with latest date after inner join - mysql

I have this query:
SELECT
achievements.id,
achievements.name,
achievements.category,
userAchievements.createdAt
FROM
achievements
INNER JOIN
userAchievements ON userAchievements.achievementId = achievements.id
AND userAchievements.userId = 12
WHERE
achievements.type = 2
Result of this query is:
id
name
category
createdAt
8
First
1
2021-02-11
13
Second
2
2021-02-12
14
Third
4
2021-03-01
15
Fourth
4
2021-03-02
I have to leave only unique category with max createdAt field.
I need a query that will give the following result:
id
name
category
createdAt
8
First
1
2021-02-11
13
Second
2
2021-02-12
15
Fourth
4
2021-03-02
If I use group by then it returns row with id 14.
MySQL version - 5.7.33.
Achievements table:
id
name
category
8
First
1
13
Second
2
14
Third
4
15
Fourth
4
UserAchievements table:
id
achievementId
userId
createdAt
3
8
12
2021-02-11
7
13
12
2021-02-12
36
15
12
2021-03-02
40
14
12
2021-03-01
P.S.
I managed to write a query that solves the problem
SELECT
*
FROM
(SELECT
*,
IF(#prev <> category, #rn:=0, #rn),
#prev:=category,
#rn:=#rn + 1 AS rn
FROM
(SELECT
achievements.id,
achievements.name,
achievements.category,
userAchievements.createdAt
FROM
achievements
INNER JOIN userAchievements ON userAchievements.achievementId = achievements.id
AND userAchievements.userId = 12
WHERE
achievements.type = 2) as ach, (SELECT #rn:=0) rn, (SELECT #prev:='') prev
ORDER BY ach.createdAt DESC) t
WHERE
rn = 1

You can use window functions:
SELECT a.*
FROM (SELECT a.*,
ROW_NUMBER() OVER (PARTITION BY a.category ORDER BY a.created_at DESC) as seqnum
FROM achievements JOIN
userAchievements ua
ON ua.achievementId = a.id AND
ua.userId = 12
WHERE a.type = 2
) a
WHERE seqnum = 1;

This subquery basically search for all maximum dates, grouped by category.
SELECT MAX(_ua.createdAt) FROM `achievements` _a INNER JOIN user_achievements _ua ON _a.id = _ua.achievementId group by _a.category
So you maybe you can do :
SELECT a.* FROM achievements a
INNER JOIN user_achievements ua ON a.id = ua.achievementId
AND ua.createdAt IN (SELECT MAX(_ua.createdAt) FROM `achievements` _a INNER JOIN
user_achievements _ua ON _a.id = _ua.achievementId group by _a.category)

Related

select rows with condition in other rows

I want select rows from my table with last status_Id if there is a row with status_Id = 2 for that rows
ticketStatus_Id ticket_Id status_Id
======================================
1 1 1
2 1 2 -
3 1 3 *
4 2 1
5 3 1
6 3 2 - *
7 4 1
8 4 2 -
9 4 3
10 4 4 *
I want select just rows 3, 6, 10. there are another rows with status_Id = 2 (rows 2, 6, 8) for that ticket_Id,
In other word How to select rows 3,6,10 with ticket_Id =1,3,4 that there are another row with these ticket_Ids and status_Id=2 (rows 2,6,8)
If you want the complete row, then I would view this as exists:
select t.*
from t
where exists (select 1
from t t2
where t2.ticket_id = t.ticket_id and t2.status_id = 2
) and
t.status_Id = (select max(t2.status_id)
from t t2
where t2.ticket_id = t.ticket_id
);
If you just want the ticket_id and status_id (and not the whole row), I would recommend aggregation:
select ticket_id, max(status_id)
from t
group by ticket_id
having sum(status_id = 2) > 0;
In your case, ticketStatus_Id seems to increase with status_id, so you can use:
select max(ticketStatus_Id) as ticketStatus_Id, ticket_id, max(status_id) as Status_Id
from t
group by ticket_id
having sum(status_id = 2) > 0;
First, for each ticket we get the row with the highest status. We can do this with a self-join. Each row is joined with the row with the next highest status. We select the rows which have no higher status, those will be the highest. Here's a more detailed explanation.
select ts1.*
from ticket_statuses ts1
left outer join ticket_statuses ts2
on ts1.ticket_Id = ts2.ticket_Id
and ts1.status_Id < ts2.status_Id
where ts2.ticketStatus_Id is null
3 1 3
4 2 1
6 3 2
10 4 4
11 5 3
Note that I've added a curve-ball of 11, 5, 3 to ensure we only select tickets with a status of 2, not greater than 2.
Then we can use that as a CTE (or subquery if you're not using MySQL 8) and select only those tickets who have a status of 2.
with max_statuses as (
select ts1.*
from ticket_statuses ts1
left outer join ticket_statuses ts2
on ts1.ticket_Id = ts2.ticket_Id
and ts1.status_Id < ts2.status_Id
where ts2.ticketStatus_Id is null
)
select ms.*
from max_statuses ms
join ticket_statuses ts
on ms.ticket_id = ts.ticket_id
and ts.status_id = 2;
3 1 3
6 3 2
10 4 4
This approach ensures we select the complete rows with the highest statuses and any extra data they may contain.
dbfiddle
This is basicaly a "last row per group" problem. You will find some solutions here. My prefered solution would be:
select t.*
from (
select max(ticketStatus_Id) as ticketStatus_Id
from mytable
group by ticket_Id
) tmax
join mytable t using(ticketStatus_Id)
The difference in your question is that you have a condition requiring a specific value within the group. This can be solved with a JOIN within the subquery:
select t.*
from (
select max(t1.ticketStatus_Id) as ticketStatus_Id
from mytable t2
join mytable t1 using(ticket_Id)
where t2.status_Id = 2
group by t2.ticket_Id
) tmax
join mytable t using(ticketStatus_Id)
Result:
| ticketStatus_Id | ticket_Id | status_Id |
| --------------- | --------- | --------- |
| 3 | 1 | 3 |
| 6 | 3 | 2 |
| 10 | 4 | 4 |
View on DB Fiddle
A solution using window functions could be:
select ticketStatus_Id, ticket_Id, status_Id
from (
select *
, row_number() over (partition by ticket_Id order by ticketStatus_Id desc) as rn
, bit_or(status_Id = 2) over (partition by ticket_Id) > 0 as has_status2
from mytable
) x
where has_status2 and rn = 1
A quite expressive way is to use EXISTS and NOT EXISTS subquery conditions:
select t.*
from mytable t
where exists (
select *
from mytable t1
where t1.ticket_Id = t.ticket_Id
and t1.status_Id = 2
)
and not exists (
select *
from mytable t1
where t1.ticket_Id = t.ticket_Id
and t1.ticketStatus_Id > t.ticketStatus_Id
)
SELECT a.*
FROM t a
JOIN
(
SELECT ticket_id, MAX(status_id) max_status_id
FROM t
WHERE status_id >= 2
GROUP BY ticket_id
) b
ON a.ticket_id = b.ticket_id
AND a.status_id = b.max_status_id;
SELECT
MAX(m1.ticketstatus_Id) as ticket_status,
m1.ticket_Id as ticket,
MAX(m1.status_Id) as status
FROM mytable m1
WHERE
m1.ticket_Id in (select m2.ticket_Id from mytable m2 where m2.ticket_Id=m1.ticket_Id and m2.status_Id=2)
GROUP BY m1.ticket_Id

MySQL includes a specific row with order by

Given 2 tables, I want to generate top 3 highest amount from [Purchase] table.
Additional criteria is [Crocs] must be included in top 3 of the records.
I have following SQL, but it cannot generates the result as I wanted (Result A), please guide me on how to pull out the result in Result B. Thank you.
Table (Purchase):
Purchase_ID | StoreID | Amount
------------|---------|--------
1 | 21 | 22
2 | 23 | 13
3 | 25 | 6
4 | 26 | 23
5 | 28 | 18
Table (Store):
Store_ID | StoreName
---------|----------
21 | Adidas
22 | Nike
23 | Puma
24 | New Balance
25 | Crocs
26 | Converse
SQL:
SELECT IF(SUM(amount) IS NULL, 0, SUM(amount)) as totalAmount
FROM (
SELECT a.amount
FROM purchase a
INNER JOIN store b
ON a.store_id = b.storeid
GROUP BY a.amount
HAVING b.StoreName = 'Crocs'
ORDER BY a.amount DESC
LIMIT 3
) t
Result A: $6
Explanation A: Amount of Crocs is $6
Result B: $51
Explanation B: Total Amount of top 3 = $22 (Adidas) + 23 (Puma) + $6 (Crocs)
The answer from scaisEdge is almost right, but the first query could also return a row with crocs and the sorting is wrong (order by max(a.amount) limit 2 means that the lowest 2 results will be shown). Additionally you could wrap the query in another select query to sort the results
SELECT * FROM (
SELECT b.storename, max(a.amount) as maxAmount
FROM purchase a
INNER JOIN store b ON a.store_id = b.storeid
WHERE b.storename != 'crocks'
GROUP BY a.storename
ORDER BY max(a.amount) DESC
LIMIT 2
UNION
SELECT b.storename, a.amount as maxAmount
FROM purchase a
INNER JOIN store b
ON a.store_id = b.storeid
WHERE b.storename='crocks'
ORDER BY a.amount DESC
LIMIT 1
) ORDER BY maxAmount DESC
You could use an union
SELECT b.storename, max(a.amount)
FROM purchase a
INNER JOIN store b
ON a.store_id = b.storeid
GROUP BY a.storename
order by max(a.amount) limit 2
union
SELECT b.storename, a.amount
FROM purchase a
INNER JOIN store b
ON a.store_id = b.storeid
where b.storename='crocks'
try this one:
SELECT sum(amount)as sum_amount,a.store_id,storename,category from
(select amount,store_id from tbl_purchase) as a
inner JOIN
(select store_id,storename,category from tbl_store)as b on a.store_id = b.store_id where b.category = 'supermarket' GROUP BY category

Mysql group_concat for count in sub query using parent filed is not allowed

I need to process album count for each of the country per artist; however, I have a problem once I do group_concat for count in mysql, I search a bit in stackoverflow, I found I have to do sub select for group_concat. The problem is once I do the sub select in from I can not use a.id from the parent from filed table. I got error like following Unknown column 'a.id' in 'where clause'
This is the query:
SELECT a.seq_id, a.id
(SELECT GROUP_CONCAT(cnt) AS cnt FROM (
SELECT CONCAT_WS('-', mgr.country_code, count(mgr.media_id)) AS cnt
FROM music_album_artists AS ma
JOIN media_geo_restrict AS mgr ON ma.album_id = mgr.media_id
WHERE ma.artist_id = a.id
GROUP BY mgr.country_code
) count_table
) AS album_count
FROM music_artist AS a
WHERE a.seq_id > 0 and a.seq_id < 10000
The sample data in tables:
music_artists:
seq_id id name
1 1 Hola
2 2 Vivi
music_album_artists:
id artist_id album_id
1 1 1
2 1 2
3 1 5
4 1 10
5 2 2
6 2 10
6 2 1
media_geo_restrict:
album_id country_code
1 BE
1 CA
1 DE
1 US
2 CH
2 CA
2 CH
5 DE
10 US
The result I would like to have
seq_id id album_count
1 1 BE--1,CA--2,CH--1,DE--1,US--1
2 2 CA--1,US--2,CH--1
Here is what you need:
select seq_id, id, group_concat(concat(country_code, '--', qtd))
from (
select ma.seq_id, ma.id,
mgr.country_code, count(*) qtd
from music_artists ma
inner join music_album_artists maa
on ma.id = maa.artist_id
inner join media_geo_restrict mgr
on maa.album_id = mgr.album_id
where ma.seq_id > 0 and ma.seq_id < 10000
group by ma.seq_id, ma.id, ma.name,
mgr.country_code
) tb
group by seq_id, id
Here is the working sample: http://sqlfiddle.com/#!9/ff8b5/8
Try this and tell me:
SELECT a.seq_id, a.id, GROUP_CONCAT(cnt) AS cnt
FROM music_artist AS a,
(
SELECT ma.artist_id, CONCAT_WS('-', mgr.country_code, count(mgr.media_id)) AS cnt
FROM music_album_artists AS ma
JOIN media_geo_restrict AS mgr ON ma.album_id = mgr.album_id
GROUP BY mgr.country_code
) AS count_table
WHERE a.seq_id > 0 and a.seq_id < 10000
and a.id=count_table.artist_id
group by a.id

select id from table with max date and max id if dates are the same

For an example I have MySQL table like:
id category date value
1 a 2013-01-02 7
2 a 2013-01-01 2
3 a 2013-01-01 3
4 b 2013-01-01 1
5 b 2013-01-02 4
6 b 2013-01-03 5
7 c 2013-01-03 4
8 c 2013-01-03 8
I need select records with MAX(date) for each category, but if date is the same, with MAX(id).
So, expected result must be:
id category date value
1 a 2013-01-02 7
6 b 2013-01-03 5
8 c 2013-01-03 8
As you can see, dates for same category can be not sorted (like id=4,5).
I tried query like:
SELECT * FROM mytable t1
INNER JOIN
(
SELECT category, MAX(date) AS MAXDATE
FROM mytable
GROUP BY category
) t2
ON t1.category = t2.category
AND t1.date = t2.MAXDATE
It works fine, when we have no same dates in table. But for my case, it will return records with lowest ids:
id category date value
1 a 2013-01-01 7
6 b 2013-01-03 5
7 c 2013-01-03 4
You are on the right track but you need to change your join.
I think what you want is the following:
SELECT * FROM mytable t1
INNER JOIN
(
SELECT MAX(id) AS ID, MAX(date) AS MAXDATE
FROM mytable
GROUP BY category
) t2
ON t1.id = t2.ID
AND t1.date = t2.MAXDATE
As mentioned, the previous wont work for every use case. The following might.
SELECT a.*
FROM mytable AS a,
(
SELECT category, date, MAX(id) AS ID
FROM mytable
GROUP BY category, date
) AS b,
(
SELECT category, MAX(date) AS date
FROM mytable
GROUP BY category
) AS c
WHERE b.category = c.category
AND b.date = c.date
AND a.id = b.id

Sum from different fields in different MySQL tables

I have these 2 tables:
table1
uid points
5 13
7 9
12 5
17 3
1 1
2 2
3 1
table2
uid points
9 21
13 17
15 11
17 7
12 6
2 2
1 3
22 1
I need a query to return top 5 users have points
Target result:
uid points
9 21
13 17
5 13
12 11
15 11
What I tried:
select uid, count(points) c from table1 order by c limit 5
union all
select uid, count(points) c from table2 order by c limit 5
But I did not get what I want.
SELECT al.uid as UID , SUM(al.points) AS total_points FROM (SELECT points, uid FROM table1
UNION ALL
SELECT points,uid FROM table2) al group by al.uid
Try This
select uid, (table1.points + table2.points) as c from table1
Left join table2 on table1.uid = table2.uid
order by (table1.points + table2.points) desc limit 5
You can try this query
Select
t3.uid , SUM(t3.points) As points
From
(SELECT * from Table1 UNION ALL SELECT * from Table2) t3
GROUP by
t3.uid
Order by
SUM(t3.points) DESC
LIMIT 5
demo at http://sqlfiddle.com/#!2/5605d/23
Try this:
SELECT Uid,SUM(Points)
FROM
(
SELECT Uid,Points FROM Table1
UNION ALL
SELECT Uid,Points FROM Table2
) as T
GROUP BY Uid
ORDER BY SUM(Points) DESC
LIMIT 5
SQLFiddle demo
If you want the top 5 overall, you need to use an order by and limit over a subquery:
select uid, sum(points) points from (
select uid, points from table1
union all
select uid, points from table2
) x
group by uid
order by sum(points) desc
limit 5