Почему в GroupBy обнуляются суммируемые поля?

Помогите пожалуйста разобраться с моим кодом. На первый взгляд всё в нем просто. Но не работает так как ожидается. И у меня уже кончились идеи, что еще попробовать.

Делаю отчет по расчетам с плательщиками. Данные выбираются языком запросов. Не суть важно что это за язык, достаточно сказать, что почти полностью похож на стандартный SQL. Выбираемые данные состоят из четырех частей: оплаты, корректировки, квитирование, начисления. Части объединяются через UNION ALL. Когда убираю из запроса оплаты, квитирование, начисления, оставляю только корректировки - отчет работает как надо, то есть выводит корректировки такими, какими они есть в базе данных. Когда же гоняю запрос полный, то есть к корректировкам добавляются оплаты, квитирование, начисления - корректировки куда-то пропадают из конечного результата. Почему - в этом весь вопрос. Вроде бы никак они не могут пропасть, потому что в остальных подзапросах - по оплатам, квитированию, начислениям, - в колонки корректировок явным образом прописаны нули, поэтому они ни при каких дальнейших преобразованиях данных не могут изменить значения корректировок. Тем не менее изменяют.

Удалось выяснить, что корректировки пропадают на этапе последующей обработки выбранных данных, а именно на этапе группировки посредством GroupBy. Но как выяснить, почему это происходит - не знаю как выяснить. Прошу помочь с идеями.

Код запроса без оплат, квитирования, начислений - только корректировки:

/*Стандартные параметры*/
DECLARE @DateFrom AS [Parus.Business.Date];
DECLARE @DateTo AS [Parus.Business.Date];
DECLARE @ServOrg AS [Parus.Business.ServedOrganization];

/*Параметры-массивы*/
--DECLARE @Groups AS ArrayOf([ParusYug.Business.Payroll.Group]);
--DECLARE @GroupTypes AS ArrayOf([ParusYug.Business.Payroll.GroupType]);
--DECLARE @ServiceAndCompensation AS ArrayOf([ParusYug.Business.ParentsPayments.ServiceAndCompensation]);
--DECLARE @Counteragent AS ArrayOf([Parus.Business.Counteragent]);
--DECLARE @ServiceRecipient AS ArrayOf([ParusYug.Business.Payroll.ServiceRecipient]);


SELECT 
Main.CSD,
Main.Payer AS Counteragent,
 Main.[Group],
 Main.ProvidedService AS ServiceProvide,
 Main.ServiceRecipient,
 Main.Service,
 Main.Schedule,
 Main.PaymentFrom,
 Main.ChargedFrom,
 Main.ChargedFrom - Main.PaymentFrom AS SaldoFrom,
 Main.ChargedTo,
 Main.PaymentTo,
 Main.CorrPere,
 Main.CorrVozvr,
 Main.ChargedTo - Main.PaymentTo AS SaldoTo,
 Main.PaymentFrom + Main.PaymentTo AS PaymentFromTo,
 Main.ChargedFrom + Main.ChargedTo AS ChargedFromTo,
 (Main.ChargedFrom - Main.PaymentFrom) + (Main.ChargedTo - Main.PaymentTo) AS SaldoFromTo,
 Main.ChargingPayer,
 Main.Payment,
 Main.ChargeConfirm,
