При разработке отчетов, обработок выгрузки в банк или алгоритмов расчета выплат часто возникает ситуация: в тексте запроса четко задан отбор по положительным суммам, однако в результирующей таблице все равно присутствуют отрицательные значения. Разберем, почему возникает такая ситуация, в чем разница между секциями ГДЕ и ИМЕЮЩИЕ, а также выясним, где чаще всего скрывается ошибка.
Рассмотрим типовой пример запроса, выполняющего выборку сумм по сотрудникам с привязкой к банковским счетам:
сум = 0;
ЗапросВыплата = Новый Запрос;
ЗапросВыплата.УстановитьПараметр("ТекущийДокумент", ДокументВыплата);
ЗапросВыплата.УстановитьПараметр("Банк", ТекБанк);
ЗапросВыплата.УстановитьПараметр("сум", сум);
ЗапросВыплата.Текст =
"ВЫБРАТЬ
| ЛицевыеСчета.Наименование,
| ЛицевыеСчета.Банк,
| ЛицевыеСчета.Владелец.Ссылка КАК Сотрудник
|ПОМЕСТИТЬ Лицевые
|ИЗ
| Справочник.ЛицевыеСчета КАК ЛицевыеСчета
|ГДЕ
| ЛицевыеСчета.Владелец В
| (ВЫБРАТЬ
| ВыплатаТаблицаДокумента.Сотрудник.Ссылка
| ИЗ
| Документ.Выплата.ТаблицаДокумента КАК ВыплатаТаблицаДокумента
| ГДЕ
| ВыплатаТаблицаДокумента.Ссылка = &ТекущийДокумент)
| И ЛицевыеСчета.Банк = &Банк
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| СУММА(ВыплатаТаблицаДокумента.Сумма) КАК Сумма,
| Лицевые.Наименование,
| ВыплатаТаблицаДокумента.Сотрудник
|ИЗ
| Документ.Выплата.ТаблицаДокумента КАК ВыплатаТаблицаДокумента
| ЛЕВОЕ СОЕДИНЕНИЕ Лицевые КАК Лицевые
| ПО ВыплатаТаблицаДокумента.Сотрудник = Лицевые.Сотрудник
|ГДЕ
| ВыплатаТаблицаДокумента.Ссылка = &ТекущийДокумент
| И ВыплатаТаблицаДокумента.Сумма >= &сум
|
|СГРУППИРОВАТЬ ПО
| ВыплатаТаблицаДокумента.Сотрудник,
| Лицевые.Наименование";
ТаблицаВыплата = ЗапросВыплата.Выполнить().Выгрузить();
Математически и логически запрос платформы 1С не может вернуть отрицательное значение в поле Сумма, если в секции ГДЕ наложено жесткое условие ВыплатаТаблицаДокумента.Сумма >= &сум при значении параметра, равном нулю. СУБД сначала отфильтровывает все строки с отрицательными числами, а затем суммирует исключительно положительные значения (или нули). Сумма положительных чисел всегда положительна. Следовательно, причина появления отрицательных чисел кроется в других деталях выполнения программы.
Проанализируем принципиальное различие между наложением условий в секции ГДЕ и секции ИМЕЮЩИЕ:
ГДЕ отсекает записи до момента их группировки и суммирования. Если в табличной части документа по сотруднику есть строка начисления (+10 000 руб.) и строка удержания/сторно (-3 000 руб.), условие Сумма >= 0 просто проигнорирует отрицательную строку. В итоге запрос выдаст 10 000 руб. вместо реального долга к выплате в 7 000 руб.ИМЕЮЩИЕ применяется уже к результату агрегатной функции после схлопывания строк. Если требуется учесть и плюсы, и минусы по сотруднику, но исключить тех, у кого итоговый результат отрицательный или нулевой, фильтр переносят в секцию ИМЕЮЩИЕ.Посмотрим на пример правильной структуры запроса с использованием ИМЕЮЩИЕ:
ВЫБРАТЬ
СУММА(ВыплатаТаблицаДокумента.Сумма) КАК Сумма,
Лицевые.Наименование КАК НомерСчета,
ВыплатаТаблицаДокумента.Сотрудник КАК Сотрудник
ИЗ
Документ.Выплата.ТаблицаДокумента КАК ВыплатаТаблицаДокумента
ЛЕВОЕ СОЕДИНЕНИЕ Лицевые КАК Лицевые
ПО ВыплатаТаблицаДокумента.Сотрудник = Лицевые.Сотрудник
ГДЕ
ВыплатаТаблицаДокумента.Ссылка = &ТекущийДокумент
СГРУППИРОВАТЬ ПО
ВыплатаТаблицаДокумента.Сотрудник,
Лицевые.Наименование
ИМЕЮЩИЕ
СУММА(ВыплатаТаблицаДокумента.Сумма) > 0
Если в коде установлено условие ГДЕ Сумма >= &сум или ИМЕЮЩИЕ СУММА(Сумма) > 0, но в результате всё равно виден минус, разберем по шагам алгоритм диагностики:
ТаблицаВыплата = ЗапросВыплата.Выполнить().Выгрузить(); и сразу за ней. Откроем таблицу ТаблицаВыплата через окно вычисления выражений. Если в этой таблице отрицательных чисел нет, значит, появление минусов происходит дальше по коду — например, в последующем цикле обработки строк, вычитании НДФЛ или формировании файла для банка.сум не была непреднамеренно изменена до вызова метода УстановитьПараметр. Значение параметра в запросе должно быть строго числовым.Документ.Выплата.ТаблицаДокумента считывает данные из таблиц базы данных СУБД, а не из экранной формы. Если пользователь отредактировал документ на форме, но не нажал «Записать», запрос прочитает старые данные, которые были сохранены в базе ранее.Обратим внимание на скрытую проблему с временной таблицей Лицевые. В исходном запросе выборка счетов выполняется так:
ВЫБРАТЬ
ЛицевыеСчета.Наименование,
ЛицевыеСчета.Банк,
ЛицевыеСчета.Владелец.Ссылка КАК Сотрудник
ПОМЕСТИТЬ Лицевые
ИЗ
Справочник.ЛицевыеСчета КАК ЛицевыеСчета
ГДЕ
ЛицевыеСчета.Владелец В (...)
И ЛицевыеСчета.Банк = &Банк
Если у одного сотрудника в выбранном банке открыто два или более лицевых счета (например, старый закрытый счет и новый актуальный), левое соединение размножит строки документа по этому человеку. В результате агрегатная функция СУММА(ВыплатаТаблицаДокумента.Сумма) посчитает сумму документа столько раз, сколько счетов найдено в справочнике. Чтобы избежать искажения сумм, обязательно накладывайте фильтр на активность счета (например, проверяйте НЕ ЛицевыеСчета.ПометкаУдаления) и гарантируйте получение только одной записи на каждого сотрудника.