122

I'm using LINQ on an IQueryable returned from NHibernate and I need to select the row with the maximum value(s) in a couple of fields.

I've simplified the bit that I'm sticking on. I need to select the one row from my table with the maximum value in one field.

var table = new Table { new Row(id: 1, status: 10), new Row(id: 2, status: 20) }

from u in table
group u by 1 into g
where u.Status == g.Max(u => u.Status)
select u

This is incorrect but I can't work out the right form.

BTW, what I'm actually trying to achieve is approximately this:

var clientAddress = this.repository.GetAll()
    .GroupBy(a => a)
    .SelectMany(
            g =>
            g.Where(
                a =>
                a.Reference == clientReference && 
                a.Status == ClientStatus.Live && 
                a.AddressReference == g.Max(x => x.AddressReference) && 
                a.StartDate == g.Max(x => x.StartDate)))
    .SingleOrDefault();

I started with the above lambda but I've been using LINQPad to try and work out the syntax for selecting the Max().

UPDATE

Removing the GroupBy was key.

var all = this.repository.GetAll();

var address = all
            .Where(
                a =>
                a.Reference == clientReference && 
                a.Status == ClientStatus.Live && 
                a.StartDate == all.Max(x => x.StartDate) &&
                a.AddressReference == all.Max(x => x.AddressReference))
            .SingleOrDefault();
Boggin
  • 3,251
  • 3
  • 33
  • 48
  • possible duplicate: http://stackoverflow.com/questions/1101841/linq-how-to-perform-max-on-a-property-of-all-objects-in-a-collection-and-ret – M.Babcock Feb 02 '12 at 15:29
  • @M.Babcock there was a good answer quite far down in that question: http://stackoverflow.com/a/6330485/444244 – Boggin Feb 02 '12 at 15:58
  • There are much better ones than that... – M.Babcock Feb 02 '12 at 15:59
  • Take a look at [the answer](http://stackoverflow.com/a/1101979/490018). – Sergey Vyacheslavovich Brunov Feb 02 '12 at 17:27
  • @Serge I agree that [morelinq](http://code.google.com/p/morelinq/) would be best but I'm afraid this project has impediments to adding new libraries. – Boggin Feb 02 '12 at 21:20
  • possible duplicate of [How to use LINQ to select object with minimum or maximum property value](http://stackoverflow.com/questions/914109/how-to-use-linq-to-select-object-with-minimum-or-maximum-property-value) – nawfal Jul 19 '14 at 18:44

7 Answers7

276

I don't see why you are grouping here.

Try this:

var maxValue = table.Max(x => x.Status)
var result = table.First(x => x.Status == maxValue);

An alternate approach that would iterate table only once would be this:

var result = table.OrderByDescending(x => x.Status).First();

This is helpful if table is an IEnumerable<T> that is not present in memory or that is calculated on the fly.

Daniel Hilgarth
  • 171,043
  • 40
  • 335
  • 443
  • 1
    I took out the grouping and found I could get it working: `from u in User_Accounts where u.Status == User_Accounts.Max(y => y.Status) select u` – Boggin Feb 02 '12 at 16:05
  • 1
    You can also nest the lambda syntax: `table.First(x => x.Status == table.Max(x => x.Status))` – Landon Poch Aug 21 '12 at 22:15
  • 17
    @LandonPoch: That's not a good idea, as this would calculate the maximum N times with N being the number of items in `table`. – Daniel Hilgarth Aug 22 '12 at 04:06
  • 3
    @Daniel Hilgarth: Good catch! That would in fact calculate the max per every row in the table. My bad. – Landon Poch Aug 27 '12 at 03:31
  • For the second approach, it may only iterate once, but it's using a sort, which will be O(n log n), right? The first way is just 2 O(n) operations. – General Grievance Feb 16 '23 at 17:02
  • Actually, my previous comment was not quite accurate: https://learn.microsoft.com/en-us/dotnet/core/compatibility/core-libraries/5.0/orderby-firstordefault-complexity-increase. .NET Core 1-3 made it so that calling `First` made it O(n), later it was revised so that it was O(n log n), but optimized to call the predicate fewer than `n` times if not needed, which apparently is better overall. This seems consistent with the results I've been getting from testing. – General Grievance Feb 16 '23 at 17:34
22

You can also do:

(from u in table
orderby u.Status descending
select u).Take(1);
Chris Walsh
  • 3,423
  • 2
  • 42
  • 62
KAPIL SHARMA
  • 609
  • 7
  • 4
13

You can group by status and select a row from the largest group:

table.GroupBy(r => r.Status).OrderByDescending(g => g.Key).First().First();

The first First() gets the first group (the set of rows with the largest status); the second First() gets the first row in that group.
If the status is always unqiue, you can replace the second First() with Single().

SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
7

Addressing the first question, if you need to take several rows grouped by certain criteria with the other column with max value you can do something like this:

var query =
    from u1 in table
    join u2 in (
        from u in table
        group u by u.GroupId into g
        select new { GroupId = g.Key, MaxStatus = g.Max(x => x.Status) }
    ) on new { u1.GroupId, u1.Status } equals new { u2.GroupId, Status = u2.MaxStatus}
    select u1;
Dmitry Komin
  • 549
  • 6
  • 7
2

What about using Aggregate?

It's better than

  1. Select max
  2. Select by max value

since it only scans the array once.

var maxRow = table.Aggregate(
  (a, b) => a.Status > b.Status ? a : b  // whatever you need to compare
);
General Grievance
  • 4,555
  • 31
  • 31
  • 45
PavelP
  • 119
  • 4
  • The question clearly states "LINQ on an IQueryable returned from NHibernate". That requires a method that can be translated into SQL. I.e. not `Aggregate`. – Gert Arnold Feb 17 '23 at 11:22
  • In other words, Aggregate is executed immediately, not deferred. It's executed in memory on the running code, not as a SQL command executed in the DB https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/classification-of-standard-query-operators-by-manner-of-execution – Lombas Mar 22 '23 at 13:17
0

More one example:

Follow:

 qryAux = (from q in qryAux where
            q.OrdSeq == (from pp in Sessao.Query<NameTable>() where pp.FieldPk
            == q.FieldPk select pp.OrdSeq).Max() select q);

Equals:

 select t.*   from nametable t  where t.OrdSeq =
        (select max(t2.OrdSeq) from nametable t2 where t2.FieldPk= t.FieldPk)
ALFA
  • 1,726
  • 1
  • 10
  • 19
SantanaFire
  • 101
  • 3
-1

Simply in one line:

var result = table.First(x => x.Status == table.Max(y => y.Status));

Notice that there are two action. the inner action is for finding the max value, the outer action is for get the desired object.

Shneor
  • 304
  • 3
  • 4
  • 1
    This method was discussed in the comments to the accepted answer where it was pointed out it was a bad idea. – Boggin Nov 13 '17 at 12:30
  • It is a bad idea because table.Max(y => y.Status) is going to be invoked once for each item in table, which is far from being optimal. – pablocom Feb 07 '21 at 09:53
  • Far-from optimal meaning O(n ^ 2), performing worse than the sorting solution and the double-scan solution. – General Grievance Feb 16 '23 at 17:06