Как получить сумму списания номенклатуры по документу «Требование-накладная» в 1С без дублирования строк и зависаний запроса

Программист 1С v8.3 (Управляемые формы) 1C:Бухгалтерия Бухгалтерский учет IT и автоматизация бизнеса
← На главную

При разработке отчетов, печатных форм или проведении двустороннего анализа движений документов в 1С:Предприятии перед разработчиком часто возникает задача: получить точные суммы списания материальных запасов по конкретному первичному документу (например, Требование-накладная или Расход материалов). На первый взгляд задача кажется тривиальной — достаточно соединить табличную часть документа с регистром бухгалтерии. Однако на практике разработчики сталкиваются с серьезными проблемами: задвоением (или даже затроением) сумм в строках, критическим падением производительности базы данных и ошибками СУБД типа «Операция не разрешена в предложении...».

В этой статье мы подробно разберем, почему возникают эти проблемы, как правильно оптимизировать запросы к регистру бухгалтерии, научимся грамотно использовать временные таблицы и типизацию субконто, а также рассмотрим альтернативные и методологические нюансы учета себестоимости в 1С:Бухгалтерии 3.0.

---

Почему простой запрос с левым соединением дублирует строки?

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

Выясним причину этого поведения. Физическая таблица регистра бухгалтерии хранит проводки. Для одной строки документа (например, списание 10 кг кабеля) в регистре может быть сделано несколько записей. Это происходит из-за того, что:

  1. В одной проводке могут быть задействованы разные виды учета (активно-пассивные счета, учет по подразделениям).
  2. В проводке формируются движения не только по бухгалтерскому учету (БУ), но и по налоговому учету (НУ), а также разницы (ПР и ВР). Физическая таблица содержит все эти срезы.
  3. Соединение происходит без точной привязки к конкретному субконто (номенклатуре) и направлению проводки (дебет/кредит), из-за чего строка спецификации документа связывается со всеми проводками этого документа сразу.

Чтобы избежать дублирования, нам необходимо обращаться не к физической таблице проводок, а к специализированным виртуальным таблицам регистра бухгалтерии, которые умеют предварительно сворачивать и группировать данные по заданным измерениям.

---

Решение 1: Использование виртуальной таблицы «Обороты» с временной таблицей

Наиболее надежный и производительный способ решения задачи — это выборка оборотов по кредиту счетов учета материалов (как правило, группа счетов 10 «Материалы») в разрезе регистратора и номенклатуры (Субконто1).

Давайте пошагово разберем, как построить этот запрос. Чтобы база данных работала быстро, мы разделим наш запрос на два пакета с использованием временной таблицы. Это исключит соединение «на лету» нетипизированных полей субконто, что крайне негативно сказывается на производительности СУБД.

Важный нюанс типизации субконто: Поле Субконто1 в регистре имеет составной тип данных (в него могут входить справочники Номенклатура, ОсновныеСредства, Контрагенты и т.д.). Если попытаться сделать левое соединение напрямую по полю Субконто1, СУБД будет вынуждена выполнять неявное приведение типов для миллионов записей, что приведет к зависанию системы. Поэтому мы обязаны жестко ограничить тип значения с помощью конструкции ВЫРАЗИТЬ.

При этом использовать ВЫРАЗИТЬ непосредственно в секции соединения ПО (JOIN) категорически запрещено! Платформа 1С выдаст синтаксическую ошибку: «Операция не разрешена в предложении...». Мы должны выразить тип заранее — при помещении данных во временную таблицу.

Проанализируем правильный шаблон пакета запросов:


// ПАКЕТ 1: Выбираем обороты регистра и принудительно типизируем Субконто1
ВЫБРАТЬ
    ХозрасчетныйОбороты.СуммаОборотКт КАК СуммаОборотКт,
    ВЫРАЗИТЬ(ХозрасчетныйОбороты.Субконто1 КАК Справочник.Номенклатура) КАК Номенклатура,
    ХозрасчетныйОбороты.Счет КАК Счет
ПОМЕСТИТЬ ХозВрем
ИЗ
    РегистрБухгалтерии.Хозрасчетный.Обороты(, , Регистратор, , , , , ) КАК ХозрасчетныйОбороты
ГДЕ
    ХозрасчетныйОбороты.Регистратор В(&Ссылка);

