SQL with multiple ROW_NUMBER or RANK - sql

I have the need to do multiple joins with the same table, between (eg) Person and PersonEvents. There are multiple events for each person (0 or more). I need to create a VIEW that selects each person with certain columns from their most recent event, plus columns from the next-most-recent event.
Person data:
Id Name
1 Iain
2 Fred
3 Mary
4 Foo
5 Bar
PersonEvents data:
PersonId DateStarted ReasonForLeaving
1 2011-03-12 00:00:00.000 sick
1 2013-02-12 00:00:00.000 NULL
1 2012-04-12 00:00:00.000 holiday
2 2011-05-12 00:00:00.000 new baby
2 2013-06-12 00:00:00.000 NULL
2 2012-07-12 00:00:00.000 had enough
3 2011-08-12 00:00:00.000 pregnant
3 2013-09-12 00:00:00.000 NULL
4 2012-10-12 00:00:00.000 NULL
An output sample would be:
Id Name MemberSince ReasonForChange
1 Iain 2011-03-12 00:00:00.000 holiday
4 Foo 2012-10-12 00:00:00.000 NULL
...
The "old way" used a top 1 join or sub-select statement:
SELECT p.*,
(
SELECT TOP 1 DateStarted
FROM PersonEvents e
WHERE e.PersonId = p.Id
ORDER BY DateFoo DESC
) As MemberSince
FROM Person p
....
However if you need multiple columns from this Join, (eg Date, Comment, and maybe further ids), then you need to do multiple sub-select statements, which is expensive.
So the question is: How do you get multiple columns from a join using the row number for the most recent, and previous events?

