MySQL: Get most recent record satisfying certain conditions - mysql

I was inspired by this post. But what I'm going to solve is more complex.
In the table below we have three columns, id,rating,created, call it test_table,
+----+--------+----------------------+
| id | rating | created |
+----+--------+----------------------+
| 1 | NULL | 2011-12-14 09:25:21 |
| 1 | 2 | 2011-12-14 09:26:21 |
| 1 | 1 | 2011-12-14 09:27:21 |
| 2 | NULL | 2011-12-14 09:25:21 |
| 2 | 2 | 2011-12-14 09:26:21 |
| 2 | 3 | 2011-12-14 09:27:21 |
| 2 | NULL | 2011-12-14 09:28:21 |
| 3 | NULL | 2011-12-14 09:25:21 |
| 3 | NULL | 2011-12-14 09:26:21 |
| 3 | NULL | 2011-12-14 09:27:21 |
| 3 | NULL | 2011-12-14 09:28:21 |
+----+--------+----------------------+
I want to write a query which selects the most recent rating but not null for every id. If all of the ratings are null for a specific id, we select the most recent rating. The desired result is as follows:
+----+--------+----------------------+
| id | rating | created |
+----+--------+----------------------+
| 1 | 1 | 2011-12-14 09:27:21 |
| 2 | 3 | 2011-12-14 09:27:21 |
| 3 | NULL | 2011-12-14 09:28:21 |
+----+--------+----------------------+

The following gets the creation date:
select t.id,
coalesce(max(case when rating is not null then creation_date end),
creation_date
) as creation_date
from t
group by t.id;
You can then do this as:
select t.*
from t
where (id, creation_date) in (select t.id,
coalesce(max(case when rating is not null then creation_date end),
creation_date
) as creation_date
from t
group by t.id
);

One possible answer is this. Create a list of max(create) date per id and id having all NULL rating.
select t1.*
from myTable t1
join (
select id, max(created) as created
from myTable
where rating is not NULL
group by id
UNION ALL
select id, max(created) as created
from myTable t3
where rating is NULL
group by id
having count(*) = (select count(*) from myTable t4 where t4.id=t3.id)
) t2
where t1.id=t2.id
and t1.created=t2.created
order by t1.id;

select a.* from #test a join (select id, max(created) created
from #test
where rating is not null
group by id )b on a.id=b.id and a.created=b.created
union
select a.* from #test a join
(select id, max(created) created
from #test
where rating is null
and id not in
(select id from (select id, max(created) created
from #test
where rating is not null
group by id )d
group by id)
group by id )b on a.id=b.id and a.created=b.created

This query should work:
select a.id, a.rating, b.m from test_table a
join (
select id, max(created) as m from test_table
where rating is not null
group by id
) b on b.id = a.id and b.m = a.created
union
select a.id, a.rating, b.m from test_table a
join(
select id, max(created) as m from test_table a
where not exists
(select 1 from test_table b where a.id = b.id and b.rating is not null)
group by id
)b on b.id = a.id and b.m = a.created

You can get the created value in a correlated LIMIT 1 subquery:
select t.id, (
select created
from mytable t1
where t1.id = t.id
order by rating is null asc, created desc
limit 1
) as created
from (select distinct id from mytable) t
If you also need the rating column, you will need to join the result with the table again:
select t.*
from (
select t.id, (
select created
from mytable t1
where t1.id = t.id
order by rating is null asc, created desc
limit 1
) as created
from (select distinct id from mytable) t
) x
natural join mytable t
Demo: http://sqlfiddle.com/#!9/49e68c/8

Related

min and max with and without windowing function

