Ограничение NULL подзапросов из набора результатов - PullRequest
1 голос
/ 17 февраля 2011

Простая настройка: мастер-таблица и связанная дочерняя таблица (один мастер, много дочерних). Допустим, мы хотим извлечь всех мастеров и их главные хронологические дочерние значения (обновлено, доступно и т. Д.). Запрос будет выглядеть так (например):

var masters = from m in Master
              let mc = m.Childs.Max(c => c.CreatedOn)
              select new { m, mc };

Потенциальная проблема возникает, если у master нет дочерних элементов, подзапрос выдаст NULL и преобразование из NULL в DateTime завершится неудачно с

InvalidOperationException: Нуль значение не может быть назначено члену с типом System.DateTime, который является необнуляемый тип значения.

Решением для исключения является приведение mc к DateTime?, но мне нужны мастера, у которых есть дети, и просто обходят немногие, у которых еще нет детей.

Решение № 1 Добавить where m.Childs.Count() > 0. Этот пнул меня сильно и неожиданно, сгенерированный SQL был просто ужасен (как и его план выполнения) и работал почти вдвое медленнее:

SELECT [t2].[Name] AS [MasterName], [t2].[value] AS [cm]
FROM (
    SELECT [t0].[id], [t0].[Name], (
        SELECT MAX([t1].[CreatedOn])
        FROM [Child] AS [t1]
        WHERE [t1].[masterId] = [t0].[id]
        ) AS [value]
    FROM [Master] AS [t0]
    ) AS [t2]
WHERE ((
    SELECT COUNT(*)
    FROM [Child] AS [t3]
    WHERE [t3].[masterId] = [t2].[id]
    )) > @p0

Решение # 2 с where mc != null еще хуже, оно дает более короткий скрипт, но выполняется намного дольше, чем предыдущий (занимает примерно столько же времени, что и два выше вместе взятых)

SELECT [t2].[Name] AS [MasterName], [t2].[value] AS [cm]
FROM (
    SELECT [t0].[id], [t0].[Name], (
        SELECT MAX([t1].[CreatedOn])
        FROM [Child] AS [t1]
        WHERE [t1].[masterId] = [t0].[id]
        ) AS [value]
    FROM [Master] AS [t0]
    ) AS [t2]
WHERE ([t2].[value]) IS NOT NULL

В целом много тратится впустую времени SQL на устранение нескольких строк из десятков или тысяч и более. Это привело меня к Solution # 3 , получить все и устранить пустые стороны клиента, но для этого мне пришлось поцеловать IQueryable до свидания:

var masters = from m in Master
              let mc = (DateTime?)m.Childs.Max(c => c.CreatedOn)
              select new { m, mc };
var mastersNotNull = masters.AsEnumerable().Where(m => m.mc != null);

и это работает, однако я пытаюсь выяснить, есть ли какие-либо недостатки в этом? Будет ли это вести себя в любом случае принципиально иначе, чем при полной монти IQueryable? Я полагаю, это также означает, что я не могу (или не должен) использовать мастера как фактор в другом IQueryable? Любой вклад / наблюдение / альтернатива приветствуется.

1 Ответ

0 голосов
/ 17 февраля 2011

Только на основании этого требования:

мастер-таблица и связанная дочерняя таблица (один мастер, много детей).Допустим, мы хотим извлечь всех мастеров и их старшие хронологические дочерние значения

SELECT [m].[Name] AS [MasterName]
    , Max([c].[value]) as [cm] 
FROM [Master] AS [m] 
left outer join [Child] as [c] on m.id = c.id
group by [m].[name]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...