Как ограничить запрос в 1С данными только одного текущего документа?

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

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

Способ 1. Фильтрация запроса по ссылке (классическое решение)

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

Для реализации этого подхода в текст запроса добавляется условие ПоступлениеТоваровУслугТовары.Ссылка = &Ссылка. Символ амперсанда (&) указывает на то, что Ссылка является параметром, значение которого будет передано динамически при выполнении кода.

Проанализируем исправленный вариант исходного кода:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	ПоступлениеТоваровУслугТовары.Номенклатура,
    |	ПоступлениеТоваровУслугТовары.СтатьяРасходов,
    |	ПоступлениеТоваровУслугТовары.АналитикаРасходов,
    |	ПоступлениеТоваровУслугТовары.СуммаВзаиморасчетов,
    |	ПоступлениеТоваровУслугТовары.Сделка
    |ИЗ
    |	Документ.ПоступлениеТоваровУслуг.Товары КАК ПоступлениеТоваровУслугТовары
    |ГДЕ
    |	ПоступлениеТоваровУслугТовары.Ссылка = &Ссылка
    |	И ПоступлениеТоваровУслугТовары.СтатьяРасходов.ВариантРаспределенияРасходов = &ВариантРаспределенияРасходов";

// Установка параметров запроса
Запрос.УстановитьПараметр("Ссылка", Ссылка); 
Запрос.УстановитьПараметр("ВариантРаспределенияРасходов", ПредопределенноеЗначение("Перечисление.ВариантыРаспределенияРасходов.НеРаспределять"));

Выборка = Запрос.Выполнить().Выбрать();

Движения.ДополнительныеРасходыПоСделке.Записывать = Истина;
Пока Выборка.Следующий() Цикл
    Движение = Движения.ДополнительныеРасходыПоСделке.Добавить();
    Движение.Период = Дата;
    Движение.Организация = Организация;
    Движение.Подразделение = Подразделение;
    Движение.Контрагент = Контрагент;
    Движение.Валюта = Валюта;
    Движение.СтатьяРасходов = Выборка.СтатьяРасходов;
    Движение.АналитикаРасходов = Выборка.АналитикаРасходов;
    Движение.Сделка = Выборка.Сделка;
    Движение.Сумма = Выборка.СуммаВзаиморасчетов;
КонецЦикла;

В данном примере переменная Ссылка (передаваемая в качестве параметра) представляет собой ссылку на текущий документ, в модуле которого выполняется этот код. Если код выполняется во внешней обработке, вместо Ссылка может использоваться реквизит формы или объекта, например, Объект.Ссылка.

Способ 2. Оптимальный метод 1С — Передача табличной части в качестве параметра

Хотя первый способ полностью работоспособен, у него есть два критических недостатка, о которых должен знать каждый профессиональный разработчик:

  1. Проблема незаписанного документа: Если пользователь создал новый документ, заполнил строки в табличной части, но еще ни разу не нажал кнопку «Сохранить» или «Записать», физически этого документа и его строк в базе данных еще нет. Запрос к таблице Документ.ПоступлениеТоваровУслуг.Товары вернет пустой результат.
  2. Неактуальные данные в транзакции: Если пользователь изменил количество или сумму в строках документа и сразу нажал кнопку «Провести», запрос к физической таблице базы данных может прочитать старые данные, сохраненные до изменения, а не актуальные данные из оперативной памяти.

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

Рассмотрим, как правильно реализовать этот подход:


Запрос = Новый Запрос;
// Передаем табличную часть текущего объекта напрямую в параметры
Запрос.УстановитьПараметр("ТабличнаяЧасть", Товары); 
Запрос.УстановитьПараметр("ВариантРаспределенияРасходов", ПредопределенноеЗначение("Перечисление.ВариантыРаспределенияРасходов.НеРаспределять"));

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

Выборка = Запрос.Выполнить().Выбрать();

Использование этого подхода гарантирует, что система обработает самые свежие данные, которые пользователь видит на экране своего монитора, даже если документ еще не был физически записан в базу данных.

Способ 3. Избавление от неявных соединений («разыменования ссылок»)

Обратите внимание на конструкцию в первоначальном коде: ПоступлениеТоваровУслугТовары.СтатьяРасходов.ВариантРаспределенияРасходов. В платформе 1С обращение через точку от ссылочного поля к его реквизитам называется неявным разыменованием.

На уровне СУБД (например, MS SQL Server или PostgreSQL) такое обращение автоматически преобразуется в левое соединение (LEFT JOIN). Если в документе содержится большое количество строк, а база данных имеет высокую нагрузку, такие неявные соединения могут серьезно замедлить выполнение запроса и привести к неоптимальным планам выполнения на сервере.

Для оптимизации производительности рекомендуется всегда использовать явное ВНУТРЕННЕЕ СОЕДИНЕНИЕ (как показано во втором примере кода). Мы соединяем временную таблицу товаров со справочником статей расходов по ключевому полю СтатьяРасходов. Это позволяет СУБД построить максимально быстрый и эффективный план выполнения запроса.

Способ 4. Простой обход в цикле (альтернатива для небольших документов)

Если перед вами стоит задача простой проверки или переноса данных внутри документа, в котором гарантированно содержится небольшое количество строк (например, до 50–100 строк), и вам не нужно выполнять сложные группировки или расчеты, можно полностью отказаться от написания запроса. В этом случае достаточно использовать стандартный обход коллекции строк табличной части в цикле.

Проанализируем пример кода с использованием обычного обхода:


Движения.ДополнительныеРасходыПоСделке.Записывать = Истина;

Для Каждого СтрокаТЧ Из Товары Цикл
    
    // Получаем значение варианта распределения напрямую из реквизита ссылки
    ВариантРаспределения = СтрокаТЧ.СтатьяРасходов.ВариантРаспределенияРасходов;
    
    Если ВариантРаспределения = Перечисления.ВариантыРаспределенияРасходов.НеРаспределять Тогда
        
        Движение = Движения.ДополнительныеРасходыПоСделке.Добавить();
        Движение.Период = Дата;
        Движение.Организация = Организация;
        Движение.Подразделение = Подразделение;
        Движение.Контрагент = Контрагент;
        Движение.Валюта = Валюта;
        
        Движение.СтатьяРасходов = СтрокаТЧ.СтатьяРасходов;
        Движение.АналитикаРасходов = СтрокаТЧ.АналитикаРасходов;
        Движение.Сделка = СтрокаТЧ.Сделка;
        Движение.Сумма = СтрокаТЧ.СуммаВзаиморасчетов;
        
    КонецЕсли;
    
КонецЦикла;

Важное предостережение: Помните, что обращение к реквизитам через точку в цикле (например, СтрокаТЧ.СтатьяРасходов.ВариантРаспределенияРасходов) при работе с большим объемом данных может вызвать проблему "N+1 запросов к базе данных". Если строк в документе будет несколько тысяч, система выполнит тысячи отдельных мелких запросов к базе для получения реквизита каждой статьи расходов. В таких ситуациях всегда отдавайте предпочтение Способу №2.

← На главную