Search code examples
sqlsql-serverdatabaset-sqlrdbms

How to get a row number or ID of where the MAX() value was found


Good day community. I'm having a hard time trying to figure out a way to achieve the results I try to get. As im not very skilled with SQL queries, I start to lose my mind. What I'm trying to do is to find the highest and lowest grade on a particular test, but I also wish to get the ID or the row number (they are matching) of the rows where the MAX() and MIN() were found.

The table "Results" looks like this:

 ResultID|Test_UK|Test_US|TestUK_Scr|TestUS_Scr|TestTakenOn
   1         1       3      85          14     2018-11-22 00:00:00.000
   2         3       1      41          94     2018-11-23 00:00:00.000
   3         2       4      71          54     2018-11-24 00:00:00.000
   4         4       2      51          52     2018-12-25 00:00:00.000
   5         6       3      74          69     2018-12-01 00:00:00.000
   6         3       6      83          57     2018-12-02 00:00:00.000
   7         7       4      91          98     2018-12-03 00:00:00.000
   8         4       7      88          22     2018-12-04 00:00:00.000
   9         5       8      41          76     2018-12-08 00:00:00.000
   10        8       5      37          64     2018-12-09 00:00:00.000

The results I get when I run my query...

TestID|TopScore|LowScore|LastDateTestTaken
   1      94       85    2018-11-23 00:00:00.000
   2      71       52    2018-11-25 00:00:00.000
   3      83       14    2018-12-02 00:00:00.000
   4      98       51    2018-12-04 00:00:00.000
   5      64       41    2018-12-09 00:00:00.000
   6      74       57    2018-12-02 00:00:00.000
   7      91       22    2018-12-04 00:00:00.000
   8      76       37    2018-12-09 00:00:00.000

This is the queries I'm working on.

This query returns the results mentioned above

WITH
-- Combine the results of UK and US tests
    Combined_Results_Both_Tests AS(
                select ResultID as resultID, Test_UK as TestID, Test_UK_Scr as TestScore, TestTakenOn as TestDate from Results
                union all 
                select ResultID as resultID, Test_US as TestID, Test_US_Scr as TestScore, TestTakenOn as TestDate from Results),
--Gets TOP and WORST results of the tests, LastDateTaken (Needs to add ResultID!)
    Get_Best_and_Worst_Results_And_LastTestDate AS(
                SELECT TestID ,max(TestScore) AS TopScore ,min(TestScore) AS LowScore ,max(TestDate) AS LastDateTestTaken
                FROM Combined_Results_Both_Tests
                GROUP BY TestID)
--Final query execution
SELECT * FROM Get_Best_and_Worst_Results_And_LastTestDate

I've tried to achieve my desired results with something like this, which doesn't work and is also very inefficient. What I mean that it doesn't work, it is filled with dublicates, whenever the match is found on US and UK tests.

--Gets ReslutID of Min and Max values
    Get_ResultID_Of_Results AS(
                SELECT * FROM Get_Best_and_Worst_Results_And_LastTestDate A
                CROSS APPLY
                (SELECT ResultID FROM Results res
                WHERE   (A.TestID = res.Test_UK AND A.TopScore = res.Test_UK_Scr) OR
                        (A.TestID = res.Test_US AND A.TopScore = res.Test_UK_Scr) OR
                        (A.TestID = res.Test_UK AND A.LowScore = res.Test_UK_Scr) OR
                        (A.TestID = res.Test_US AND A.LowScore = res.Test_UK_Scr) OR
                        (A.TestID = res.Test_UK AND A.TopScore = res.Test_US_Scr) OR
                        (A.TestID = res.Test_US AND A.TopScore = res.Test_US_Scr) OR
                        (A.TestID = res.Test_UK AND A.LowScore = res.Test_US_Scr) OR
                        (A.TestID = res.Test_US AND A.LowScore = res.Test_US_Scr)) D)
SELECT * FROM Get_ResultID_Of_Results

This is the results I'm trying to achieve (extra columns that would state where Max value and Min value was found) that would state the ResultID from Results table. Also, the row numbers match the ResultIDs in the table.

TestID|TopScore|LowScore|LastDateTestTaken    |MaxValueLocID|MinValueLocID|
   1      94       85    2018-11-23 00:00:00.000       2             1
   2      71       52    2018-11-25 00:00:00.000       3             4
   3      83       14    2018-12-02 00:00:00.000       6             1
   4      98       51    2018-12-04 00:00:00.000       7             4
   5      64       41    2018-12-09 00:00:00.000       10            9
   6      74       57    2018-12-02 00:00:00.000       5             6
   7      91       22    2018-12-04 00:00:00.000       7             8
   8      76       37    2018-12-09 00:00:00.000       9             10

Asking for any help with the solution, theoretical or even practical. Thank you!


Solution

  • If I follow correctly, you want to unpivot the data and aggregate:

    select v.testid, max(v.score), min(v.score) max(v.TestTakenOn)
    from results r cross apply
         (values (Test_UK, TestUK_Scr, TestTakenOn),
                 (Test_US, TestUS_Scr, TestTakenOn)
         ) v(testid, score, TestTakenOn)
    group by v.testid;
    

    Then you can modify this using window functions:

    select v.testid, max(v.score), min(v.score) max(v.TestTakenOn),
           max(case when seqnum_desc = 1 then resultid end) as resultid_max,
           max(case when seqnum_asc = 1 then resultid end) as resultid_min
    from (select r.resultid, v.*,
                 row_number() over (partition by v.testid order by v.score asc) as seqnum_asc,
                 row_number() over (partition by v.testid order by v.score desc) as seqnum_desc
          from results r cross apply
               (values (Test_UK, TestUK_Scr, TestTakenOn),
                       (Test_US, TestUS_Scr, TestTakenOn)
               ) v(testid, score, TestTakenOn)
         ) v
    group by v.testid;