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