Main.PaymentDate,
Main.Src
FROM
(
SELECT
Base.CSD,
Base.PaymentDate,
Base.Src,
Base.Payer,
   Base.[Group],
   Base.ProvidedService,
   Base.ProvidedService.BC,
   Base.ServiceRecipient,
   Base.Service,
   Base.Schedule,
   sum(Base.PaymentFrom) AS PaymentFrom,
   sum(Base.ChargedFrom) AS ChargedFrom,
   sum(Base.ChargedTo) AS ChargedTo,
   sum(Base.PaymentTo) AS PaymentTo,
   sum(Base.CorrPere) AS CorrPere,
   sum(Base.CorrVozvr) AS CorrVozvr,
   Base.ChargingPayer,
   Base.Payment,
   Base.ChargeConfirm
 FROM
 (


   --1.5 Корректировка. Для каждой строки оплаты,
   --    у которой нераспределенная сумма неважно какая и дата оплаты больше левой границы и меньше правой границы
   --    и это корректировка, то есть префикс равен 'корр':
   --  Дата, Услуга, График услуг, Ребенок, Группа, Плательщик, Организация,
   --  Остаток оплат на начало        0
   --  Начислено за услуги на начало  0
   --  Оплата                         0
   --  Начислено за услуги            0
   --  Корректировка переплата        сумма оплаты, если не стоит галочка 'Возврат сумм', иначе 0
   --  Корректировка возврат          сумма оплаты, если    стоит галочка 'Возврат сумм', иначе 0
   SELECT 
TRUE as CSD,
P.PaymentDate,

CASE WHEN P.Service IS NOT NULL
THEN
P.Service
ELSE
EMPTY([ParusYug.Business.ParentsPayments.ServiceAndCompensation])
END AS Service,

EMPTY([ParusYug.Business.ParentsPayments.ScheduleServices]) AS Schedule,

CASE WHEN P.ServiceRecipient IS NOT NULL
THEN
P.ServiceRecipient
ELSE
EMPTY([ParusYug.Business.Payroll.ServiceRecipient])
END AS ServiceRecipient,

CASE WHEN P.[Group] IS NOT NULL
THEN
P.[Group]
ELSE
EMPTY([ParusYug.Business.Payroll.Group])
END AS [Group],

       P.PayCounteragent AS Payer,
       P.Institution.ServedOrganization,
       EMPTY([ParusYug.Business.ParentsPayments.ServiceProvide]) AS ProvidedService,
CONVERT([Parus.Business.Sum], 0) AS PaymentFrom,
CONVERT([Parus.Business.Sum], 0) AS ChargedFrom,
CONVERT([Parus.Business.Sum], 0) AS PaymentTo,
CONVERT([Parus.Business.Sum], 0) AS ChargedTo,

CONVERT([Parus.Business.Sum], CASE WHEN P.ReturnSum = FALSE THEN P.PayAmount ELSE 0 END) AS CorrPere,
CONVERT([Parus.Business.Sum], CASE WHEN P.ReturnSum = TRUE  THEN P.PayAmount ELSE 0 END) AS CorrVozvr,
       P.Identity AS Payment,
       EMPTY([ParusYug.Business.ParentsPayments.ChargingPayer]) AS ChargingPayer,
       EMPTY([ParusYug.Business.ParentsPayments.ChargeConfirm]) AS ChargeConfirm,
  'Корректировка' AS Src

     FROM [ParusYug.Business.ParentsPayments.Payment] P
     WHERE (@DateTo IS NULL OR P.PaymentDate <= @DateTo)
       AND (@DateFrom IS NULL OR P.PaymentDate >= @DateFrom)
       --AND P.UnknownAmount <> 0
       AND P.Institution.ServedOrganization = @ServOrg
       --AND (@Counteragent IS NULL OR P.PayCounteragent IN (@Counteragent))
       --AND (@Groups IS NULL OR P.[Group] IN (@Groups))
       --AND (@ServiceAndCompensation IS NULL OR P.Service IN (@ServiceAndCompensation))
       --AND (@ServiceRecipient IS NULL OR P.ServiceRecipient IN (@ServiceRecipient))
       AND P.Prefix = 'корр'

) Base
--WHERE (Base.Payer IN (@Counteragent) OR @Counteragent IS NULL)
 --AND (Base.[Group] IN (@Groups) OR @Groups IS NULL)
 --AND 
 --(@GroupTypes IS NULL
 -- OR Base.ProvidedService.GroupAttendance.[Group].GroupType IN (@GroupTypes)
 -- OR Base.ProvidedService.GroupService.[Group].GroupType IN (@GroupTypes)
 -- )
 --AND (Base.Service IN (@ServiceAndCompensation) OR @ServiceAndCompensation IS NULL)
 --AND (Base.ServiceRecipient IN (@ServiceRecipient) OR @ServiceRecipient IS NULL)
GROUP BY Base.Payer,
 Base.[Group],
 Base.ProvidedService,
 Base.ProvidedService.BC,
 Base.ServiceRecipient,
 Base.Service,
 Base.Schedule,
 Base.ChargingPayer,
 Base.Payment,
 Base.ChargeConfirm,
Base.PaymentDate,
Base.Src,
Base.CSD
) Main;

Код запроса с оплатами (квитирование и начисления аналогично, поэтому их не привожу здесь):

/*Стандартные параметры*/
DECLARE @DateFrom AS [Parus.Business.Date];
DECLARE @DateTo AS [Parus.Business.Date];
DECLARE @ServOrg AS [Parus.Business.ServedOrganization];

