Почему запрос в 1С возвращает отрицательные суммы при наличии условия отбора?

Программист 1С v8.3 (Обычные формы) IT и автоматизация бизнеса
← На главную

При разработке отчетов, обработок выгрузки в банк или алгоритмов расчета выплат часто возникает ситуация: в тексте запроса четко задан отбор по положительным суммам, однако в результирующей таблице все равно присутствуют отрицательные значения. Разберем, почему возникает такая ситуация, в чем разница между секциями ГДЕ и ИМЕЮЩИЕ, а также выясним, где чаще всего скрывается ошибка.

Анализ исходного запроса

Рассмотрим типовой пример запроса, выполняющего выборку сумм по сотрудникам с привязкой к банковским счетам:


сум = 0;
ЗапросВыплата = Новый Запрос;
ЗапросВыплата.УстановитьПараметр("ТекущийДокумент", ДокументВыплата);
ЗапросВыплата.УстановитьПараметр("Банк", ТекБанк);
ЗапросВыплата.УстановитьПараметр("сум", сум);
ЗапросВыплата.Текст =
"ВЫБРАТЬ
|   ЛицевыеСчета.Наименование,
|   ЛицевыеСчета.Банк,
|   ЛицевыеСчета.Владелец.Ссылка КАК Сотрудник
|ПОМЕСТИТЬ Лицевые
|ИЗ
|   Справочник.ЛицевыеСчета КАК ЛицевыеСчета
|ГДЕ
|   ЛицевыеСчета.Владелец В
|           (ВЫБРАТЬ
|               ВыплатаТаблицаДокумента.Сотрудник.Ссылка
|           ИЗ
|               Документ.Выплата.ТаблицаДокумента КАК ВыплатаТаблицаДокумента
|           ГДЕ
|               ВыплатаТаблицаДокумента.Ссылка = &ТекущийДокумент)
|   И ЛицевыеСчета.Банк = &Банк
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
|   СУММА(ВыплатаТаблицаДокумента.Сумма) КАК Сумма,
|   Лицевые.Наименование,
|   ВыплатаТаблицаДокумента.Сотрудник
|ИЗ
|   Документ.Выплата.ТаблицаДокумента КАК ВыплатаТаблицаДокумента
|       ЛЕВОЕ СОЕДИНЕНИЕ Лицевые КАК Лицевые
|       ПО ВыплатаТаблицаДокумента.Сотрудник = Лицевые.Сотрудник
|ГДЕ
|   ВыплатаТаблицаДокумента.Ссылка = &ТекущийДокумент
|   И ВыплатаТаблицаДокумента.Сумма >= &сум
|
|СГРУППИРОВАТЬ ПО
|   ВыплатаТаблицаДокумента.Сотрудник,
|   Лицевые.Наименование";
      
ТаблицаВыплата = ЗапросВыплата.Выполнить().Выгрузить();

Математически и логически запрос платформы 1С не может вернуть отрицательное значение в поле Сумма, если в секции ГДЕ наложено жесткое условие ВыплатаТаблицаДокумента.Сумма >= &сум при значении параметра, равном нулю. СУБД сначала отфильтровывает все строки с отрицательными числами, а затем суммирует исключительно положительные значения (или нули). Сумма положительных чисел всегда положительна. Следовательно, причина появления отрицательных чисел кроется в других деталях выполнения программы.

Различие логики: условие «ГДЕ» против «ИМЕЮЩИЕ»

Проанализируем принципиальное различие между наложением условий в секции ГДЕ и секции ИМЕЮЩИЕ:

  1. Условие в секции ГДЕ отсекает записи до момента их группировки и суммирования. Если в табличной части документа по сотруднику есть строка начисления (+10 000 руб.) и строка удержания/сторно (-3 000 руб.), условие Сумма >= 0 просто проигнорирует отрицательную строку. В итоге запрос выдаст 10 000 руб. вместо реального долга к выплате в 7 000 руб.
  2. Условие в секции ИМЕЮЩИЕ применяется уже к результату агрегатной функции после схлопывания строк. Если требуется учесть и плюсы, и минусы по сотруднику, но исключить тех, у кого итоговый результат отрицательный или нулевой, фильтр переносят в секцию ИМЕЮЩИЕ.

