Как вернуть родительские записи, только если есть дочерняя запись, с максимальной датой, попадающей в диапазон дат? - PullRequest
0 голосов
/ 24 апреля 2019

Мне нужно вернуть все заполненные родительские элементы из моей таблицы PARENTS, когда они были завершены в пределах диапазона дат.Все действия против родителя хранятся в дочерней таблице PARENT_ACTIONS. Трудность, с которой я столкнулся, заключается в том, что после завершенного действия может быть больше действий или даже несколько выполненных действий, сохраненных для этого PARENT_ID, и мне нужно только, чтобы родитель возвращался, еслиМаксимальное действие - это завершенное действие, и оно попадает в выбранный пользователем диапазон дат.

Я попытался выполнить поиск по форумам, но большая часть того, что я нашел, было «как найти максимальную дату дочерней записи для родительской записи», что было полезно в том, как сделать максимум, но неполезно для моей точной проблемы.

Я не помню всего, что я пробовал, но в настоящее время я пытаюсь этот код, который действительно возвращает правильные результаты (11 строк), это занимает всего 10 минут из-заужасно неэффективный SQL.

var parents = (from p in db.PARENTs
               where p.PARENT_ACTION
                        .Any(pa => pa.ACTION_ID == 99 
                                && pa.ACTION_DATE >= beginDate
                                && pa.ACTION_DATE <= endDate
                                && pa.ACTION_DATE == p.PARENT_ACTION.Max(pam => pam.ACTION_DATE))
              select p);

Я хотел бы написать что-то, что очень похоже на приведенный ниже SQL, который возвращает <1 секунду для 11 строк. </p>

select * from parent p
 where p.STATUS = 99
   and exists (select 'x' from parent_action pa
                where pa.PARENT_ID = p.ID
                  and pa.ACTION_ID = 99
                  and pa.ACTION_DATE = (select max(pam.action_date)
                                          from parent_action pam
                                         where pam.parent_id = p.id)
                  and pa.action_date between to_date('04/10/2019', 'MM/DD/YYYY') and to_date('04/23/2019', 'MM/DD/YYYY'));

***** Обновление 1 *****

Я использую System.Data.Entity для запроса базы данных Oracle 12c

Sample data:
STATUS
ID, DESC
1, ENTERED
2, SUBMITTED
99, COMPLETED

ACTIONS
ID, DESC
1, ENTER
2, SUBMIT
99, COMPLETE

PARENT
ID, STATUS
1, 99
2, 1
3, 99
4, 99

PARENT_ACTIONS
ID, PARENT_ID, ACTION_ID, ACTION_DATE
1, 1, 1, 04/01/2019
2, 1, 2, 04/05/2019
3, 1, 99, 04/11/2019
4, 2, 1, 04/11/2019
5, 3, 1, 04/15/2019
6, 3, 2, 04/16/2019
7, 3, 99, 04/17/2019
8, 3, 2, 04/18/2019 --Parent sent back to submitted status
9, 4, 1, 04/01/2019
10, 4, 2, 04/11/2019
11, 4, 99, 04/15/2019
12, 4, 99, 04/24/2019 --Completion details updated by customer, business rules require a new complete action be written

На основе этих примеров данных с диапазоном дат 4 /С 10/2019 по 23.04.2009 я хочу написать что-то, что будет возвращать ТОЛЬКО родительский идентификатор 1, потому что это единственный родитель с максимальной датой действия, равной 1) завершенному действию (99) и 2) между указаннымдиапазон дат.Я хочу, чтобы родитель 4 был исключен, поскольку максимальная дата действия выходит за пределы указанного диапазона дат.

***** Обновление 2 *****

Похоже, что я делаюиметь правильный код, чтобы вернуть желаемые результаты;Тем не менее, я думал, что делал что-то ужасно неправильно, так как выполнение запроса превысило 10 минут.После дальнейшего расследования выясняется, что я стал еще одной жертвой появления CAST AS в сгенерированном SQL, что резко снизило производительность.В моем случае это приведение моих столбцов идентификаторов в SQL как число (10,0), когда в моей таблице столбцы идентификаторов являются целочисленными, а в моей модели EF столбцы идентификаторов - Int32.Я не совсем уверен, как это исправить, или я должен просто написать свой собственный SQL для этой конкретной потребности данных.

Ответы [ 3 ]

0 голосов
/ 25 апреля 2019

Мне нужен только родитель, возвращенный, если максимальное действие является завершенным действием, и оно попадает в выбранный пользователем диапазон дат.

Итак, вам нужен родитель, если статус родительского объекта завершен (99) и последнее действие родителя этого родителя было завершено с датой между beginDate и endDate. Или точнее: последнее ParentAction этого Parent имеет ActionId Complete (99) и ActionDate между beginDate и endDate.

