SQL GroupBy to show one row with max value in one row - sql

I have following table
ID Date quantity storename
id1 01-01 1 A1
id2 01-03 3 A2
id1 01-03 40 A2
I want to see
ID Date quantity storename
id1 01-03 40 A2
id2 01-03 3 A2
So basically I would like to groupby ID and find the max(newest) date. Then get the entire row data from that max(newest) date.
I tried the following code and it's not working out.
SELECT ID, max(Date), quantity, storename FROM table
GROUPBY ID
Also, is it possible to get all the columns(Like using *) instead of specifying one by one?

You can use aggregation:
select t.*
from t
where t.date = (select max(t2.date) from t t2);
Do be sure that the date does not have a time component. If it does:
select t.*
from t
where trunc(t.date) = (select trunc(max(t2.date)) from t t2);

You would want to do something like this:
select *
from (
select
ROW_NUMBER() OVER
(
PARTITION BY
tbl.ID
ORDER BY
tbl.date desc
) AS RowNumber,
*
from
tablename as tbl
) tmp
where tmp.RowNumber = 1
The partition by will group your data and then order it by the field(s) you want. You can include as many group bys (partition by) or order bys as you would like by using a comma to separate them.

Related

SQL: order by two columns and get the firsts rows with equal values in 2-nd column

I have a table sorted by 1, 2 columns. And I need to get the first row from the top and all succeeding rows while their values of 2-nd column is the same as value of the first row.
F.e I have data sample:
select * from sample
order by ID desc, date desc
ID Date
--- ----
45 NULL
44 NULL
40 01/01/10
35 NULL
32 04/05/08
I need to get the first two rows (with id in (45, 44)), because 2-nd row have Date = NULL.
If I'd had data sample:
ID Date
--- ----
45 NULL
44 NULL
40 NULL
35 NULL
32 04/05/08
I will need to get the first 4 rows (with id in (45, 44, 40, 35)).
I can't make query to resolve my issue. I considered about using row_number() and rank(), but I can't adapt they for me purpose.
Thanks a lot for any help!
Based on your description, you can do something like this:
with t as (<your query here>)
select t
from t cross join
(select t.*
from t
order by id desc
limit 1
) tt
order by (case when t.date = tt.date or t.date is null and t2.date is null then 1 else 2 end),
t.id desc;
Well, I concocted something like this, but it doesnt look elegantly.
select *
from (
select *,
sum(rank_date) over (partition by rank_date order by ID desc) as sm
from (
select *
,rank() over(order by DATE desc nulls first) rank_date
,row_number() over(order by ID desc) rank_id
from sample
) ss
) s
where sm = row_number

Group by with MIN value in same query while presnting all other columns

I have a view called a with this data:
ID tDate name task val
23 2015-06-14
23 2015-06-25
126 2015-06-18
126 2015-06-22
126 2015-06-24
ID is integer and tDate is timestamp.
Basically I want to get for each ID the min value of tDate and present this row.
meaning:
ID tDate name task val
23 2015-06-14
126 2015-06-18
I wrote this query:
select ID, min(tDate)
from a
group by ID
order by ID
This is working BUT it doesn't allow me to present all other columns of a
for example if I do:
select ID, min(tDate), name
from a
group by ID
order by ID
it says that name must be under group by. So I wrote this query:
select ID, MIN(tDate), name, task, val , ....
from a
group by ID, name, task, val , ....
order by ID
And this one doesn't work. it gives false results.
How do I solve it?
Postgres has the very convenient distinct on for this type of problem:
select distinct on (id) a.*
from a
order by id, tdate;
This will return one row for each id. The row is the first one determined by the ordering defined in the order by clause.
Do a join from the one table to a sub-query table on just the ID / Min Date
select
YT.ID,
YT.tDate as OriginalDate,
PQ.MinDate,
YT.name,
YT.task,
YT.val
from
YourTable YT
JOIN ( select ID, min( tdate ) as MinDate
from YourTable
group by ID ) as PQ
on YT.ID = PQ.ID
AND YT.tDate = PQ.MinDate
order by
ID
Try something like this:
select a.id, a.tdate , .... from a
join (select id, min(tdate) min_date
from a
group by ID
) b
on a.id=b.id and a.tdate = b.min_date
order by a.id