The most straight forward (ie. readable SQL) answer that I have come up with uses WITH and ROW_NUMBER.
First, make a ROW_NUMBER query that orders the events and gives a number to each event unique to that PersonId:
SELECT *,
ROW_NUMBER() OVER (PARTITION BY PersonId ORDER BY DateStarted DESC) AS EventOrder
FROM PersonEvents
Results:
PersonId DateStarted ReasonForLeaving EventOrder
1 2013-02-12 00:00:00.000 NULL 1
1 2012-04-12 00:00:00.000 holiday 2
1 2011-03-12 00:00:00.000 sick 3
2 2013-06-12 00:00:00.000 NULL 1
2 2012-07-12 00:00:00.000 had enough 2
2 2011-05-12 00:00:00.000 new baby 3
3 2013-09-12 00:00:00.000 NULL 1
3 2011-08-12 00:00:00.000 pregnant 2
4 2012-10-12 00:00:00.000 NULL 1
Now, the "first" event (in my case the most recent) for every person contains the date that the change was made (real-life example: this is student enrolment history data across multiple schools, containing School ID and lots of other guff). The "Second" event for every person contains the previous event and reason for leaving. To add it together:
WITH SortedEvents AS (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY PersonId ORDER BY ReasonForLeaving DESC) AS EventOrder
FROM PersonEvents
)
SELECT p.*, MostRecent.DateStarted AS MemberSince, NextRecent.ReasonForLeaving AS ReasonForChange
FROM Person p
LEFT OUTER JOIN SortedEvents AS MostRecent ON p.Id = MostRecent.PersonId AND MostRecent.EventOrder = 1
LEFT OUTER JOIN SortedEvents AS NextRecent ON p.Id = NextRecent.PersonId AND NextRecent.EventOrder = 2
which provides the nicely formatted output:
Id Name MemberSince ReasonForChange
1 Iain 2013-02-12 00:00:00.000 holiday
2 Fred 2013-06-12 00:00:00.000 had enough
3 Mary 2013-09-12 00:00:00.000 pregnant
4 Foo 2012-10-12 00:00:00.000 NULL
5 Bar NULL NULL
in reality you could pick multiple columns from any row number. The real life example (again, student enrolment history) picks:
From the master student table:
student id
name
DOB, etc
From the Enrolment History table as "current enrolment"
School id
various enrolment status info
date started
From the Enrolment History table as "previous enrolment"
reason for leaving
This method is quite efficient with about 150k students and their respective history.
complete SQL for my tests:
CREATE TABLE Person
(
Id INT NOT NULL,
Name VARCHAR(50)
)
GO
CREATE TABLE PersonEvents
(
PersonId INT NOT NULL,
DateStarted DATETIME NOT NULL,
ReasonForLeaving VARCHAR(50)
)
GO
INSERT INTO Person
SELECT 1, 'Iain' UNION ALL
SELECT 2, 'Fred' UNION ALL
SELECT 3, 'Mary' UNION ALL
SELECT 4, 'Foo' UNION ALL
SELECT 5, 'Bar'
GO
INSERT INTO PersonEvents
SELECT 1, '20110312', 'sick' UNION ALL
SELECT 1, '20130212', NULL UNION ALL
SELECT 1, '20120412', 'holiday' UNION ALL
SELECT 2, '20110512', 'new baby' UNION ALL
SELECT 2, '20130612', NULL UNION ALL
SELECT 2, '20120712', 'had enough' UNION ALL
SELECT 3, '20110812', 'pregnant' UNION ALL
SELECT 3, '20130912', NULL UNION ALL
SELECT 4, '20121012', NULL
GO
--SELECT *
--FROM Person
--SELECT *
--FROM PersonEvents
--GO
WITH SortedEvents AS (
SELECT *,
ROW_NUMBER() OVER (PARTITION BY PersonId ORDER BY DateStarted DESC) AS EventOrder
FROM PersonEvents
)
SELECT p.*, MostRecent.DateStarted AS MemberSince, NextRecent.ReasonForLeaving AS ReasonForChange
FROM Person p
LEFT OUTER JOIN SortedEvents AS MostRecent ON p.Id = MostRecent.PersonId AND MostRecent.EventOrder = 1
LEFT OUTER JOIN SortedEvents AS NextRecent ON p.Id = NextRecent.PersonId AND NextRecent.EventOrder = 2
GO
SELECT p.*,
(
SELECT TOP 1 DateStarted
FROM PersonEvents pe
WHERE pe.PersonId = p.Id
ORDER BY DateStarted DESC
) AS MemberSince,
'unknown' AS ReasonForChange
FROM Person p
GO
DROP TABLE Person
DROP TABLE PersonEvents
GO

For the last event and previous event date:
SELECT ID,NAME,NextToMostEventDate,ReasonForLeaving
FROM PersonEvents pe
INNER JOIN(
SELECT pe1.PersonId,TheMostEventDate,NextToMostEventDate=MAX(pe1.DateStarted)
FROM PersonEvents pe1
INNER JOIN(
SELECT PersonId,TheMostEventDate=MAX(DateStarted)
FROM PersonEvents
GROUP BY PersonId
) pe2
ON pe2.PersonId=pe1.PersonId
WHERE DateStarted<TheMostEventDate
GROUP BY pe1.PersonId,TheMostEventDate
) pe12 ON pe12.PersonId=pe.PersonId
INNER JOIN Person ON Id=pe.PersonId
WHERE pe.DateStarted=TheMostEventDate
For the last event date and previous event:
SELECT ID,NAME,TheMostEventDate,ReasonForLeaving
FROM PersonEvents pe
INNER JOIN(
SELECT pe1.PersonId,TheMostEventDate,NextToMostEventDate=MAX(pe1.DateStarted)
FROM PersonEvents pe1
INNER JOIN(
SELECT PersonId,TheMostEventDate=MAX(DateStarted)
FROM PersonEvents
GROUP BY PersonId
) pe2
ON pe2.PersonId=pe1.PersonId
WHERE DateStarted<TheMostEventDate
GROUP BY pe1.PersonId,TheMostEventDate
) pe12 ON pe12.PersonId=pe.PersonId
INNER JOIN Person ON Id=pe.PersonId
WHERE pe.DateStarted=NextToMostEventDate