////////////////////////////////////////////////////////////////////////////////
// ПАКЕТ 2: Соединяем табличную часть документа с нашей подготовленной ВТ
ВЫБРАТЬ
    ТН.Номер КАК Номер,
    ТН.Дата КАК Дата,
    ТН.Организация КАК Организация,
    ТНМат.НомерСтроки КАК НомерСтроки,
    ТНМат.Номенклатура КАК Номенклатура,
    ТНМат.ЕдиницаИзмерения КАК ЕдИзм,
    ТНМат.Количество КАК Количество,
    ЕСТЬNULL(ХозВрем.СуммаОборотКт, 0) КАК Сумма
ИЗ
    Документ.ТребованиеНакладная.Материалы КАК ТНМат
    ЛЕВОЕ СОЕДИНЕНИЕ Документ.ТребованиеНакладная КАК ТН 
        ПО ТНМат.Ссылка = ТН.Ссылка
    ЛЕВОЕ СОЕДИНЕНИЕ ХозВрем КАК ХозВрем 
        ПО ТНМат.Номенклатура = ХозВрем.Номенклатура
        И ТНМат.Счет = ХозВрем.Счет
ГДЕ
    ТН.Ссылка В(&Ссылка)

Разберем преимущества этого подхода. Во-первых, во временную таблицу попадают обороты только по конкретному документу (или массиву документов) благодаря условию ГДЕ Регистратор В(&Ссылка). Во-вторых, соединение во втором пакете происходит по уже проиндексированным (при необходимости) и четко типизированным полям Номенклатура и Счет. Никаких дублей строк в этом случае не возникнет.

---

Решение 2: Оптимизация параметров виртуальной таблицы «Обороты»

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

Для СУБД это означает следующее: сначала виртуальная таблица попытается рассчитать обороты по всей базе данных (или за весь период, если он не ограничен), а уже затем применит фильтр по конкретному регистратору. Если база данных большая, запрос будет выполняться медленно.

Для оптимизации мы должны передать все возможные условия фильтрации непосредственно в параметры-конструкторы виртуальной таблицы. Изменим первый пакет запроса:


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

Передавая &НачалоПериода, &КонецПериода и список счетов (например, массив из счетов 10.01, 10.02, 10.05, 10.06), мы заставляем платформу 1С генерировать оптимальный SQL-запрос, который обратится к узкому диапазону итогов регистра, снижая нагрузку на сервер до минимума.

---

Решение 3: Использование виртуальной таблицы «ДвиженияССубконто»

Если нам необходимо получить детальные проводки списания именно по конкретному документу-регистратору, использование виртуальной таблицы ДвиженияССубконто является отличной альтернативой таблице Обороты.

Она изначально спроектирована для работы в связке с регистратором. Кроме того, в ней поля субконто сразу разделены по направлениям дебета и кредита (СубконтоДт1, СубконтоКт1 и т.д.). Поскольку материалы списываются с кредита 10-х счетов, нам достаточно обратиться к кредитовым субконто.

Рассмотрим пример запроса:


ВЫБРАТЬ
    Движения.Сумма КАК Сумма,
    ВЫРАЗИТЬ(Движения.СубконтоКт1 КАК Справочник.Номенклатура) КАК Номенклатура,
    Движения.СчетКт КАК Счет
ПОМЕСТИТЬ ХозВрем
ИЗ
    РегистрБухгалтерии.Хозрасчетный.ДвиженияССубконто(
        &НачалоПериода, 
        &КонецПериода, 
        Регистратор В (&Ссылка) И СчетКт В (&МассивСчетовУчета)
    ) КАК Движения;

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

Этот вариант запроса выполняется чрезвычайно быстро, так как платформа транслирует его в прямое чтение физических таблиц движений конкретных документов, минуя сложные расчеты остатков и оборотов.

---

Решение 4: Программный обход движений без написания сложных запросов

В первом сообщении темы пользователь упомянул, что в типовой конфигурации «Бухгалтерия предприятия 3.0» могут существовать встроенные экспортные функции для получения себестоимости. Действительно, при работе в рамках кода модулей документов (например, при формировании печатных форм), писать тяжелые запросы вручную не всегда обязательно.