Group-by statement doesn't work

So currently, I have the following table:
ID, Name, Code, Date
1 AB x1 01/03/2014
1 AB x2 01/04/2014
1 AB x3 01/05/2014
2 BC x3 01/05/2014
2 BC x5 01/06/2014
3 CD x1 01/06/2014
I want the following output:
ID, Name, Code, Date
1 AB x3 01/05/2014
2 BC x5 01/06/2014
3 CD x1 01/06/2014
So basically, I just want the latest date, without caring for the code.
In my code, I have
select id, name, code, max(date)
group by id, name, code
But the group by does not work as it's also going to take the code into consideration, thus I don't get just the latest date. Also, I can't leave code in the group by statement as it'll give me an error.
How do I use a group by without including code?
I'm using PL/SQL developer as IDE.
select id, name, code, date
from (
select id, name, code,
date,
max(date) over (partition by id) as max_date
from the_table
)
where date = max_date;
If you want to pick exactly one of the dates if there are multiple "max dates" you can use row_number() instead:
select id, name, code, date
from (
select id, name, code,
date,
row_number() over (partition by id order by date desc) as rn
from the_table
)
where rn = 1;
Btw: date is a horrible name for a column. For one because it's also the name of a data type but more importantly because it does not document at all what the column contains. An "end date"? A "start date"? A "due date"? ...
What you want is latest updated record right?
select t1.*
from table t1
inner join (select id, name, max(date) as latest_date
from table
group by id, name) t2 on t1.date = t2.latest_date
and t1.id = t2.id and t1.name = t2.name
It will be good to have index on date column
I assume you want to get whatever the code is that is on the row that has the max date. If you truly don't care what code gets returned, just use an aggregate function on it like max(code).
Otherwise, you can do this:
SELECT t1.id, t1.name, t2.code, t2.date
FROM MyTable t1
CROSS JOIN (
SELECT TOP 1 code, date
FROM MyTable t3
WHERE t3.id=t1.id
AND t3.name=t1.name
ORDER BY t3.date DESC
) t2
I'm not sure if CROSS JOIN is PL/SQL compatible, but you can find the equivalent, I'm sure.

How do I return a single row based on the aggregate of more than one column

