최대 또는 기본?


176

행을 반환하지 않을 수있는 LINQ 쿼리에서 Max 값을 얻는 가장 좋은 방법은 무엇입니까? 내가 방금하면

Dim x = (From y In context.MyTable _
         Where y.MyField = value _
         Select y.MyCounter).Max

쿼리가 행을 반환하지 않으면 오류가 발생합니다. 난 할 수 있습니다

Dim x = (From y In context.MyTable _
         Where y.MyField = value _
         Select y.MyCounter _
         Order By MyCounter Descending).FirstOrDefault

그러나 그것은 간단한 요청에 약간 둔감을 느낍니다. 더 좋은 방법이 누락 되었습니까?

업데이트 : 뒷이야기는 다음과 같습니다. 자식 테이블에서 다음 자격 카운터를 검색하려고합니다 (레거시 시스템, 시작하지 마십시오 ...). 각 환자의 첫 번째 자격 행은 항상 1이고 두 번째는 2 등입니다 (분명히 자식 테이블의 기본 키는 아님). 따라서 환자의 최대 기존 카운터 값을 선택한 다음 1을 추가하여 새 행을 만듭니다. 기존 자식 값이 없으면 0을 반환하는 쿼리가 필요합니다. 따라서 1을 추가하면 카운터 값이 1이됩니다. 레거시 앱이 카운터 값에 차이를 일으킬 수있는 경우 자식 행의 원시 수에 의존하고 싶지 않습니다. 질문을 너무 일반적으로 만들려고 노력했기 때문에 나쁘다.

답변:


206

이후 DefaultIfEmptySQL에 LINQ에서 구현되지 않습니다, 나는 그것이 반환 된 오류에서 검색을했고, 발견 된 매혹적인 기사 집계 함수에 널 (null) 세트를 다루고 그. 내가 찾은 것을 요약하기 위해 선택 내에서 nullable로 캐스팅 하여이 제한을 해결할 수 있습니다. 내 VB는 조금 녹슬었지만 다음과 같이 될 것이라고 생각 합니다.

Dim x = (From y In context.MyTable _
         Where y.MyField = value _
         Select CType(y.MyCounter, Integer?)).Max

또는 C #에서 :

var x = (from y in context.MyTable
         where y.MyField == value
         select (int?)y.MyCounter).Max();

1
VB를 수정하려면 Select는 "Select CType (y.MyCounter, Integer?)"입니다. 내 목적을 위해 Nothing을 0으로 변환하려면 원래 검사를 수행해야하지만 예외없이 결과를 얻는 것이 좋습니다.
gfrizzle

2
LINQ to SQL에서는 DefaultIfEmpty의 두 가지 오버로드 중 하나가 지원되며 매개 변수를 사용하지 않습니다.
DamienG

아마이 정보는 구식입니다. LINQ to SQL에서 DefaultIfEmpty의 두 가지 형식을 모두 성공적으로 테스트 했으므로
Neil

3
@ 닐 : 대답 해주세요. DefaultIfEmpty 나를 위해 작동하지 않습니다 내가 원하는 Max의를 DateTime. Max(x => (DateTime?)x.TimeStamp)아직도 유일한 방법 ..
duedl0r

1
DefaultIfEmpty가 이제 LINQ to SQL에서 구현되었지만 DefaultIfEmpty를 사용하면 합계되는 모든 값에 대해 행을 리턴하는 SQL 문 'SELECT MyCounter'가 생성 되는 반면이 응답은 MAX (MyCounter)를 리턴하므로이 응답은 더 나은 IMO로 유지 됩니다 . 단일, 합산 된 행. (EntityFrameworkCore 2.1.3에서 테스트.)
칼 샤먼에게

107

방금 비슷한 문제가 있었지만 쿼리 구문이 아닌 목록에서 LINQ 확장 메서드를 사용하고있었습니다. Nullable 트릭으로 캐스팅하면 다음과 같이 작동합니다.

int max = list.Max(i => (int?)i.MyCounter) ?? 0;

48

DefaultIfEmpty다음과 같은 경우처럼 들립니다 (예상치 않은 코드는 다음과 같습니다).

Dim x = (From y In context.MyTable _
         Where y.MyField = value _
         Select y.MyCounter).DefaultIfEmpty.Max

DefaultIfEmpty에 익숙하지 않지만 위 구문을 사용할 때 "SQL로 실행하기 위해 'OptionalValue'노드를 형식화 할 수 없습니다"라는 메시지가 표시됩니다. 또한 기본값 (0)을 제공하려고했지만 그중 하나도 마음에 들지 않았습니다.
gfrizzle