Посмотрим на пример правильной структуры запроса с использованием ИМЕЮЩИЕ:


ВЫБРАТЬ
    СУММА(ВыплатаТаблицаДокумента.Сумма) КАК Сумма,
    Лицевые.Наименование КАК НомерСчета,
    ВыплатаТаблицаДокумента.Сотрудник КАК Сотрудник
ИЗ
    Документ.Выплата.ТаблицаДокумента КАК ВыплатаТаблицаДокумента
    ЛЕВОЕ СОЕДИНЕНИЕ Лицевые КАК Лицевые
    ПО ВыплатаТаблицаДокумента.Сотрудник = Лицевые.Сотрудник
ГДЕ
    ВыплатаТаблицаДокумента.Ссылка = &ТекущийДокумент
СГРУППИРОВАТЬ ПО
    ВыплатаТаблицаДокумента.Сотрудник,
    Лицевые.Наименование
ИМЕЮЩИЕ
    СУММА(ВыплатаТаблицаДокумента.Сумма) > 0

Пошаговый план поиска ошибки: почему выводятся отрицательные числа

Если в коде установлено условие ГДЕ Сумма >= &сум или ИМЕЮЩИЕ СУММА(Сумма) > 0, но в результате всё равно виден минус, разберем по шагам алгоритм диагностики:

  1. Проверим точку останова сразу после выполнения запроса. Поставим точку останова в конфигураторе на строке ТаблицаВыплата = ЗапросВыплата.Выполнить().Выгрузить(); и сразу за ней. Откроем таблицу ТаблицаВыплата через окно вычисления выражений. Если в этой таблице отрицательных чисел нет, значит, появление минусов происходит дальше по коду — например, в последующем цикле обработки строк, вычитании НДФЛ или формировании файла для банка.
  2. Проверим значение параметра в отладчике. Убедимся, что переменная сум не была непреднамеренно изменена до вызова метода УстановитьПараметр. Значение параметра в запросе должно быть строго числовым.
  3. Проверим сохранение данных документа. Обращение к табличной части через Документ.Выплата.ТаблицаДокумента считывает данные из таблиц базы данных СУБД, а не из экранной формы. Если пользователь отредактировал документ на форме, но не нажал «Записать», запрос прочитает старые данные, которые были сохранены в базе ранее.
  4. Проверим версию исполняемого кода. Если доработка выполняется во внешней обработке или печатной форме, часто возникает ситуация, когда изменения сохраняются в один файл, а в режиме предприятия открывается другой файл или устаревшая версия из справочника дополнительных отчетов.
  5. Протестируем запрос в консоли запросов. Выгрузим текст запроса и текущие параметры в консоль запросов платформы 1С. Это позволит исключить влияние программного окружения конфигурации и увидеть чистый результат выполнения выборки СУБД.

Опасность задвоения данных при соединении со справочником

Обратим внимание на скрытую проблему с временной таблицей Лицевые. В исходном запросе выборка счетов выполняется так:


ВЫБРАТЬ
    ЛицевыеСчета.Наименование,
    ЛицевыеСчета.Банк,
    ЛицевыеСчета.Владелец.Ссылка КАК Сотрудник
ПОМЕСТИТЬ Лицевые
ИЗ
    Справочник.ЛицевыеСчета КАК ЛицевыеСчета
ГДЕ
    ЛицевыеСчета.Владелец В (...)
    И ЛицевыеСчета.Банк = &Банк

Если у одного сотрудника в выбранном банке открыто два или более лицевых счета (например, старый закрытый счет и новый актуальный), левое соединение размножит строки документа по этому человеку. В результате агрегатная функция СУММА(ВыплатаТаблицаДокумента.Сумма) посчитает сумму документа столько раз, сколько счетов найдено в справочнике. Чтобы избежать искажения сумм, обязательно накладывайте фильтр на активность счета (например, проверяйте НЕ ЛицевыеСчета.ПометкаУдаления) и гарантируйте получение только одной записи на каждого сотрудника.

← На главную