create table dt
(
id varchar(20),
user_id int,
name varchar(20),
td DATE,
amount float
);
INSERT INTO dt VALUES('blah',1, 'Rodeo', '2018-01-20', 10.12);
INSERT INTO dt VALUES('blahblah',1, 'Rodeo', '2019-01-01', 40.44);
INSERT INTO dt VALUES('sas',2, 'Janice', '2018-02-05', 18.18);
INSERT INTO dt VALUES('dsdcd',3, 'Sam', '2019-01-26', 16.13);
INSERT INTO dt VALUES('sdc',2, 'Janice', '2018-02-01', 12.19);
INSERT INTO dt VALUES('scsc',2, 'Janice', '2017-12-06', 5.10);
+----------+---------+--------+------------+--------+
| id | user_id | name | td | amount |
+----------+---------+--------+------------+--------+
| blah | 1 | Rodeo | 2018-01-20 | 10.12 |
| blahblah | 1 | Rodeo | 2019-01-01 | 40.44 |
| sas | 2 | Janice | 2018-02-05 | 18.18 |
| dsdcd | 3 | Sam | 2019-01-26 | 16.13 |
| sdc | 2 | Janice | 2018-02-01 | 12.19 |
| scsc | 2 | Janice | 2017-12-06 | 5.1 |
+----------+---------+--------+------------+--------+
For the above table how i can get this output. I can achieve this by windowing function but not sure how to do this by correlated subquery. Appreciate any help!
Output
Basically difference of users first transaction amount from their latest transaction amount. If the user has only one transaction then the difference is 0
User_id name amount
1 Rodeo 30.32 [40.44(latest trans) - 10.12 (min trans)]
3 Sam 0
2 Janice 13.08 [18.18 (latest trans) - 5.1 (min trans)]
With 2 subqueries to get the latest and earliest amounts:
select distinct t.user_id, t.name,
(select amount from dt
where user_id = t.user_id
order by td desc limit 1
)
-
(select amount from dt
where user_id = t.user_id
order by td limit 1
) amount
from dt t
See the demo.
Or:
select t.user_id, t.name,
max(t.latest * t.amount) - max(t.earliest * t.amount) amount
from (
select d.user_id, d.name, d.amount,
d.td = g.earliestdate earliest, d.td = g.latestdate latest
from dt d inner join (
select user_id, min(td) earliestdate, max(td) latestdate
from dt
group by user_id
) g on d.user_id = g.user_id and d.td in (earliestdate, latestdate)
) t
group by t.user_id, t.name
See the demo.
Results:
| user_id | name | amount |
| ------- | ------ | ------ |
| 1 | Rodeo | 30.32 |
| 2 | Janice | 13.08 |
| 3 | Sam | 0 |
This is similar to SQL select only rows with max value on a column, but you need to do it twice: once for the earliest row, again for the latest row.
SELECT t1.user_id, t1.name, t1.amount - t2.amount ASA amount
FROM (
SELECT dt1.user_id, dt1.name, dt1.amount
FROM dt AS dt1
JOIN (
SELECT user_id, name, MAX(td) AS maxdate
FROM dt
GROUP BY user_id, name) AS dt2
ON dt1.user_id = dt2.user_id AND dt1.name = dt2.name AND dt1.td = dt2.maxdate
) AS t1
JOIN (
SELECT dt1.user_id, dt1.name, dt1.amount
FROM dt AS dt1
JOIN (
SELECT user_id, name, MIN(td) AS mindate
FROM dt
GROUP BY user_id, name) AS dt2
ON dt1.user_id = dt2.user_id AND dt1.name = dt2.name AND dt1.td = dt2.mindate
) AS t2
ON t1.user_id = t2.user_id AND t1.name = t2.name
Approach using Correlated Subquery:
Query
SELECT user_id,
name,
Round(Coalesce ((SELECT t1.amount
FROM dt t1
WHERE t1.user_id = dt.user_id
ORDER BY t1.td DESC
LIMIT 1) - (SELECT t2.amount
FROM dt t2
WHERE t2.user_id = dt.user_id
ORDER BY t2.td ASC
LIMIT 1), 0), 2) AS amount
FROM dt
GROUP BY user_id,
name;
| user_id | name | amount |
| ------- | ------ | ------ |
| 1 | Rodeo | 30.32 |
| 2 | Janice | 13.08 |
| 3 | Sam | 0 |
View on DB Fiddle
You can try this as well
Select t3.user_id, t3.name, max(t3.new_amount) FROM (
Select t1.user_id, t2.name, (t1.amount - t2.amount) as new_amount
FROM dt t1
INNER JOIN dt t2
ON t1.user_id=t2.user_id
Order by t1.user_id ASC, t1.td DESC, t2.user_id ASC, t2.td ASC
) as t3
group by t3.user_id,t3.name;
Demo

How to select the latest price for product? [duplicate]