아 LINQ to SQL에서는 DefaultIfEmpty가 지원되지 않는 것 같습니다. .ToList를 사용하여 목록으로 먼저 캐스팅하면 문제를 해결할 수 있지만 성능이 크게 저하됩니다.
Jacob Proffitt

3
고마워, 이것은 내가 찾던 것입니다. 확장 방법 사용 :var colCount = RowsEnumerable.Select(row => row.Cols.Count).DefaultIfEmpty().Max()
Jani

35

당신이 요구하는 것을 생각하십시오!

{1, 2, 3, -1, -2, -3}의 최대 값은 분명히 3입니다. {2}의 최대 값은 분명히 2입니다. 그러나 빈 세트의 최대 값 {}은 무엇입니까? 분명히 그것은 무의미한 질문입니다. 빈 세트의 최대 값은 단순히 정의되지 않았습니다. 답을 얻으려고하는 것은 수학적 오류입니다. 세트의 최대 값 자체는 해당 세트의 요소 여야합니다. 빈 집합에는 요소가 없으므로 특정 집합이 해당 집합에 속하지 않고 해당 집합의 최대 값이라고 주장하면 수학적 모순입니다.

프로그래머가 0으로 나누기를 요청할 때 컴퓨터가 예외를 throw하는 것이 올바른 동작 인 것처럼, 프로그래머가 빈 세트의 최대 값을 가져 오도록 요청할 때 컴퓨터가 예외를 throw하는 것이 올바른 동작입니다. 빈 세트의 최대 값을 취하고, spacklerorke를 자극하고, 비행 유니콘을 Neverland로 타는 것은 의미가 없으며 불가능하며 정의되지 않습니다.

이제 실제로 하고 싶은 것은 무엇입니까?


좋은 요점-자세한 내용으로 질문을 곧 업데이트하겠습니다. 선택할 레코드가 없을 때 0을 원한다는 것을 알고 있으면 충분합니다. 결과적으로 최종 솔루션에 영향을 미칩니다.
gfrizzle

17
나는 종종 유니콘을 네버 랜드로 날 리려고 노력하며, 나의 노력이 의미가없고 정의되지 않았다는 당신의 제안에 위배됩니다.
Chris Shouts

2
나는이 주장이 옳다고 생각하지 않는다. linq-to-sql이 명확하고 SQL에서 0보다 큰 행은 null로 정의됩니다.
duedl0r

4
Linq는 일반적으로 쿼리가 개체에 대해 메모리 내에서 실행되는지 또는 쿼리가 데이터베이스의 행에서 실행되는지에 관계없이 동일한 결과를 생성해야합니다. Linq 쿼리는 Linq 쿼리이므로 사용중인 어댑터에 관계없이 충실하게 실행해야합니다.
yfeldblum

1
Linq 결과가 메모리에서 실행 되든 SQL에서 실행 되든 동일해야한다는 이론에 동의하지만 실제로 조금 더 깊이 파고 들었을 때 이것이 항상 그렇게 할 수없는 이유를 알게됩니다. Linq 표현식은 복잡한 표현식 변환을 사용하여 SQL로 변환됩니다. 간단한 일대일 번역이 아닙니다. 한 가지 차이점은 널 (null)의 경우입니다. C #에서 "null == null"은 true입니다. SQL에서 "null == null"일치는 외부 조인에는 포함되지만 내부 조인에는 포함되지 않습니다. 그러나 내부 조인은 거의 항상 원하는 것이므로 기본값입니다. 이로 인해 동작에 차이가 생길 수 있습니다.
커티스 얄롭

25

항상 Double.MinValue시퀀스에 추가 할 수 있습니다 . 이렇게하면 최소한 하나의 요소 Max가 있으며 실제로 최소 인 경우에만 반환합니다. 더 효율적입니다 (옵션을 결정하기 위해 Concat, FirstOrDefault또는 Take(1)), 당신은 적절한 벤치마킹을 수행해야합니다.

double x = context.MyTable
    .Where(y => y.MyField == value)
    .Select(y => y.MyCounter)
    .Concat(new double[]{Double.MinValue})
    .Max();

10
int max = list.Any() ? list.Max(i => i.MyCounter) : 0;

목록에 요소가없는 경우 (예 : 비어 있지 않은 경우) MyCounter 필드의 최대 값을 사용하고 그렇지 않으면 0을 반환합니다.


3
이 쿼리를 2 회 실행하지 않습니까?
andreapier

10

.Net 3.5부터 DefaultIfEmpty ()를 사용하여 기본값을 인수로 전달할 수 있습니다. 다음 방법 중 하나와 같은 것 :