Если документ гарантированно проведен, мы можем быстро прочитать его движения по регистру бухгалтерии напрямую через объектную модель 1С. Для этого используется чтение набора записей регистра с фильтром по регистратору.

Рассмотрим программный код, реализующий этот подход:


Функция ПолучитьТаблицуСписанияМатериалов(СсылкаНаДокумент) Экспорт
    
    // Создаем набор записей регистра бухгалтерии Хозрасчетный
    НаборЗаписей = РегистрыБухгалтерии.Хозрасчетный.СоздатьНаборЗаписей();
    НаборЗаписей.Отбор.Регистратор.Установить(СсылкаНаДокумент);
    НаборЗаписей.Прочитать();
    
    // Выгружаем записи в таблицу значений
    ТаблицаПроводок = НаборЗаписей.Выгрузить();
    
    // Подготавливаем структуру для результирующей таблицы
    ТаблицаРезультат = Новый ТаблицаЗначений;
    ТаблицаРезультат.Колонки.Добавить("Номенклатура", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
    ТаблицаРезультат.Колонки.Добавить("Сумма", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2)));
    
    // Анализируем проводки списания (кредит 10-х счетов)
    Для Каждого Проводка Из ТаблицаПроводок Цикл
        // Проверяем, является ли счет Кт счетом учета материалов (группа 10)
        Если Проводка.СчетКт.ПринадлежитИерархии(ПланыСчетов.Хозрасчетный.Материалы) Тогда
            НоваяСтрока = ТаблицаРезультат.Добавить();
            НоваяСтрока.Номенклатура = Проводка.СубконтоКт1; // Первое субконто Кт - это Номенклатура
            НоваяСтрока.Сумма = Проводка.Сумма;
        КонецЕсли;
    КонецЦикла;
    
    // Сворачиваем таблицу по номенклатуре для устранения дублей по разным проводкам
    ТаблицаРезультат.Свернуть("Номенклатура", "Сумма");
    
    Возврат ТаблицаРезультат;
    
КонецФункции

Этот способ крайне прост в отладке, не нагружает СУБД компиляцией сложных SQL-планов соединений и отлично подходит для задач, выполняющихся в контексте одного конкретного документа (например, при печати накладной М-11).

---

Методологический нюанс: «Корректировка стоимости списания» при закрытии месяца

Разработчикам, решающим задачи получения себестоимости списания в 1С:Бухгалтерии, критически важно понимать внутреннюю методологию учета запасов.

Если в учетной политике предприятия выбран способ оценки материально-производственных запасов (МПЗ) «По средней стоимости», то при проведении документа «Требование-накладная» в течение месяца рассчитывается лишь предварительная (средняя скользящая) стоимость списания по формуле:

Стоимость списания = (Стоимость остатка на складе на момент проведения) / (Количество остатка на складе на момент проведения)

Эта сумма не является окончательной финансовой стоимостью списания сырья! Реальная средневзвешенная себестоимость за календарный месяц рассчитывается только в конце отчетного периода специальным регламентным документом «Регламентная операция» с видом операции «Корректировка стоимости списания» (выполняется в рамках обработки «Закрытие месяца»). Этот документ делает дополнительные корректирующие проводки в дебет затратных счетов с кредита 10-х счетов, корректируя сумму в плюс или минус.

Что это означает для программиста:

---

Краткие выводы и чек-лист для разработчика

Для закрепления материала сформируем краткий чек-лист по работе с суммами списания материалов в 1С:

  1. Никогда не соединяйте табличные части документов напрямую с физической таблицей регистра бухгалтерии Хозрасчетный — это приведет к дублированию строк и неверным суммам.
  2. Для получения сумм используйте виртуальные таблицы Обороты или ДвиженияССубконто.
  3. При использовании виртуальных таблиц всегда задавайте параметры-фильтры (период, счета учета, список номенклатуры) внутри скобок самой таблицы, а не в секции ГДЕ.
  4. Всегда типизируйте поля субконто с помощью функции ВЫРАЗИТЬ. Выполняйте типизацию только в первом пакете запроса при помещении данных во временную таблицу. Использование ВЫРАЗИТЬ в условиях связи (JOIN) вызывает ошибку СУБД.
  5. Помните, что окончательная себестоимость материалов формируется только после выполнения процедуры «Закрытие месяца».
← На главную