This question already has answers here:
SQL select only rows with max value on a column [duplicate]
(27 answers)
Closed 4 years ago.
Here is my table:
+----+------------+-----------+---------------+
| id | product_id | price | date |
+----+------------+-----------+---------------+
| 1 | 4 | 2000 | 2019-02-10 |
| 2 | 5 | 1600 | 2019-02-11 |
| 3 | 4 | 850 | 2019-02-11 |
| 4 | 5 | 1500 | 2019-02-13 |
+----+------------+-----------+---------------+
I need to get a list of unique product ids that are the latest (newest, in other word, bigger date) ones. So this is the expected result:
+------------+-----------+---------------+
| product_id | price | date |
+------------+-----------+---------------+
| 4 | 850 | 2019-02-11 |
| 5 | 1500 | 2019-02-13 |
+------------+-----------+---------------+
Any idea how can I achieve that?
Here is my query:
SELECT id, product_id, price, MAX(date)
FROM tbl
GROUP BY product_id
-- ot selects the max `date` with random price like this:
+------------+-----------+---------------+
| product_id | price | date |
+------------+-----------+---------------+
| 4 | 2000 | 2019-02-11 |
| 5 | 1600 | 2019-02-13 |
+------------+-----------+---------------+
-- See? Prices are wrong
You could use a correlated subquery
select t1.* from table t1
where t1.date=( select max(date) from table t2
where t1.product_id=t2.product_id
)
Select *from
table1 t1
where (t1.product_id, t1.date) in
(select t2.product_id, max(t2.date)
from table1 t2
where t1.product_id = t2.product_id
)
Don't use a GROUP BY. Use a filter:
SELECT id, product_id, price, MAX(date)
FROM tbl
WHERE tbl.date = (SELECT MAX(t2.date)
FROM tbl t2
WHERE t2.product_id = tbl.product_id
);
With an index on (product_id, date), this is probably the fastest method.
If you can have duplicates on a given date, you can resolve them with:
SELECT id, product_id, price, MAX(date)
FROM tbl
WHERE tbl.id = (SELECT t2.id
FROM tbl t2
WHERE t2.product_id = tbl.product_id
ORDER BY t2.date DESC
LIMIT 1
);
My solution is with the analytic function first_value
SELECT distinct product_id,
first_value(price) over (partition by product_id order by date desc) last_price,
first_value(date) over (partition by product_id order by date desc) last_date
FROM tbl
Assuming that you are using a modern version of MySQL (8.0), you can use this:
select *
from (
SELECT id
, product_id
, price
, date
, row_number() over (partition by product_id order by date desc) rn
FROM tbl
) a
where rn = 1

how to get last and secondlast record.? and how to use it in as sub query.?