int max = (from e in context.Table where e.Year == year select e.RecordNumber).DefaultIfEmpty(0).Max();
DateTime maxDate = (from e in context.Table where e.Year == year select e.StartDate ?? DateTime.MinValue).DefaultIfEmpty(DateTime.MinValue).Max();

첫 번째 열은 NOT NULL 열을 쿼리 할 때 허용되며 두 번째 열은 NULLABLE 열을 쿼리하는 데 사용되는 방식입니다. 인수없이 DefaultIfEmpty ()를 사용하는 경우 기본값 표 에서 볼 수 있듯이 기본값 은 출력 유형에 정의 된 입니다.

결과 SELECT는 그렇게 우아하지는 않지만 수용 가능합니다.

도움이 되길 바랍니다.


7

쿼리에 결과가 없을 때 문제가 발생한다고 생각합니다. 예외적 인 경우 try / catch 블록으로 쿼리를 래핑하고 표준 쿼리가 생성하는 예외를 처리합니다. 쿼리에서 결과를 반환하지 않아도되는 경우 결과가 원하는 결과를 파악해야합니다. @David의 대답 일 수도 있습니다 (또는 비슷한 것이 작동 할 것입니다). 즉, MAX가 항상 양수이면 알려진 "나쁜"값을 목록에 삽입하면 결과가없는 경우에만 선택됩니다. 일반적으로 최대 값을 검색하는 쿼리에서 일부 데이터를 처리 할 것으로 예상하고 try / catch 경로를 사용합니다. 그렇지 않으면 얻은 값이 올바른지 여부를 항상 확인해야합니다. 나는'

Try
   Dim x = (From y In context.MyTable _
            Where y.MyField = value _
            Select y.MyCounter).Max
   ... continue working with x ...
Catch ex As SqlException
       ... do error processing ...
End Try

내 경우에는 행을 반환하지 않는 것이 빈번하지 않은 것보다 더 자주 발생합니다 (레거시 시스템, 환자는 이전 자격이 있거나 없을 수 있음). 이것이 더 예외적 인 경우라면 아마도이 길을 갈 것입니다 (그리고 여전히 더 잘 보이지 않을 수도 있습니다).
gfrizzle

6

또 다른 가능성은 원시 SQL에서 접근하는 방법과 유사한 그룹화입니다.

from y in context.MyTable
group y.MyCounter by y.MyField into GrpByMyField
where GrpByMyField.Key == value
select GrpByMyField.Max()

VB LINQ 플레이버로 전환하는 것은 LINQPad에서 다시 테스트하는 것만으로도 그룹화 절에 구문 오류가 발생합니다. 나는 개념적 등가물을 쉽게 찾을 수 있다고 확신하지만 VB에 그것을 반영하는 방법을 모른다.

생성 된 SQL은 다음과 같은 라인에 있습니다.

SELECT [t1].[MaxValue]
FROM (
    SELECT MAX([t0].[MyCounter) AS [MaxValue], [t0].[MyField]
    FROM [MyTable] AS [t0]
    GROUP BY [t0].[MyField]
    ) AS [t1]
WHERE [t1].[MyField] = @p0

쿼리 실행이 모든 행을 검색 한 다음 검색된 집합에서 일치하는 행을 선택하는 것처럼 중첩 된 SELECT는 이상하게 보입니다. 질문은 SQL Server가 쿼리를 where SELECT를 내부 SELECT에 적용하는 것과 비슷한 것으로 최적화하는지 여부입니다. 나는 지금 그것을 조사하고 있습니다 ...

SQL Server의 실행 계획을 해석하는 데 정통하지는 않지만 WHERE 절이 외부 SELECT에있을 때 해당 단계의 실제 행 수는 테이블의 모든 행과 일치하는 행만 WHERE 절이 내부 SELECT에있을 때 즉, 모든 행을 고려할 때 1 %의 비용만이 다음 단계로 전환 된 것처럼 보이며 SQL Server에서 한 행만 다시 돌아 오므로 그랜드 사물의 구성표에서 그다지 큰 차이가 없을 것입니다 .


6

늦었지만 나는 같은 걱정을했다 ...

원래 게시물에서 코드를 표현하면 다음과 같이 정의 된 최대 세트 S를 원합니다.

(From y In context.MyTable _
 Where y.MyField = value _
 Select y.MyCounter)

마지막 코멘트를 고려

선택할 레코드가 없을 때 0을 원한다는 것을 알고 있으면 충분합니다. 결과적으로 솔루션에 영향을 미칩니다.

다음과 같이 문제를 다시 말할 수 있습니다. 최대 {0 + S}를 원합니다. 그리고 concat이있는 제안 된 솔루션이 의미 적으로 올바른 것 같습니다 :-)