Всякий раз, когда вам нужен «Предмет с его подэлементами», например, «Школа с его учениками», «Клиент с его заказами», «Продукт с его ProductParts», рассмотрите возможность использования Queryable.GroupBy

var parentsThatCompletedTheirActionsInTime = dbContext.Parents
    .Where(parent => parent.Status == 99) // = completed
    .GroupJoin(dbContext.ParentActions,   // GroupJoin with the ParentActions
        parent => parent.Id,              // from every Parent take the primary key
    parentAction => parentAction.Id,      // from every ParentAction the foreign key

    // ResultSelector: take the parent and all its matching parentActions
    // to make one new object
    (parent, actionsOfThisParent) => new
    {
        Parent = parent,
        LastParentAction = actionsOfThisParent
           .Select(action => new
           {
               IsCompleted = action.ActionId == 99,
               ActionDate = action.ActionDate,
           })
           .OrderByDescending(action => action.ActionDate)               
           .FirstOrDefault(),
    })

    // Keep only those parents where the Last Action was completed in time
    .Where(joinResult => joinResult.LastParentAction.IsCompleted
        && joinResult.LastParentAction.ActionDate >= beginDate
        && joinResult.LastParentAction.ActionDate <= endDate)

    // finally: keep only the Parent:
    .Select(joinResult => joinResult.Parent);
0 голосов
/ 26 апреля 2019

Оказывается, код, который у меня изначально был, возвращает то, что я хочу, и это, по-видимому, относительно эффективный способ сделать это. Моя первоначальная причина задать этот вопрос состояла в том, чтобы проверить, был ли 1) мой код верным, чтобы вернуть то, что я хотел, и что еще более важно 2) Был ли более эффективный способ получения данных, которые я хотел, так как мой код занимал более 10 минут вернуться - я точно подумал, что, должно быть, я что-то делал не так.

Попробовав весь предоставленный замечательный код (большое спасибо Метени и Харальду) и все еще испытывая 10 + минутное время возврата для 10 строк, я наткнулся на некоторые сообщения, в которых говорится, что Linq to Entity может иметь ужасную производительность, когда генерируемый SQL содержит "CAST (FIELD AS TYPE)", который оказывается именно тем, что я испытывал.

Следующий код возвращает желаемые результаты для моего вопроса.

var parents = (from p in db.PARENTs
               where p.PARENT_ACTION
                        .Any(pa => pa.ACTION_ID == 99 
                                && pa.ACTION_DATE >= beginDate
                                && pa.ACTION_DATE <= endDate
                                && pa.ACTION_DATE == p.PARENT_ACTION.Max(pam => pam.ACTION_DATE))
              select p);

В дополнение к этому коду, чтобы удалить «CAST AS» из сгенерированного SQL, мне пришлось изменить свой файл .edmx в Блокноте и изменить тип всех столбцов Oracle INTEGER с «number» на «int» ,

Наконец, позвольте мне заявить, что я не совсем уверен, как лучше всего справиться с закрытием моего вопроса. Я не уверен, потому что мой исходный код в вопросе возвращает правильные результаты, код Харальда возвращает правильные результаты, а код Метени также возвращает правильные результаты (хотя у меня есть опасения, если существует большое количество родительских идентификаторов). Похоже также, что мой первоначальный вопрос не был вопросом, который мне нужно было задать, чтобы решить мою проблему, я просто не знал, в чем заключается настоящая проблема.

0 голосов
/ 24 апреля 2019

Отредактировано: упорядочить действия по дате в порядке убывания, затем выполнить первое (последнее) действие.Статус этого действия должен быть 99, а его дата должна находиться в требуемом диапазоне:

    var prs = (from p in parents
           where p.PARENT_ACTION
                .OrderByDescending(pa => pa.ACTION_DATE)
               .Take(1)
               .Any(pa => 
                    pa.ACTION_ID == 99
                    && pa.ACTION_DATE >= beginDate
                    && pa.ACTION_DATE <= endDate)
          select p);

Редактировать (по следующей ссылке этот код должен быть эквивалентен функциям ранжирования T-SQL, которые должны быть более эффективными: https://smehrozalam.wordpress.com/2009/12/29/linq-how-to-get-the-latest-last-record-with-a-group-by-clause/)

    var parentIds = new HashSet<int>(from a in PARENT_ACTIONS
            group a by a.PARENT_ID into grp
            let maxDate = grp.Max (g => g.ACTION_DATE)
            from p in grp
            where p.ACTION_DATE == maxDate 
                && p.ACTION_ID == 99
                && p.ACTION_DATE >= beginDate
                && p.ACTION_DATE <= endDate
            select p.PARENT_ID);
    var prs = parents.Where(p => parentIds.Contains(p.PARENT_ID));
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...