+----+-------------------------+--------+------------+-------+--------+--------------+------------+
| id | user_email | cat_id | sub_cat_id | score | out_of | score_in_per | date |
+----+-------------------------+--------+------------+-------+--------+--------------+------------+
| 13 | inststudent#yopmail.com | 9 | 11 | 40 | 40 | 100 | 22-04-2017 |
+----+-------------------------+--------+------------+-------+--------+--------------+------------+
| 14 | inststudent#yopmail.com | 9 | 11 | 37 | 40 | 92.5 | 22-04-2017 |
+----+-------------------------+--------+------------+-------+--------+--------------+------------+
| 26 | inststudent#yopmail.com | 9 | 11 | 36 | 40 | 88 | 23-04-2017 |
+----+-------------------------+--------+------------+-------+--------+--------------+------------+
| 27 | inststudent#yopmail.com | 9 | 11 | 35 | 40 | 80 | 23-04-2017 |
+----+-------------------------+--------+------------+-------+--------+--------------+------------+
From above table i want to get the record like this can anyone help me/
+-----------+-----------------+---------------+-------------------------+--------+------------+-------+--------+--------------+------+
| lastScore | secondLastScore | maxPortaScore | user_email | cat_id | sub_cat_id | score | out_of | score_in_per | date |
+-----------+-----------------+---------------+-------------------------+--------+------------+-------+--------+--------------+------+
| 80 | 88 | 100 | inststudent#yopmail.com | 9 | 11 | - | - | - | - |
+-----------+-----------------+---------------+-------------------------+--------+------------+-------+--------+--------------+------+
This is my query:
SELECT
scor.id,
(SELECT score_in_per
FROM tbl_student_skill_score
WHERE cat_id = scor.cat_id and sub_cat_id = scor.sub_cat_id and scor.user_email='inststudent#yopmail.com'
ORDER BY scor.date DESC,scor.id DESC LIMIT 1)
as lastScore,
(SELECT score_in_per
FROM tbl_student_skill_score
WHERE cat_id=scor.cat_id and sub_cat_id=scor.sub_cat_id and scor.user_email='inststudent#yopmail.com'
ORDER BY scor.date DESC,scor.id DESC LIMIT 1,1)
as secondLastScore,
(SELECT max(cast(score_in_per as decimal(5,2)))
FROM tbl_student_skill_score
WHERE cat_id = scor.cat_id and sub_cat_id=scor.sub_cat_id)
as maxPortaScore,
scor.user_email,scor.cat_id,
scor.sub_cat_id,scor.score, scor.out_of,
scor.score_in_per,scor.date
FROM
tbl_student_skill_score scor
LEFT JOIN
tbl_skilltest_subcategory subc
ON
scor .sub_cat_id = subc.scat_id
LEFT JOIN
tbl_skilltest_category catg
ON subc.cat_id = catg.id
where
scor.user_email = 'inststudent#yopmail.com'
GROUP BY
sub_cat_id
ORDER by
scor.id DESC,scor.date DESC
Form the above Query lastScore and secondLastScore are not working
lastScore means the last stored record in my case from 1st table it is id:27 record. so the result should be80
similarly
secondLastScore means the second last stored record in my case from 1st table it is id:26 record. so the result should be 88
maxPortaScore means the max score of that particular category across the table it is not relates to the particular student in my case it is 100
i have used the same user for example but actually it can be any user score.[THIS IS WORKING ABSOLUTLY FINE]
This query seems to be a bit of complicate, here I do choose to use more less joins to do this:
select
(
select t1.score_in_per
from tbl_student_skill_score t1
where t1.user_email = t.user_email
and t1.cat_id = t.cat_id
and t1.sub_cat_id = t.sub_cat_id
and t1.id = max(t.id) -- this will get the max(id) record in each group
limit 1
) lastScore,
(
select t1.score_in_per
from tbl_student_skill_score t1
where t1.user_email = t.user_email
and t1.cat_id = t.cat_id
and t1.sub_cat_id = t.sub_cat_id
and t1.id < max(t.id) -- this will get all the record which id less than max(id), then use `order by t1.id desc limit 1`, of course the second last record will be retrieved.
order by t1.id desc
limit 1
) secondLastScore,
max(score_in_per) as maxPortaScore,
user_email, `cat_id`, `sub_cat_id`
from tbl_student_skill_score t
group by `user_email`, `cat_id`, `sub_cat_id`
some other joins, you should add them yourself.
And if there is only one record in each group, the secondLastScore will be NULL.
You can do that in three steps: first of all you get the last id and the maximum score for each user
select user_email, max(id) max_id, max(score_in_per) max_score
from tbl_student_skill_score
group by user_email
Then you get the second last id by joining the table with the above query
select t2.max_id, max(id) as second_max, t2.max_score, user_email
from tbl_student_skill_score t1
right join (
select user_email, max(id) max_id, max(score_in_per) max_score
from tbl_student_skill_score
group by user_email
) t2
on t1.user_email = t2.user_email
where t1.id < t2.max_id
group by user_email
Finally you can join all this with the original table to get categories information and the score associated with last and second last id
select t2.score_in_per as lastScore,
t3.score_in_per as secondLastScore,
t1.max_score as maxPortaScore,
t2.user_email,
t2.cat_id,
t2.sub_cat_id
from (
select t2.max_id, max(id) as second_max, t2.max_score, user_email
from tbl_student_skill_score t1
right join (
select user_email, max(id) max_id, max(score_in_per) max_score
from tbl_student_skill_score
group by user_email
) t2
on t1.user_email = t2.user_email
where t1.id < t2.max_id
group by user_email
) t1
join tbl_student_skill_score t2
on t1.user_email = t2.user_email and
t1.max_id = t2.id
left join
tbl_student_skill_score t3
on t1.user_email = t3.user_email and
t1.second_max = t3.id

Get min price id without inner select