Related

Get SQL Server result using subquery

I have a Members table like this:
PersonID FirstName Address City date
---------------------------------------------------------
3 Rasanga Skagen 21 South 2019-01-05
and a Persons table:
PersonID FirstName Address City date
-------------------------------------------------------
3 Rasanga Skagen 21 South 2019-01-06
1 Tom B. Skagen 21 Colombo 2018-01-07
2 Tom B. Skagen 21 Colombo 2019-01-05
I want to get Persons that do not exists in Members table using the FirstName column. For that I'm using this query:
SELECT *
FROM Persons p
WHERE NOT EXISTS (SELECT * FROM Members m WHERE m.FirstName = p.FirstName)
When I execute above query I'm getting same FirstName and 2 records but my requirement is if there's 2 records for same name retrieve latest record using date column. Therefore above scenario it should be Tom B. with 2018-01-07 record. If both records have same date should retrieve 1 record from 2 records.
Can somebody explain how to do this?
You can use the left join and checking the Members.PersonId is null.
create table Members(PersonID int
, FirstName varchar(20)
, Address varchar(50)
, City varchar(50)
, Dtdate date)
insert into Members values
(3, 'Rasanga', 'Skagen 21', 'South', '2019-01-05')
Create table Persons(PersonID int
, FirstName varchar(20)
, Address varchar(50)
, City varchar(50)
, Dtdate date)
insert into Persons values
(3, 'Rasanga', 'Skagen 21', 'South', '2019-01-06'),
(1, 'Tom B.', 'Skagen 21', 'Colombo', '2018-01-07'),
(2, 'Tom B.', 'Skagen 21', 'Colombo', '2019-01-05')
Select Persons.* from Persons
left join Members on Persons.PersonID = Members.PersonID
where Members.PersonId is null
Demo
Using the not exists you can check as shown below.
SELECT Persons.*
FROM Persons
WHERE NOT EXISTS (SELECT 1
FROM Members
WHERE Persons.PersonID = Members.PersonID)
Using the in operator
SELECT * FROM Persons
WHERE PersonID NOT IN (
SELECT PersonID FROM Members
)
To get the unique records based on the first name and date you can use the following query using ROW_NUMBER() function.
;WITH cte
AS (
SELECT Persons.*
,ROW_NUMBER() OVER (
PARTITION BY Persons.FirstName ORDER BY Persons.Dtdate DESC
) AS RN
FROM Persons
LEFT JOIN Members ON Persons.PersonID = Members.PersonID
WHERE Members.PersonId IS NULL )
SELECT *
FROM CTE
WHERE RN = 1
Output
PersonID FirstName Address City Dtdate RN
----------------------------------------------------------
2 Tom B. Skagen 21 Colombo 2019-01-05 1
You could use a window function as
SELECT T.PersonID,
T.FirstName,
T.Address,
T.City,
T.[Date]
FROM
(
SELECT *, ROW_NUMBER() OVER(PARTITION BY FirstName ORDER BY [Date] DESC) RN
FROM Persons
) T
WHERE NOT EXISTS
(
SELECT 1
FROM Members
WHERE FirstName = T.FirstName
) AND T.RN = 1;
Here is a db<>fiddle

Select except where different in SQL