var max = new[]{0}
          .Concat((From y In context.MyTable _
                   Where y.MyField = value _
                   Select y.MyCounter))
          .Max();

3

왜 더 직접적이지 않습니까?

Dim x = context.MyTable.Max(Function(DataItem) DataItem.MyField = Value)

1

주목할만한 흥미로운 차이점 중 하나는 FirstOrDefault와 Take (1)이 동일한 SQL을 생성하지만 (LINQPad에 따라), FirstOrDefault는 일치하는 행이없고 Take (1)이 반환 할 때 기본값을 반환합니다. 적어도 LINQPad에서는 결과가 없습니다.


1

Linq를 사용하여 엔터티를 사용하고 있음을 알리기 위해 위의 방법은 작동하지 않습니다 ...

당신이 같은 것을하려고하면

var max = new[]{0}
      .Concat((From y In context.MyTable _
               Where y.MyField = value _
               Select y.MyCounter))
      .Max();

예외가 발생합니다.

System.NotSupportedException : LINQ to Entities에서 LINQ 표현식 노드 유형 'NewArrayInit'이 지원되지 않습니다.

그냥하는 것이 좋습니다

(From y In context.MyTable _
                   Where y.MyField = value _
                   Select y.MyCounter))
          .OrderByDescending(x=>x).FirstOrDefault());

그리고이 FirstOrDefault목록이 비어있는 경우 0을 반환합니다.


주문은 큰 데이터 세트에서 심각한 성능 저하를 초래할 수 있습니다. 최대 값을 찾는 것은 매우 비효율적 인 방법입니다.
피터 브루 인스

1
decimal Max = (decimal?)(context.MyTable.Select(e => e.MyCounter).Max()) ?? 0;

1

MaxOrDefault확장 방법을 중단했습니다 . 그다지 많지는 않지만 Intellisense에서의 존재 Max는 빈 시퀀스에서 예외가 발생할 것임을 알려주는 유용한 알림입니다 . 또한이 방법을 사용하면 필요한 경우 기본값을 지정할 수 있습니다.

    public static TResult MaxOrDefault<TSource, TResult>(this 
    IQueryable<TSource> source, Expression<Func<TSource, TResult?>> selector,
    TResult defaultValue = default (TResult)) where TResult : struct
    {
        return source.Max(selector) ?? defaultValue;
    }

0

Entity Framework 및 Linq to SQL의 경우 Expression전달 된 메소드를 수정하는 확장 메소드를 정의하여이를 달성 할 수 있습니다 IQueryable<T>.Max(...).

static class Extensions
{
    public static TResult MaxOrDefault<T, TResult>(this IQueryable<T> source, 
                                                   Expression<Func<T, TResult>> selector)
        where TResult : struct
    {
        UnaryExpression castedBody = Expression.Convert(selector.Body, typeof(TResult?));
        Expression<Func<T, TResult?>> lambda = Expression.Lambda<Func<T,TResult?>>(castedBody, selector.Parameters);
        return source.Max(lambda) ?? default(TResult);
    }
}

용법:

int maxId = dbContextInstance.Employees.MaxOrDefault(employee => employee.Id);
// maxId is equal to 0 if there is no records in Employees table

생성 된 쿼리는 동일하며 IQueryable<T>.Max(...)메서드에 대한 일반 호출과 동일하게 작동 하지만 레코드가 없으면 실행을 throw하는 대신 T 유형의 기본값을 반환합니다.


-1

방금 비슷한 문제가 있었지만 단위 테스트는 Max ()를 사용하여 통과했지만 라이브 데이터베이스에 대해 실행할 때 실패했습니다.

내 솔루션은 수행중인 논리와 쿼리를 분리하는 것이 아니라 하나의 쿼리로 조인하지 않았습니다.
라이브 환경에서 실행할 때 Linq-objects (Linq-objects Max ()는 null과 함께 작동 함) 및 Linq-sql을 사용하여 단위 테스트에서 작동하는 솔루션이 필요했습니다.

(테스트에서 Select ()를 조롱합니다)

var requiredDataQuery = _dataRepo.Select(x => new { x.NullableDate1, .NullableDate2 }); 
var requiredData.ToList();
var maxDate1 = dates.Max(x => x.NullableDate1);
var maxDate2 = dates.Max(x => x.NullableDate2);

덜 효율적입니까? 아마.

다음에 내 앱이 넘어지지 않는 한 관심이 있습니까? 아니.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.