I have a table called a with this data:
+-----+-----------+-------+
| id | parent_id | price |
+-----+-----------+-------+
| 1 | 1 | 100 |
| 2 | 1 | 200 |
| 3 | 1 | 99 |
| 4 | 2 | 1000 |
| 5 | 2 | 999 |
+-----+-----------+-------+
I want to get the id of min pirce for each parent_id.
There is any way to get this result without subquery?
+-----+-----------+-------+
| id | parent_id | price |
+-----+-----------+-------+
| 3 | 1 | 99 |
| 5 | 2 | 999 |
+-----+-----------+-------+
SELECT D1.id, D1.parent_id, D1.price
FROM Data D1
LEFT JOIN Data D2 on D2.price < D1.price AND D1.parent_id = D2.parent_id
WHERE D2.id IS NULL
Here is a shot at how to do it without subqueries. I haven't tested, let me know if it works!
SELECT t.id, t.parent_id, t.price
FROM table t
LEFT JOIN table t2
ON (t.parent_id = t2.parent_id AND t.price > t2.price)
GROUP BY t.id, t.parent_id, t.price
HAVING COUNT(*) = 1 AND max(t2.price) is null
ORDER BY t.parent_id, t.price desc;
Try this:
SELECT T1.id,T2.parent_id,T2.price FROM
(SELECT id,price
FROM TableName) T1
INNER JOIN
(
SELECT parent_id,MIN(price) as price
FROM TableName
GROUP BY parent_id) T2 ON T1.price=T2.price
See result in SQL Fiddle.
Try group by,
SELECT parent_id,min(price)
FROM TableName
GROUP BY parent_id
You can do this with a LEFT JOIN
SELECT a.id, a.parent_id, a.price
FROM a
LEFT JOIN a AS b ON b.price < a.price AND b.parent_id = a.parent_id
WHERE b.id IS NULL
Find the results at this fiddle:
http://sqlfiddle.com/#!2/09c888/10
You can try this without using any join or subquery you will surely get the desired result.
SELECT TOP 2 FROM a ORDER BY price

Select Rows with maximum column value grouped by another column without nested select statement

I know that this is a duplicate of Select Rows with Maximum Column Value group by Another Column but I want to select rows that have the maximum column value,as group by another column , but without nested select statement, I know it can be done like this:
SELECT
T.Name,
T.Rank,
T.ID
FROM MyTable T
WHERE T.Rank = (
SELECT MAX( T1.Rank) FROM MyTable T1
WHERE T1.Name= T.Name
)
where ID,
Rank,
Name is the table schema, and I want to group by results by Name first, and then choose one row from each Name group, depending on which one has the highest Rank.
Attached is a sample of the table I want to select from
mysql> SELECT t1.nm, t1.rank,t1.id
FROM mytable t1
LEFT JOIN (
SELECT nm, max(rank) as top
FROM mytable t2
GROUP BY nm
) AS t2 ON t1.nm=t2.nm AND t1.rank = t2.top
WHERE t2.nm IS not NULL
ORDER BY nm;
+----+------+---------+
| nm | rank | id |
+----+------+---------+
| m | -1 | b7kjhsf |
| n | 13 | d3sf |
+----+------+---------+
2 rows in set (0.00 sec)
mysql> select * from mytable;
+----+------+----------+
| nm | rank | id |
+----+------+----------+
| n | 11 | asfd |
| n | 11 | bsf |
| n | 11 | zzasdfsf |
| n | 13 | d3sf |
| n | 11 | effesf |
| n | 10 | yxxgesf |
| n | 11 | bkhjusf |
| m | -1 | b7kjhsf |
| m | -4 | cdfgabsf |
+----+------+----------+
9 rows in set (0.00 sec)
As mentioned in the other answer, the only other alternative that I know of, is using Common Table Expressions:
;WITH CTE AS
(
T.Name,
T.Rank,
T.ID,
ROW_NUMBER() OVER
(PARTITION BY Name ORDER BY Rank DESC)
AS RowNumber
FROM MyTable
)
SELECT *
FROM CTE
WHERE RowNumber = 1
SELECT Name, Id, Rank FROM
(
SELECT T.Name, T.Id, T.Rank, RANK() OVER (PARTITION BY T.Name ORDER BY T.Rank DESC) = 1 AS NameRank
FROM MyTable T
)
WHERE NameRank = 1
Not sure whether you are just trying to exclude the nested select, and whether joining aginst a subselect would be acceptable. If so:-
SELECT
T.Name,
T.Rank,
T.ID
FROM MyTable T
INNER JOIN (SELECT Name, MAX(Rank) AS MaxRank FROM MyTable GROUP BY Name ) T1
ON T.Name = T1.Name
AND T.Rank = T1.MaxRank