This question already has answers here:
Retrieving the last record in each group - MySQL
(33 answers)
Closed 2 years ago.
I have a table like so. The way it works is that the billing occurs daily to make sure that accounts are current.
+------+------------+-------------+
| ID | AcctType | BillingDate |
+------+------------+-------------+
| 100 | Individual | 2020-01-01 |
| 100 | Individual | 2020-01-02 |
| 100 | Individual | 2020-01-03 |
| 101 | Group | 2020-01-01 |
| 101 | Group | 2020-01-02 |
| 101 | Individual | 2020-01-01 |
+------+------------+-------------+
What I need to find is the first and last AcctType of each plan by ID since the AcctType can change. I am using MySQL and the aggregation of select ID, AcctType, min(BillingDate) from table group by ID won't work because AcctType will return a random value associated with the ID. How do I reliably get the latest and earliest AcctType by ID? Using version 5.6.
If you are running MySQL 8.0, you can use window functions for this:
select distinct
id,
first_value(acctType) over(
partition by id
order by billingDate
rows between unbounded preceding and unbounded following
) firstAccType,
last_value(acctType) over(
partition by id
order by billingDate
rows between unbounded preceding and unbounded following
) lastAccType
from mytable
This generates a single record for each id, with the first and last value of accType in columns.
In earlier versions, using correlated subquery is probably the simplest solution to achieve the same result:
select distinct
id,
(
select t1.accType
from mytable t1
where t1.id = t.id
order by billingDate asc
limit 1
) firstAccType,
(
select t1.accType
from mytable t1
where t1.id = t.id
order by billingDate desc
limit 1
) lastAccType
from mytable
Related
This question already has answers here:
SQL select only rows with max value on a column [duplicate]
(27 answers)
Closed 7 months ago.
I have a database contains multiple values with same id but different build. What I am trying is to get only the row with highest build.
Lets say I have a data like below;
| id | build | name | value |
|------|---------|--------|---------|
| 1 | 100 | Older | 5 |
| 1 | 101 | Old | 10 |
| 1 | 102 | Curr | 15 |
When I run the following query;
SELECT id, MAX(build), name, value
FROM myTable
WHERE id = 1 (or id in (1..n) in real life)
GROUP BY id
I get the following
| id | build | name | value |
|------|---------|--------|---------|
| 1 | 102 | Older | 5 |
instead of;
| id | build | name | value |
|------|---------|--------|---------|
| 1 | 102 | Curr | 15 |
I am trying to achieve expected result without subquery. Is there any way to achieve this?
Thanks in advance!
You must group by name and value too :
SELECT id, MAX(build), name, value
FROM myTable
WHERE id = 1 (or id in (1..n) in real life)
GROUP BY id, name, value
Many databases process the "FROM" line first, the "WHERE" line second, and the "SELECT" last. Because of that, you are getting the first column that fits your "WHERE" line. You can eliminate this by adding the max requirement in the "WHERE" line such as:
SELECT id, MAX(build), name, value
FROM myTable
WHERE id = 1 (or id in (1..n) in real life)
AND build = (SELECT MAX(build) from myTable)
GROUP BY id;
I think you want a LIMIT query here:
SELECT id, build, name, value
FROM myTable
ORDER BY build DESC
LIMIT 1;
In the event that there could be two or more records tied for the maximum build value, then use a subquery:
SELECT id, build, name, value
FROM myTable
WHERE build = (SELECT MAX(build) FROM myTable);
Edit: To handle your problem finding the max build record for each separate id groups of records, use either ROW_NUMBER or RANK on MySQL 8+. Assuming ROW_NUMBER:
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY id ORDER BY build DESC) rn
FROM myTable
)
SELECT id, build, name, value
FROM cte
WHERE rn = 1;
This should do it for you:
WITH tempdata as
(
SELECT ROW_NUMBER() OVER(PARTITION BY id
ORDER BY build desc) as RowNumber,
id,
build,
name,
value
FROM #temptable
)
SELECT id,build,name,value FROM tempdata WHERE RowNumber = 1
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 have a table which contains an amount per month and a previous month amount.
Each month I need to carry the last previous month amount if it does not exist.
To explain slightly better (and with examples) I might have the following data;
Month,Amount,Previous
2019-01-01,100,0
2019-02-01,100,100
2019-03-01,100,null
2019-04-01,100,null
2019-05-01,100,200
2019-06-01,100,null
So I want to carry the 100 to March and April and then the 200 to June so it looks like this;
Month,Amount,Previous
2019-01-01,100,0
2019-02-01,100,100
2019-03-01,100,100
2019-04-01,100,100
2019-05-01,100,200
2019-06-01,100,200
I'm just hitting blanks, I know there is a way but the mind simply isn't putting it together.
I think it's going to involve LEFT JOIN on the same table and getting a MIN month value with the amount where the date is greater than the last month value but is not greater than the next.
Or it's going to be doing a subquery in a WHERE clause and a LEFT JOIN.
So far I've managed the below but it duplicates the May and June rows for each previous value (100 and 200).
SELECT
*
FROM
table1 t1
LEFT JOIN
table1 t2 ON
t1.month > t2.month
Month,Amount,Previous
2019-01-01,100,0
2019-02-01,100,100
2019-03-01,100,100
2019-04-01,100,100
2019-05-01,100,200
2019-05-01,100,100
2019-06-01,100,200
2019-06-01,100,100
drop table if exists t;
create table t
(Month date,Amount int,Previous int);
insert into t values
('2019-01-01',100,0),
('2019-02-01',100,100),
('2019-03-01',100,null),
('2019-04-01',100,null),
('2019-05-01',100,200),
('2019-06-01',100,null);
select t.*,
case when previous is null then
(select previous from t t1 where t1.month < t.month and t1.previous is not null order by month desc limit 1)
else previous
end as previousdownfilled
from t;
+------------+--------+----------+--------------------+
| Month | Amount | Previous | previousdownfilled |
+------------+--------+----------+--------------------+
| 2019-01-01 | 100 | 0 | 0 |
| 2019-02-01 | 100 | 100 | 100 |
| 2019-03-01 | 100 | NULL | 100 |
| 2019-04-01 | 100 | NULL | 100 |
| 2019-05-01 | 100 | 200 | 200 |
| 2019-06-01 | 100 | NULL | 200 |
+------------+--------+----------+--------------------+
6 rows in set (0.00 sec)
where the case statement checks if things need to be done and the correlated cub query does it. But I suspect this work should be done in the code which creates this table.
You can use a Correlated Subquery here. In the subquery, we will utilize ORDER BY with LIMIT to get the immediate previous amount value.
SELECT
t1.month,
t1.amount,
(SELECT t2.amount FROM table1 t2
WHERE t2.month < t1.month
ORDER BY t2.month DESC LIMIT 1) AS previous
FROM
table1 t1
In MySL 8+, you can use window functions. Unfortunately, MySQL has not (yet) implemented the simplest approach -- lag() with the ignore nulls option.
But you can still do this pretty simply:
select month, amount,
max(previous) over (partition by grp) as previous
from (select t.*, count(previous) over (order by month) as grp
from t
) t;
This question already has answers here:
SQL select only rows with max value on a column [duplicate]
(27 answers)
Closed 5 years ago.
I have this table
id|name|number|date
1|peter|2|2017-09-18
2|peter|1|2017-10-03
3|james|4|2017-09-05
4|james|1|2017-10-10
5|james|0|2017-10-15
6|kate|4|2017-09-16
7|kate|2|2017-10-17
I want to select the latest row for each person (the row with the latest date). The result will look like
2|peter|1|2017-10-03
5|james|0|2017-10-15
7|kate|2|2017-10-17
Which MYSQL query can do that?
Something like :
SELECT id, name, number, date
FROM table
GROUP BY id, name, number, date
HAVING date the latest
MariaDB [test]> select * from stack s1 where s1.d = (select max(s2.d)
from stack s2 where s2.name = s1.name);
+------+-------+------+------------+
| id | name | num | d |
+------+-------+------+------------+
| 2 | peter | 1 | 2017-10-03 |
| 5 | james | 0 | 2017-10-15 |
| 7 | kate | 2 | 2017-10-17 |
+------+-------+------+------------+
3 rows in set (0.00 sec)
And for large datasets, don't forget to add indexes to 'name' and 'd' columns.
SELECT * FROM persons WHERE name='someone' order by date desc limit 1
This works well for selecting lastest entity by specified name.
SELECT * FROM persons GROUP BY name, date ORDER BY date DESC
I wrote this with my mobile, something can be wrong, but its concepts will be correct.
select t1.* from table t1, (select t2.name name2, max(t2.date)
date2 from table t2 group by t2.name) t3 where t1.name = t3.name2 and t1.date = t3.date2
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