I need a bit of help with a SQL query.
Imagine I've got the following table
id | date | price
1 | 1999-01-01 | 10
2 | 1999-01-01 | 10
3 | 2000-02-02 | 15
4 | 2011-03-03 | 15
5 | 2011-04-04 | 16
6 | 2011-04-04 | 20
7 | 2017-08-15 | 20
What I need is all dates where only one price is present.
In this example I need to get rid of row 5 and 6 (because there is two difference prices for the same date) and either 1 or 2(because they're duplicate).
How do I do that?
select date,
count(distinct price) as prices -- included to test
from MyTable
group by date
having count(distinct price) = 1 -- distinct for the duplicate pricing
The following should work with any DBMS
SELECT id, date, price
FROM TheTable o
WHERE NOT EXISTS (
SELECT *
FROM TheTable i
WHERE i.date = o.date
AND (
i.price <> o.price
OR (i.price = o.price AND i.id < o.id)
)
)
;
JohnHC answer is more readable and delivers the information the OP asked for ("[...] I need all the dates [...]").
My answer, though less readable at first, is more general (allows for more complexes tie-breaking criteria) and also is capable of returning the full row (with id and price, not just date).
;WITH CTE_1(ID ,DATE,PRICE)
AS
(
SELECT 1 , '1999-01-01',10 UNION ALL
SELECT 2 , '1999-01-01',10 UNION ALL
SELECT 3 , '2000-02-02',15 UNION ALL
SELECT 4 , '2011-03-03',15 UNION ALL
SELECT 5 , '2011-04-04',16 UNION ALL
SELECT 6 , '2011-04-04',20 UNION ALL
SELECT 7 , '2017-08-15',20
)
,CTE2
AS
(
SELECT A.*
FROM CTE_1 A
INNER JOIN
CTE_1 B
ON A.DATE=B.DATE AND A.PRICE!=B.PRICE
)
SELECT * FROM CTE_1 WHERE ID NOT IN (SELECT ID FROM CTE2)

SQL join multiple tables with/without data

I have no idea how to create an SQL statement to join 4 tables.
1) The 'Vendor Table will always match entries from each table on Vendor #
2) Each of the remaining 3 will match to each other by Vendor # & Seq #
3) Any combination of the 3 can have data (or not)
4) I don't want to select from the Vendor table unless I get a hit on at least one of the 3
VENDOR
Vendor # Name
-------- ----
1 Tom Smith
2 Bruce Lee
3 Seamus O’Leary
4 Jonathan Stewart
5 Benjamin Franklin
Month Range Selected
Vendor # Seq # MonthFrom MonthTo
-------- ----- --------- -------
1 1 3 6
1 2 7 9
3 2 5 6
Week Selected
Vendor # Seq # Week #
-------- ----- ------
1 1 3
3 1 4
4 1 1
Day Selected
Vendor # Seq # Day #
1 1 15
1 2 25
2 1 12
4 1 05
5 1 19
Desired Table (Joined)
Vendor# Name Seq# MonthFrom MonthTo Week# Day#
1 Tom Smith 1 3 6 3 15
1 Tom Smith 2 7 9 NULL 25
2 Bruce Lee 1 NULL NULL NULL 12
3 Seamus O’Leary 1 NULL NULL 4 NULL
3 Seamus O’Leary 2 5 6 NULL NULL
4 Jonathan Stewart 1 NULL NULL 1 05
5 Benjamin Franklin 1 NULL NULL NULL 19
The trick being that any of the 3 (not including 'Vendor') can or cannot have data and I only want a row returned if there is something from one or more of the 3.
Any Advice?
To join it on Vendor and Seq, we first need to have all possible combinations. Then we can filter the tables based on these combinations. I've ran the following in SQL server:
Setup
declare #Vendors table(id int, name varchar(20));
declare #MonthRangeSelected table (vendor int, seq int null, monthFrom int null, monthTo int null);
declare #WeekSelected table (vendor int, seq int null, week int null);
declare #DaySelected table (vendor int, seq int null, day int null);
insert into #Vendors
select 1, 'Tom Smith'
union all
select 2, 'Bruce Lee'
union all
select 3, 'Seamus O’Leary'
union all
select 4, 'Jonathan Stewart'
union all
select 5, 'Benjamin Franklin';
insert into #MonthRangeSelected
select 1, 1, 3, 6
union all
select 1, 2, 7, 9
union all
select 3, 2, 5, 6;
insert into #WeekSelected
select 1, 1, 3
union all
select 3, 1, 4
union all
select 4, 1, 1;
insert into #DaySelected
select 1, 1, 15
union all
select 1, 2, 25
union all
select 2, 1, 12
union all
select 4, 1, 05
union all
select 5, 1, 19;
Query
select v.Id, v.name, combinations.seq, MonthFrom, MonthTo, Week, Day
from #Vendors v
inner join (select m.vendor, m.seq
from #MonthRangeSelected m
union
select w.vendor, w.seq
from #WeekSelected w
union
select d.vendor, d.seq
from #DaySelected d) combinations
on combinations.vendor = v.id
left join #MonthRangeSelected m
on m.Vendor = combinations.vendor
and m.seq = combinations.seq
left join #WeekSelected w
on w.Vendor = combinations.vendor
and w.seq = combinations.seq
left join #DaySelected d
on d.Vendor = combinations.vendor
and d.seq = combinations.seq
where (MonthFrom is not null
or MonthTo is not null
or Week is not null
or Day is not null)
And this is the result:
Id name seq MonthFrom MonthTo Week Day
1 Tom Smith 1 3 6 3 15
1 Tom Smith 2 7 9 NULL 25
2 Bruce Lee 1 NULL NULL NULL 12
3 Seamus O’Leary 1 NULL NULL 4 NULL
3 Seamus O’Leary 2 5 6 NULL NULL
4 Jonathan Stewart 1 NULL NULL 1 5
5 Benjamin Franklin 1 NULL NULL NULL 19
This is more complicated than it sounds. According to the result, you do not want a cartesian product when there are multiple matches in a table. So, you need to take seqnum into account.
select v.Vendor, v.name, coalesce(m.seq, w.seq, d.seq) as Seq,
m.MonthFrom, m.MonthTo, w.Week, d.Day
from Vendors v left join
SMonthRangeSelected m
on v.Vendor = m.Vendor full join
WeekSelected w
on v.Vendor = w.Vendor and m.seq = w.seq full join
DaySelected d
on v.Vendor = d.Vendor and d.seq in (w.seq, m.seq)
where m.Vendor is not null or
w.Vendor is not null or
d.Vendor is not null;
Strange things can happen when using full join, particularly if you want any filtering. An alternative approach uses union all and group by:
select mwd.Vendor, v.name, mwd.seq,
max(MonthFrom) as MonthFrom, max(MonthTo) as monthTo,
max(Week) as week, max(Day) as day
from ((select m.Vendor, m.seq, m.MonthFrom, m.MonthTo, NULL as week, NULL as day
from month m
) union all
(select w.Vendor, w.seq, NULL as MonthFrom, NULL as MonthTo, w.week, NULL as day
from week
) union all
(select d.Vendor, d.seq, NULL as MonthFrom, NULL as MonthTo, NULL as week, d.day
from day d
)
) mwd join
Vendor v
on v.vendor = vmwd.vendor
group by mwd.Vendor, v.vname, mwd.seq;
Note that this version does not require the Vendor table.
You should left outer join to each of the 3 tables, and then include the following in your where clause:
(MonthFrom is not null or Week# is not null or Day# is not null)
it sounds like you can inner join to the Vendor table
I believe that this will do what you need:
SELECT
V.[Vendor#], -- I'll never understand why people insist on using names that require brackets
V.Name,
COALESCE(M.[Seq#], W.[Seq#], D.[Seq#]) AS [Seq#],
M.MonthFrom,
M.MonthTo,
W.[Week#],
D.[Day#]
FROM
Vendor V
LEFT OUTER JOIN MonthRange M ON M.[Vendor#] = V.[Vendor#]
LEFT OUTER JOIN Week W ON W.[Vendor#] = V.[Vendor#]
LEFT OUTER JOIN Day D ON D.[Vendor#] = V.[Vendor#]
WHERE
(
M.[Vendor#] IS NOT NULL OR
W.[Vendor#] IS NOT NULL OR
D.[Vendor#] IS NOT NULL
) AND
(M.[Seq#] = W.[Seq#] OR M.[Seq#] IS NULL OR W.[Seq#] IS NULL) AND
(M.[Seq#] = D.[Seq#] OR M.[Seq#] IS NULL OR D.[Seq#] IS NULL) AND
(D.[Seq#] = W.[Seq#] OR D.[Seq#] IS NULL OR W.[Seq#] IS NULL)
You need full outer joins on the three tables, so as to get all vendor and seq number combinations. Join these with vendor and you are done:
select vendorno, v.name, x.seqno, x.monthfrom, x.monthto, x.weekno, x.dayno
from vendor v
join
(
select vendorno, seqno, m.monthfrom, m.monthto, w.weekno, d.dayno
from monthsel m
full outer join weeksel w using (vendorno, seqno)
full outer join daysel d using (vendorno, seqno)
) x using(vendorno)
order by vendorno, x.seqno;
UPDATE: Without a USING clause the same query get slightly less readable (and thus slightly more error-prone):
select v.vendorno, v.name, x.seqno, x.monthfrom, x.monthto, x.weekno, x.dayno
from vendor v
join
(
select
coalesce(m.vendorno, w.vendorno, d.vendorno) as vendorno,
coalesce(m.seqno, w.seqno, d.seqno) as seqno,
m.monthfrom, m.monthto, w.weekno, d.dayno
from monthsel m
full outer join weeksel w on w.vendorno = m.vendorno and w.seqno = m.seqno
full outer join daysel d on d.vendorno in (m.vendorno, w.vendorno)
and d.seqno in (m.seqno, w.segno)
) x on x.vendorno = v.vendorno
order by v.vendorno, x.seqno;
(Hope I didn't mix things up here. It's easy to make copy & paste errors with such a query. So if it doesn't work properly, look out for typos.)

Inner join with one row of another table

**Table Employee**
Id Name
1 EmpName1
2 EmpName2
3 EmpName3
**Table EmpDeptHistory**
Id EmpId Dept Date
1 1 Housing 2015-03-02
2 2 Finance 2015-01-03
3 1 WareHouse 2015-05-02
4 2 Housing 2015-02-06
5 3 WareHouse 2015-02-02
6 1 Housing 2015-05-01
7 2 Finance 2015-01-02
8 2 Housing 2015-05-04
9 2 Finance 2015-05-02
10 1 WareHouse 2015-03-08
11 1 Housing 2015-02-20
I need find the recent dept with which every employee worked. Also I need to find for individual employee by passing EmpId
The following query returns only one employee and not all :(
SELECT e.id, edh.dept,edh.date
FROM Employee e
inner join (select top 1 eh.empid, eh.dept, eh.date
from EmpDeptHistory eh
order by eh.date desc) as edh
on e.id=edh.empid
yes, I understand the top 1 will give the emp id based on date, hence only one employee details is show. I am not sure how to get all the employee recent department.
select e.id,edh.dept,edh.date
from employee e
inner join EmpDeptHistory edh
on e.id = (Select eh.empid, eh.dept, eh.date
from EmpDeptHistory eh
where e.id=eh.empid
order by eh.date desc)
The above throws
The ORDER BY clause is invalid in views, inline functions, derived
tables, subqueries, and common table expressions, unless TOP,
OFFSET or FOR XML is also specified.
You can use CROSS APPLY to run the right-hand subquery once for each left-hand row:
SELECT e.id, edh.dept,edh.date
FROM Employee e cross apply ( select top 1 eh.empid, eh.dept, eh.date from
EmpDeptHistory eh where eh.empid = e.id order by eh.date desc) as edh
You can use a CTE and a ranking function like ROW_NUMBER:
WITH CTE AS
(
SELECT e.id, edh.dept, edh.date,
rn = ROW_NUMBER() OVER (PARTITION BY edh.EmpId ORDER BY edh.date DESC)
FROM Employee e inner join EmpDeptHistory edh
on e.id = edh.empid
)
SELECT id, dept, date
FROM CTE
WHERE rn = 1
DEMO
For the latest department for each employee, you can do it like so:
SELECT t1.*
FROM EmpDeptHistory t1 INNER JOIN
(
SELECT EmpId, MAX(Date) [Date]
FROM EmpDeptHistory
GROUP BY EmpId
) AS t2
ON t1.EmpId = t2.EmpId AND t1.Date = t2.Date
EmpId can be put into a where clause if needed.

Selecting records with most recent date for each 'order'

I have information in the format of the sample table below. Each file can have multiple grades, I need to select the most recent grade (based on completion date) for each file. If there is a file w/ the same completion dates, I would select the best grade (a being best and subsequent letters being a lesser grade). This seems easy, but for some reason having a brain fart
Sample Table:
ID_PK File_No Grade Completion_Date
1 Smith A 10/1/2010
2 Smith C 9/25/2010
3 Davis B 11/1/2010
4 Johnson D 12/5/2010
5 Johnson A 11/1/2010
6 Johnson C 10/1/2010
7 Miller X 9/1/2010
8 Miller F 12/1/2010
9 Miller D 10/1/2010
Ideal Results:
1 Smith A 10/1/2010
3 Davis B 11/1/2010
4 Johnson D 12/5/2010
8 Miller F 12/1/2010
uSING WINDOWING FUNCTION IS MORE EFFICIENT and also simpler as
with cte AS(
select '1' AS ID_no,'Smith' AS FILE_NO,'A' AS GRADE,
CAST('10/1/2010' AS DATE) AS CREATION_DATE
union all
select '2','Smith','C','9/25/2010'
union all
select '3','Davis','B','11/1/2010'
union all
select '4','Johnson','D','12/5/2010'
union all
select '5','Johnson','A','11/1/2010'
union all
select '6','Johnson','C','10/1/2010'
union all
select '7','Miller','X','9/1/2010'
union all
select '8','Miller','F','12/1/2010'
union all
select '9','Miller','D','10/1/2010')
SELECT X.ID_NO,X.FILE_NO,X.GRADE,X.CREATION_DATE FROM(
SELECT ID_NO,FILE_NO,GRADE,CREATION_DATE ,
ROW_NUMBER() OVER(PARTITION BY FILE_NO ORDER BY CREATION_DATE DESC,GRADE ASC ) AS RN
FROM CTE)AS X
WHERE X.RN=1
ORDER BY ID_NO
try this (untested):
select max_grade.*
from `Sample Table` st
inner join (
select File_No, max(Completion_Date) as Completion_Date
from `Sample Table`
group by File_No
) max_date on st.Completion_Date = max_date.CompletionDate
inner join (
select File_No, Completion_Date, max(Grade) as Grade
from `Sample Table`
group by File_No, Completion_Date
) max_grade on st.File_No = max_grade.File_No and st.Completion_Date = max_grade.Completion_Date
Note that you may need to modify the syntax and table name for your particular DB.
I created a table with your example data. I tested the following query against the table and everything seem to work correctly and matched the example results.
SELECT
ID_PK,
StudentGrade.File_No,
MIN(StudentGrade.Grade),
StudentGrade.Completion_Date
FROM
(
SELECT File_No, MAX(Completion_Date) Completion_Date
FROM StudentGrade
GROUP BY File_No
) Student
INNER JOIN StudentGrade ON
Student.File_No = StudentGrade.File_No
AND StudentGrade.Completion_Date = Student.Completion_Date
GROUP BY ID_PK, StudentGrade.File_No, StudentGrade.Completion_Date
ORDER BY ID_PK