min and max with and without windowing function - mysql

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

Related

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

MySQL: Get most recent record satisfying certain conditions

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

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

Select last record of each person

I have the following tables
tbl_investors
id | first_name | last_name |
---------------------------------------
1 | Jon | Cold |
2 | Rob | Ark |
3 | Rickon | Bolt |
tbl_investors_ledger
id | investor_id | amount |
------------------------------------
1 | 1 | 500 |
2 | 2 | 200 |
3 | 2 | 250 |
4 | 2 | 300 |
5 | 3 | 10 |
6 | 1 | 550 |
7 | 3 | 20 |
I just want to return all investors with their latest amount. Ex, Jon Cold with 550, Rob Ark 300 and Rickon Bolt 20, alphabetically with their last name.
I have an existing query but it will not return the latest amount of the investor. Can someone help me what i'm doing wrong?
SELECT t1.*, t2.*
FROM ".tbl_investors." t1
LEFT JOIN ".tbl_investors_ledger." t2
ON t1.id = t2.investor_id
LEFT JOIN (SELECT t.investor_id, max(t.id) as tid
FROM ".tbl_investors_ledger." t ) tt
ON tt.investor_id = t2.investor_id AND tt.tid = t2.id
GROUP BY t2.investor_id
ORDER BY t1.last_name
You can use GROUP_CONCAT and SUBSTRING_INDEX together
SELECT I.*
, SUBSTRING_INDEX(GROUP_CONCAT(L.amount ORDER BY L.id DESC), ',', 1) AS LastAmount
FROM tbl_investors AS I
LEFT JOIN tbl_investors_ledgers AS L
ON L.investor_id = I.id
GROUP BY I.id
ORDER BY I.last_name
Here a demo from SQLFiddle, many thanks to #zakhefron :)
Try this;)
SELECT t1.*, t2.*
FROM tbl_investors t1
LEFT JOIN tbl_investors_ledger t2
ON t1.id = t2.investor_id
INNER JOIN (
SELECT t.investor_id, max(t.id) as tid
FROM tbl_investors_ledger t GROUP BY t.investor_id) tt
ON tt.investor_id = t2.investor_id AND tt.tid = t2.id
ORDER BY t1.last_name
SQLFiddle DEMO
And check related OP Retrieving the last record in each group and this blog How to select the first/least/max row per group in SQL, you can find more solutions for your question.

MySQL - count in same table

I have the following table:
+----+---------------------+---------------+
| id | created_at | deklaracja_id |
+----+---------------------+---------------+
| 1 | 2015-01-09 12:14:00 | 1 |/*deklaracja*/
| 2 | 2015-02-09 12:14:00 | 1 |/*korekta for 1*/
| 3 | 2015-03-09 12:14:00 | 3 |/*deklaracja/
| 4 | 2015-01-09 12:14:00 | 3 |/*korekta for 3*/
| 5 | 2015-10-09 12:14:00 | 3 |/*korekta for 3*/
| 6 | 2015-10-09 12:14:00 | 6 |/*deklaracja*/
+----+---------------------+---------------+
Cond:
id = deklaracja_id is "deklaracja"
id <> deklaracja_id is "korekta"
I need a query to show all "deklaracja" and count of their "korekty" later than 2015-01-09.
Ex.
+----+---------------------+---------------+
| id | created_at | korekty_count |
+----+---------------------+---------------+
| 1 | 2015-01-09 12:14:00 | 1 |
| 3 | 2015-03-09 12:14:00 | 2 |
| 6 | 2015-10-09 12:14:00 | 0 |
+----+---------------------+---------------+
I've tried something like:
SELECT *,
SUM(CASE WHEN (id <> deklaracja_id)THEN 1 ELSE 0 END )
AS korekty_count
FROM Deklaracja
WHERE created >= '2015-09-01 00:00:00'
but it's not working and now I'm totally stuck :/
You can use a correlated sub-query:
SELECT id, created_at,
(SELECT COUNT(*)
FROM Deklaracja AS t2
WHERE t1.id = t2.deklaracja_id AND
t2.id <> t2.deklaracja_id) AS AS korekty_count
FROM Deklaracja AS t1
WHERE id = deklaracja_id
Demo here
Add a GROUP BY clause to your query.
SELECT *,
SUM(CASE WHEN (id <> deklaracja_id)THEN 1 ELSE 0 END )
AS korekty_count
FROM Deklaracja
WHERE created_at >= '2015-01-09 00:00:00' GROUP BY deklaracja_id
SELECT created_at, count(*)-1
from Deklaracje d
where id in (select id from Deklaracje e
where e.deklaracja_id=d.deklaracja_id)
group by deklaracja_id
SQL Fiddle link
I would use a subquery to get the records you're interested in before grouping:
SELECT
id,
created_at,
COUNT(deklaracja_id) AS korekty_count
FROM (
SELECT id, deklaracja_id, created_at
FROM Deklaracja
WHERE created_at >= '2015-09-01 00:00:00'
AND id <> deklaracja_id
) tmp
GROUP BY id;
See demo.
Something like this:
select id, created_at,
( select count(*) from deklaracja dt
where dt.deklaracja_id <> dt.id
and dt.deklaracja_id = d.deklaracja_id
and dt.created_at >= '2015-09-01 00:00:00' ) as korekty_count
from deklaracja d
where id = deklaracja_id
The answer by Giorgos Betsos is a good one. However, if you want to achieve the same results without a sub-query like you tried in your query, then try this one using joins:
SELECT t1.id, t1.created_at, COUNT(t2.id) AS korekty_count
FROM Deklaracja AS t1
LEFT JOIN Deklaracja AS t2 ON t1.id = t2.deklaracja_id
AND t2.id <> t2.deklaracja_id
WHERE t1.id = t1.deklaracja_id
AND t1.created_at >= '2015-09-01 00:00:00'
GROUP BY t1.id
Here is a fiddle