/*Параметры-массивы*/
DECLARE @Groups AS ArrayOf([ParusYug.Business.Payroll.Group]);
DECLARE @GroupTypes AS ArrayOf([ParusYug.Business.Payroll.GroupType]);
DECLARE @ServiceAndCompensation AS ArrayOf([ParusYug.Business.ParentsPayments.ServiceAndCompensation]);
DECLARE @Counteragent AS ArrayOf([Parus.Business.Counteragent]);
DECLARE @ServiceRecipient AS ArrayOf([ParusYug.Business.Payroll.ServiceRecipient]);


SELECT 
Main.CSD,
Main.Payer AS Counteragent,
 Main.[Group],
 Main.ProvidedService AS ServiceProvide,
 Main.ServiceRecipient,
 Main.Service,
 Main.Schedule,
 Main.PaymentFrom,
 Main.ChargedFrom,
 Main.ChargedFrom - Main.PaymentFrom AS SaldoFrom,
 Main.ChargedTo,
 Main.PaymentTo,
 Main.CorrPere,
 Main.CorrVozvr,
 Main.ChargedTo - Main.PaymentTo AS SaldoTo,
 Main.PaymentFrom + Main.PaymentTo AS PaymentFromTo,
 Main.ChargedFrom + Main.ChargedTo AS ChargedFromTo,
 (Main.ChargedFrom - Main.PaymentFrom) + (Main.ChargedTo - Main.PaymentTo) AS SaldoFromTo,
 Main.ChargingPayer,
 Main.Payment,
 Main.ChargeConfirm,
