LINQ usando Max () para selecionar uma única linha


95

Estou usando o LINQ em um IQueryable retornado do NHibernate e preciso selecionar a linha com o (s) valor (es) máximo (s) em alguns campos.

Simplifiquei a parte que estou insistindo. Preciso selecionar uma linha da minha tabela com o valor máximo em um campo.

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

Isso está incorreto, mas não consigo descobrir a forma correta.

BTW, o que estou tentando alcançar é aproximadamente isso:

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();

Comecei com o lambda acima, mas tenho usado o LINQPad para tentar descobrir a sintaxe para selecionar Max ().

ATUALIZAR

Remover o GroupBy foi fundamental.

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();


@ M.Babcock havia uma boa resposta bem abaixo nessa pergunta: stackoverflow.com/a/6330485/444244
Boggin

Há muito melhores do que isso ...
M.Babcock

Dê uma olhada na resposta .
Sergey Brunov

@Serge Eu concordo que morelinq seria o melhor, mas temo que este projeto tenha impedimentos para adicionar novas bibliotecas.
Boggin

Respostas:


230

Não vejo por que você está agrupando aqui.

Experimente isto:

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

Uma abordagem alternativa que iteraria tableapenas uma vez seria esta:

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

Isso é útil se tablefor um IEnumerable<T>que não está presente na memória ou que é calculado rapidamente.


1
Tirei o agrupamento e descobri que poderia fazê-lo funcionar: from u in User_Accounts where u.Status == User_Accounts.Max(y => y.Status) select u
Boggin

1
Você também pode aninhar a sintaxe lambda: table.First(x => x.Status == table.Max(x => x.Status))
Landon Poch de

13
@LandonPoch: Não é uma boa ideia, já que calcularia o máximo N vezes, sendo N o número de itens em table.
Daniel Hilgarth

2
@Daniel Hilgarth: Boa pegada! Na verdade, isso calcularia o máximo para cada linha da tabela. Foi mal.
Landon Poch

17

Você também pode fazer:

(from u in table
orderby u.Status descending
select u).Take(1);

13

Você pode agrupar por status e selecionar uma linha do maior grupo:

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

O primeiro First()obtém o primeiro grupo (o conjunto de linhas com o maior status); o segundo First()obtém a primeira linha desse grupo.
Se o status for sempre unqiue, você pode substituir o segundo First()por Single().


7

Respondendo à primeira questão, se você precisar pegar várias linhas agrupadas por certos critérios com a outra coluna com valor máximo, você pode fazer algo assim:

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;

0

Mais um exemplo:

Segue:

 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);

É igual a:

 select t.*   from nametable t  where t.OrdSeq =
        (select max(t2.OrdSeq) from nametable t2 where t2.FieldPk= t.FieldPk)

-1

Simplesmente em uma linha:

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

Observe que existem duas ações. a ação interna é para encontrar o valor máximo, a ação externa é para obter o objeto desejado.


Este método foi discutido nos comentários à resposta aceita, onde foi apontado que era uma má ideia.
Boggin
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.