Sorry for the ambiguous title, not sure how to search, or ask this question.
Lets say we have TableA:
RowID FkId Rank Date
ID1 A 1 2013-3-1
ID2 A 2 2013-3-2
ID3 A 2 2013-3-3
ID4 B 3 2013-3-4
ID5 A 1 2013-3-5
I need to create a view, that will return 1 row for each FkId. The row should be the max rank, and max date. So for FkId "A", the query would return the row for "ID3".
I was able to return a single row by using sub-queries; first I get the MAX(Rank), then join to another query that gets MAX(Date) group by FkId & Rank.
SELECT TableA.*
(Select FkId, MAX(Rank) AS Rank FROM TableA GROUP BY FkId) s1
INNER JOIN (Select FkId, Rank, MAX(Date) AS Date FROM TableA GROUP BY FkId,Rank) s2 ON s1.FkId = s2.FkId AND s1.Rank = s2.Rank
INNER JOIN TableA ON s2.FkId = TableA.FkId AND s2.Rank = TableA.Rank AND s2.Date = TableA.Date
Is there a more efficient query that would achieve the same results? Thanks for looking.
Edit: Added ID5 since the last answer. If I tried a normal MAX(rank),MAX(Date) GROUP BY FkId, then for "A", I would get A; 2; 2013-3-5. This result would not match up to a RowId.
You can use ROW_NUMBER with a CTE (presuming sql-server >= 2005):
WITH CTE AS
(
SELECT TableA.*,
RN = ROW_NUMBER() OVER (PARTITION BY FkId Order By Rank Desc, Date DESC)
FROM Table A
)
SELECT RowID,FkId, Rank,Date
FROM CTE WHERE RN = 1
Your question (clarified in the comments to this answer) asks for:
A single row for each FkId
The Max Date and Rank
The results to correspond to a row in the original data.
In the case that there are FkIds with rows such that the maximum date and maximum rank are in separate rows, you'll have to relax at least one of these requirements.
If you're willing to relax requirement (3), then you can use GROUP BY:
SELECT FkId, MAX(Rank) AS Rank, Max(Date) AS Date
FROM TableA
GROUP BY FkId
Given the extra information in the comments. That you want the latest, of the highest ranked entries for each FkId, the following should work:
SELECT FkId, Rank, MAX(Date) AS Date
FROM TableA A
WHERE Rank = (SELECT MAX(Rank)
FROM TableA sub
WHERE A.FkId = sub.FkId
GROUP BY sub.FkId)
GROUP BY FkId, Rank
Here's a sqlfiddle to show it in action.
You can use Rank() and inline query to achieve it.
select * from TableA
where RowID in (
select rowID from (
select FKID, RowID,
rank() over (partition by FKID order by [Rank] desc, [Date] desc) as RankNumber
from TableA ) A
where A.RankNumber=1 )
SQL Fiddle Demo
You can also be sneaky and accomplish what ljh suggested like this:
select top 1 with ties *
from TableA
order by rank() over (
partition by FKID
order by [Rank] desc, [Date] desc
)

Select newest records that have distinct Name column

I did search around and I found this
SQL selecting rows by most recent date with two unique columns
Which is so close to what I want but I can't seem to make it work.
I get an error Column 'ID' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
I want the newest row by date for each Distinct Name
Select ID,Name,Price,Date
From table
Group By Name
Order By Date ASC
Here is an example of what I want
Table
ID
Name
Price
Date
0
A
10
2012-05-03
1
B
9
2012-05-02
2
A
8
2012-05-04
3
C
10
2012-05-03
4
B
8
2012-05-01
desired result
ID
Name
Price
Date
2
A
8
2012-05-04
3
C
10
2012-05-03
1
B
9
2012-05-02
I am using Microsoft SQL Server 2008
Select ID,Name, Price,Date
From temp t1
where date = (select max(date) from temp where t1.name =temp.name)
order by date desc
Here is a SQL Fiddle with a demo of the above
Or as Conrad points out you can use an INNER JOIN (another SQL Fiddle with a demo) :
SELECT t1.ID, t1.Name, t1.Price, t1.Date
FROM temp t1
INNER JOIN
(
SELECT Max(date) date, name
FROM temp
GROUP BY name
) AS t2
ON t1.name = t2.name
AND t1.date = t2.date
ORDER BY date DESC
There a couple ways to do this. This one uses ROW_NUMBER. Just partition by Name and then order by what you want to put the values you want in the first position.
WITH cte
AS (SELECT Row_number() OVER (partition BY NAME ORDER BY date DESC) RN,
id,
name,
price,
date
FROM table1)
SELECT id,
name,
price,
date
FROM cte
WHERE rn = 1
DEMO
Note you should probably add ID (partition BY NAME ORDER BY date DESC, ID DESC) in your actual query as a tie-breaker for date
select * from (
Select
ID, Name, Price, Date,
Rank() over (partition by Name order by Date) RankOrder
From table
) T
where RankOrder = 1
I have found another memory efficient way (but probably crude way)that has worked for me in postgress. Order the query by the date desc, then select the first record of each distinct field.
SELECT distinct on (Name) ID, Price, Date from
table
order by Date desc
Use Distinct instead of Group By
Select Distinct ID,Name,Price,Date
From table
Order By Date ASC
http://technet.microsoft.com/en-us/library/ms187831.aspx