Main.PaymentDate,
Main.Src
FROM
(
SELECT
Base.CSD,
Base.PaymentDate,
Base.Src,
Base.Payer,
   Base.[Group],
   Base.ProvidedService,
   Base.ProvidedService.BC,
   Base.ServiceRecipient,
   Base.Service,
   Base.Schedule,
   sum(Base.PaymentFrom) AS PaymentFrom,
   sum(Base.ChargedFrom) AS ChargedFrom,
   sum(Base.ChargedTo) AS ChargedTo,
   sum(Base.PaymentTo) AS PaymentTo,
   sum(Base.CorrPere) AS CorrPere,
   sum(Base.CorrVozvr) AS CorrVozvr,
   Base.ChargingPayer,
   Base.Payment,
   Base.ChargeConfirm
 FROM
 (
   -- 1. Для каждой строки оплаты,
   --    у которой нераспределенная сумма <> 0 и дата оплаты меньше правой границы
   --    и это не корректировка:
   --  Дата, Услуга, График услуг, Ребенок, Группа, Плательщик, Организация,
   --  Остаток оплат на начало        нераспределенная сумма, если дата меньше левой границы диапазона формирования, иначе 0
   --  Начислено за услуги на начало  0
   --  Оплата                         нераспределенная сумма, если дата больше или равна левой границы диапазона формирования, иначе 0
   --  Начислено за услуги            0
   --  Корректировка переплата        0
   --  Корректировка возврат          0
   SELECT 
TRUE as CSD,
P.PaymentDate,

CASE WHEN P.Service IS NOT NULL
THEN
P.Service
ELSE
EMPTY([ParusYug.Business.ParentsPayments.ServiceAndCompensation])
END AS Service,

EMPTY([ParusYug.Business.ParentsPayments.ScheduleServices]) AS Schedule,

CASE WHEN P.ServiceRecipient IS NOT NULL
THEN
P.ServiceRecipient
ELSE
EMPTY([ParusYug.Business.Payroll.ServiceRecipient])
END AS ServiceRecipient,

CASE WHEN P.[Group] IS NOT NULL
THEN
P.[Group]
ELSE
EMPTY([ParusYug.Business.Payroll.Group])
END AS [Group],

       P.PayCounteragent AS Payer,
       P.Institution.ServedOrganization,
       EMPTY([ParusYug.Business.ParentsPayments.ServiceProvide]) AS ProvidedService,
CONVERT([Parus.Business.Sum], CASE WHEN P.PaymentDate < @DateFrom THEN (case when ReturnSum <> True then P.UnknownAmount else -P.UnknownAmount end) ELSE 0 END) AS PaymentFrom,
CONVERT([Parus.Business.Sum], 0) AS ChargedFrom,
CONVERT([Parus.Business.Sum], CASE WHEN P.PaymentDate >= @DateFrom THEN (case when ReturnSum <> True then P.UnknownAmount else -P.UnknownAmount end) ELSE 0 END) AS PaymentTo,
CONVERT([Parus.Business.Sum], 0) AS ChargedTo,
CONVERT([Parus.Business.Sum], 0) AS CorrPere,
CONVERT([Parus.Business.Sum], 0) AS CorrVozvr,
       P.Identity AS Payment,
       EMPTY([ParusYug.Business.ParentsPayments.ChargingPayer]) AS ChargingPayer,
       EMPTY([ParusYug.Business.ParentsPayments.ChargeConfirm]) AS ChargeConfirm,
  'Оплата' AS Src

     FROM [ParusYug.Business.ParentsPayments.Payment] P
     WHERE (@DateTo IS NULL OR P.PaymentDate <= @DateTo)
       AND P.UnknownAmount <> 0
       AND P.Institution.ServedOrganization = @ServOrg
       AND (@Counteragent IS NULL OR P.PayCounteragent IN (@Counteragent))
       AND (@Groups IS NULL OR P.[Group] IN (@Groups))
       AND (@ServiceAndCompensation IS NULL OR P.Service IN (@ServiceAndCompensation))
       AND (@ServiceRecipient IS NULL OR P.ServiceRecipient IN (@ServiceRecipient))
       AND P.Prefix <> 'корр'


   UNION ALL


   --1.5 Корректировка. Для каждой строки оплаты,
   --    у которой нераспределенная сумма неважно какая и дата оплаты больше левой границы и меньше правой границы
   --    и это корректировка, то есть префикс равен 'корр':
   --  Дата, Услуга, График услуг, Ребенок, Группа, Плательщик, Организация,
   --  Остаток оплат на начало        0
   --  Начислено за услуги на начало  0
   --  Оплата                         0
   --  Начислено за услуги            0
   --  Корректировка переплата        сумма оплаты, если не стоит галочка 'Возврат сумм', иначе 0
   --  Корректировка возврат          сумма оплаты, если    стоит галочка 'Возврат сумм', иначе 0
   SELECT 
TRUE as CSD,
P.PaymentDate,

CASE WHEN P.Service IS NOT NULL
THEN
P.Service
ELSE
EMPTY([ParusYug.Business.ParentsPayments.ServiceAndCompensation])
END AS Service,

EMPTY([ParusYug.Business.ParentsPayments.ScheduleServices]) AS Schedule,

CASE WHEN P.ServiceRecipient IS NOT NULL
THEN
P.ServiceRecipient
ELSE
EMPTY([ParusYug.Business.Payroll.ServiceRecipient])
END AS ServiceRecipient,

CASE WHEN P.[Group] IS NOT NULL
THEN
P.[Group]
ELSE
EMPTY([ParusYug.Business.Payroll.Group])
END AS [Group],

       P.PayCounteragent AS Payer,
       P.Institution.ServedOrganization,
       EMPTY([ParusYug.Business.ParentsPayments.ServiceProvide]) AS ProvidedService,
CONVERT([Parus.Business.Sum], 0) AS PaymentFrom,
CONVERT([Parus.Business.Sum], 0) AS ChargedFrom,
CONVERT([Parus.Business.Sum], 0) AS PaymentTo,
CONVERT([Parus.Business.Sum], 0) AS ChargedTo,

CONVERT([Parus.Business.Sum], CASE WHEN P.ReturnSum = FALSE THEN P.PayAmount ELSE 0 END) AS CorrPere,
CONVERT([Parus.Business.Sum], CASE WHEN P.ReturnSum = TRUE  THEN P.PayAmount ELSE 0 END) AS CorrVozvr,
       P.Identity AS Payment,
       EMPTY([ParusYug.Business.ParentsPayments.ChargingPayer]) AS ChargingPayer,
       EMPTY([ParusYug.Business.ParentsPayments.ChargeConfirm]) AS ChargeConfirm,
  'Корректировка' AS Src

     FROM [ParusYug.Business.ParentsPayments.Payment] P
     WHERE (@DateTo IS NULL OR P.PaymentDate <= @DateTo)
       AND (@DateFrom IS NULL OR P.PaymentDate >= @DateFrom)
       --AND P.UnknownAmount <> 0
       AND P.Institution.ServedOrganization = @ServOrg
       --AND (@Counteragent IS NULL OR P.PayCounteragent IN (@Counteragent))
       --AND (@Groups IS NULL OR P.[Group] IN (@Groups))
       --AND (@ServiceAndCompensation IS NULL OR P.Service IN (@ServiceAndCompensation))
       --AND (@ServiceRecipient IS NULL OR P.ServiceRecipient IN (@ServiceRecipient))
       AND P.Prefix = 'корр'


) Base
--WHERE (Base.Payer IN (@Counteragent) OR @Counteragent IS NULL)
 --AND (Base.[Group] IN (@Groups) OR @Groups IS NULL)
 --AND 
 --(@GroupTypes IS NULL
 -- OR Base.ProvidedService.GroupAttendance.[Group].GroupType IN (@GroupTypes)
 -- OR Base.ProvidedService.GroupService.[Group].GroupType IN (@GroupTypes)
 -- )
 --AND (Base.Service IN (@ServiceAndCompensation) OR @ServiceAndCompensation IS NULL)
 --AND (Base.ServiceRecipient IN (@ServiceRecipient) OR @ServiceRecipient IS NULL)
GROUP BY Base.Payer,
 Base.[Group],
 Base.ProvidedService,
 Base.ProvidedService.BC,
 Base.ServiceRecipient,
 Base.Service,
 Base.Schedule,
 Base.ChargingPayer,
 Base.Payment,
 Base.ChargeConfirm,
Base.PaymentDate,
Base.Src,
Base.CSD
) Main;

