Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SQL Rank in LINQ to Entities

Tags:

c#

linq

I have a SQL query that ranks participants by multiple fields. I need to convert this to LINQ, which I understand doesn't have a rank function. Can someone help converting this?

If it helps, here's the what it does. This query pulls participants from a standings table and ranks them based on the fields that are listed RANK() OVER (ORDER BY W desc, L asc, RW asc, RL desc, HP desc, TB desc) AS RANK. Next I grab only those ranked 1 or 2 Where q1.RANK in ('1','2') and see if there are any ties for those two rankings Having count(q1.ParticipantID) > 1

Select q1.RANK, count(q1.ParticipantID) as 'Count'
From (
        Select Distinct ParticipantID, RANK() OVER (ORDER BY W desc, L asc, RW asc, RL desc, HP desc, TB desc) AS RANK
        From vGroupStandings
        Where CompetitionID = 8
        and GroupNumber = 1
        and EventID = 6
     ) as q1
Where q1.RANK in ('1','2')
Group By q1.RANK
Having count(q1.ParticipantID) > 1

UPDATE

Here's the data selecting all fields

enter image description here

Here's an example of what the filtered data in the subquery looks like. From that set, I look to see if there is more than 1 record ranked at rank 1 or rank 2.

enter image description here

RESPONSE

Thanks for the replies so far, I'll let you know when I can try these out. Here's another question. Would it be better to call a stored procedure from a controller instead? That way I can leave the SQL query as it is. I have a number of large queries that I'm going to have to run that involve rank. I wonder if it would be easier than rewriting everything in LINQ.

like image 732
madvora Avatar asked May 05 '15 18:05

madvora


2 Answers

This is pretty ugly, but works for me using this sample class.

public class Participant
{
    public int Id { get; set; }
    public int Score1 { get; set; }
    public int Score2 { get; set; }
    public int ExpectedRank { get; set; }
}

On this collection:

var participants = new Participant[] {
    new Participant { Id = 1, Score1 = 2, Score2 = 5, ExpectedRank = 6 },
    new Participant { Id = 2, Score1 = 10, Score2 = 8, ExpectedRank = 1 },
    new Participant { Id = 3, Score1 = 7, Score2 = 2, ExpectedRank = 4 },
    new Participant { Id = 4, Score1 = 7, Score2 = 4, ExpectedRank = 3 },
    new Participant { Id = 5, Score1 = 7, Score2 = 2, ExpectedRank = 4 },
    new Participant { Id = 6, Score1 = 7, Score2 = 7, ExpectedRank = 2 },
};

By doing the following rather ugly LINQ query:

var ranked = participants
    .OrderByDescending(p => p.Score1)
    .ThenByDescending(p => p.Score2)
    .Select((p, i) => new { Order = 1 + i, Participant = p })
    .GroupBy(p => new { p.Participant.Score1, p.Participant.Score2 })
    .SelectMany(g => g.Select(p => new {
        Id = p.Participant.Id,
        Rank = g.Min(x => x.Order),
        ExpectedRank = p.Participant.ExpectedRank
    }));

foreach (var p in ranked)
    Console.WriteLine(p);

Which produces the following output:

{ Id = 2, Rank = 1, ExpectedRank = 1 }
{ Id = 6, Rank = 2, ExpectedRank = 2 }
{ Id = 4, Rank = 3, ExpectedRank = 3 }
{ Id = 3, Rank = 4, ExpectedRank = 4 }
{ Id = 5, Rank = 4, ExpectedRank = 4 }
{ Id = 1, Rank = 6, ExpectedRank = 6 }
like image 171
Alex Avatar answered Sep 29 '22 13:09

Alex


Something like this... this is pseudo code .... I did not test.

If you gave any example data and expected output then I would do so

var inner = datasource
              .OrderByDesc(x => x.W)
              .ThenBy(x => x.L)
               // etc for all orders you need
              .GroupBy(new { W = W, L = L, RW = RW, RL = RL, HP = HP, TB = TB })
              .First(2);

if (inner[0].Count > 1 || inner[1].Count[1] > 1)
{
   Console.Writeline("1 "+inner[0].Count.ToString());
   Console.Writeline("2 "+inner[1].Count.ToString());
}
like image 28
Hogan Avatar answered Sep 29 '22 13:09

Hogan