Макс или по умолчанию? - PullRequest
       59

Макс или по умолчанию?

166 голосов
/ 04 декабря 2008

Каков наилучший способ получить значение Max из запроса LINQ, который может не возвращать строки? Если я просто сделаю

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). Обратите внимание, что я не хочу полагаться на необработанное количество дочерних строк в случае, если унаследованное приложение вводит пробелы в значениях счетчика (возможно). Моя плохая попытка сделать вопрос слишком общим.

Ответы [ 17 ]

202 голосов
/ 05 декабря 2008

Поскольку DefaultIfEmpty не реализовано в LINQ to SQL, я провел поиск по возвращаемой ошибке и нашел замечательную статью , которая имеет дело с нулевыми наборами в агрегатных функциях. Подводя итог тому, что я нашел, вы можете обойти это ограничение, применив к своему выбору значение 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();
95 голосов
/ 07 апреля 2010

У меня просто была похожая проблема, но я использовал в списке методы расширения LINQ, а не синтаксис запроса. Там также работает приведение к трюку Nullable:

int max = list.Max(i => (int?)i.MyCounter) ?? 0;
47 голосов
/ 04 декабря 2008

Звучит как случай для DefaultIfEmpty (непроверенный код следует):

Dim x = (From y In context.MyTable _
         Where y.MyField = value _
         Select y.MyCounter).DefaultIfEmpty.Max
35 голосов
/ 05 декабря 2008

Подумайте, о чем вы спрашиваете!

Максимум {1, 2, 3, -1, -2, -3}, очевидно, равен 3. Максимум {2}, очевидно, равен 2. Но каков максимум пустого множества {}? Очевидно, это бессмысленный вопрос. Максимум пустого набора просто не определен. Попытка получить ответ - математическая ошибка. Максимум любого набора сам должен быть элементом этого набора. Пустое множество не имеет элементов, поэтому утверждение о том, что какое-то конкретное число является максимумом этого множества, не входя в него, является математическим противоречием.

Так же, как правильное поведение компьютера, чтобы генерировать исключение, когда программист просит его делить на ноль, так и для компьютера правильное поведение, чтобы генерировать исключение, когда программист просит его взять максимум пустого задавать. Деление на ноль, взятие максимума пустого сета, вигер spacklerorke и езда летающего единорога в Neverland - все это бессмысленно, невозможно, неопределенно.

Теперь, что вы на самом деле хотите сделать?

24 голосов
/ 04 декабря 2008

Вы всегда можете добавить 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 голосов
/ 07 января 2015

Начиная с .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 не будет таким элегантным, но приемлемым.

Надеюсь, это поможет.

9 голосов
/ 07 февраля 2013
int max = list.Any() ? list.Max(i => i.MyCounter) : 0;

Если в списке есть какие-либо элементы (т. Е. Не пустые), он займет максимум поля MyCounter, иначе вернет 0.

7 голосов
/ 04 декабря 2008

Я думаю, проблема в том, что вы хотите, чтобы происходило, когда запрос не дал результатов. Если это исключительный случай, я бы обернул запрос в блок try / catch и обработал исключение, которое генерирует стандартный запрос. Если нормально, чтобы запрос не возвращал результатов, вам нужно выяснить, каким должен быть результат в этом случае. Это может быть ответ @ Дэвида (или что-то подобное будет работать). То есть, если 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
6 голосов
/ 29 июля 2009

поздно, но у меня была такая же проблема ...

Перефразируя ваш код из исходного поста, вы хотите, чтобы максимум набора 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();
6 голосов
/ 04 декабря 2008

Другая возможность - группировка, похожая на то, как вы можете подходить к ней в сыром SQL:

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

Единственное (при повторном тестировании в LINQPad) переключение на разновидность VB LINQ дает синтаксические ошибки в предложении группировки. Я уверен, что концептуальный эквивалент достаточно легко найти, я просто не знаю, как отразить его в 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 к внутренний ВЫБРАТЬ. Я смотрю на это сейчас ...

Я не очень разбираюсь в интерпретации планов выполнения в SQL Server, но похоже, что когда предложение WHERE находится во внешнем SELECT, число фактических строк, приводящих к этому шагу, равно всем строкам таблицы, а не только соответствующие строки, когда предложение WHERE находится во внутреннем SELECT. Тем не менее, похоже, что только 1% затрат переносится на следующий шаг, когда рассматриваются все строки, и в любом случае с SQL Server возвращается только одна строка, так что, возможно, это не так уж много различий в общей схеме вещей .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...