Значения корректировок содержатся в колонках CorrPere (корректировка переплата) и CorrVozvr (корректировка возврат). После выборки данных запросом, они подвергаются ряду дополнительных обработок, которые здесь не привожу, потому что в них нет проблемы. Проблема сидит где-то в обработке GroupBy, потому что именно после нее колонки CorrPere и CorrVozvr во всех записях становятся равны нулю:

 var data1 = data.GroupBy(
  od => new
  {
   od.GroupName,
   od.ServiceRecipientName,
   od.PayerName
  },
  (index, items) =>
  {
   var first = items.FirstOrDefault(x => x.OD != null && x.OD.Service != null) ?? items.First();
   return new
   {
    PayerId = first.PayerId,
    Rest = items.Sum(i => i.Rest),
    FinalRest = items.Sum(i => i.FinalRest),
    Rate1 = first.Rate1,
    Rate2 = first.Rate2,
    
    Charged = items.Sum(i => i.Charged),
    PayerName = first.PayerName,
    Paid = items.Sum(i => i.Paid),
    CorrPere = items.Sum(i => i.CorrPere),
    CorrVozvr = items.Sum(i => i.CorrVozvr),
    Index = Params.GroupByPayer ? 0 : ++numCounter,
    ServiceRecipientName = first.ServiceRecipientName,
    Number = first.OD.ServiceRecipient.Number.Value,
    GroupName = first.GroupName,
    ServiceName = first.ServiceName == null ? first.ServicePayName : first.ServiceName,
    Fact1 = first.Fact1,
    Fact2 = first.Fact2,
    Benefit = first.Benefit,
    Items = items,
 CSD = first.CSD
   };
  })
.Where(p => p.CSD || ((p.Rest != 0m || p.FinalRest != 0m) && !p.CSD && !Params.ClosedServices))
.ToArray();

//debug
var tmp3 = data1.Where(d => d.CorrPere != 0 || d.CorrVozvr != 0);

Вот в этот tmp3 уже ничего не отбирается - tmp3.Count()=0. Куда могут пропадать в вышеприведенном коде значения CorrPere и CorrVozvr? подскажите пожалуйста. Ведь вроде бы суммирую их как все остальные числовые поля: Rest, Charged, Paid. Куда же пропадают значения?


Ответы (1 шт):

Автор решения: SargeAnt

Выяснено: корректировки не обнуляются, а не пропускаются условием

.Where(p => p.CSD || ((p.Rest != 0m || p.FinalRest != 0m) && !p.CSD && !Params.ClosedServices))

(хотел бы я знать, как расшифровывается у автора это CSD (тип его - Boolean), и для чего этот флаг служит) Не пропускаются условием по CSD потому, что двумя строками выше это CSD берется как CSD = first.CSD, то есть довольно случайным образом. Это вообще законно так брать? По-моему, это косяк у автора. К сожалению, не знаю как такой косяк правильно лечить. Но знаю, что если взять Max() от CSD, то возьмется значение true, а нам это вполне подходит. Поэтому изменил последние строки кода следующим образом:

//debug
//CSD = first.CSD
CSD = items.Max(i => i.CSD)

Теперь корректировки не обнуляются. Но не вылезет ли это боком где-то дальше - не знаю. Кто знает - расскажите нам пожалуйста!

→ Ссылка