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

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

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

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

Причина проблемы: почему запрос выбирает лишние данные

Давайте проанализируем структуру хранения данных в 1С. Любой документ, имеющий табличную часть, на уровне базы данных (MS SQL Server, PostgreSQL и др.) представляется в виде нескольких таблиц. Первая таблица — это шапка документа (содержит реквизиты самого документа: дату, номер, контрагента и т.д.). Вторая и последующие таблицы хранят строки его табличных частей. Эти таблицы ТЧ содержат служебную колонку Ссылка, которая связывает каждую строку с конкретным документом-владельцем.

Если мы пишем запрос к табличной части без секции ГДЕ, платформа 1С транслирует это в SQL-запрос, который считывает абсолютно все записи из физической таблицы ТЧ. Чтобы получить данные только "нашего" документа, нам необходимо жестко указать условие отбора по полю Ссылка.

Решение 1. Классический запрос с использованием параметра Ссылка

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

Рассмотрим пример кода, который решает эту задачу:


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

Запрос.УстановитьПараметр("Ссылка", Объект.Ссылка);

ТаблицаРезультата = Запрос.Выполнить().Выгрузить();
Объект.НачислениеПремии.Загрузить(ТаблицаРезультата);

Давайте разберем этот код по шагам, чтобы понять логику его работы:

  1. В секции ИЗ мы указываем источник данных — табличную часть РесурсыПредприятия документа ЗаказНаПроизводство. Для удобства таблице назначен псевдоним ЗаказНаПроизводствоРесурсы.
  2. В секции ГДЕ мы прописываем условие фильтрации: ЗаказНаПроизводствоРесурсы.Ссылка = &Ссылка. Символ амперсанда (&) указывает системе, что слово Ссылка — это именованный параметр, значение которого будет передано позже.
  3. С помощью метода УстановитьПараметр() мы связываем параметр "Ссылка" в тексте запроса с реальным значением реквизита Объект.Ссылка текущего документа.
  4. Метод Выполнить().Выгрузить() выполняет запрос и возвращает результат в виде объекта ТаблицаЗначений.
  5. Метод Загрузить() заполняет другую табличную часть (НачислениеПремии) полученными данными, автоматически сопоставляя имена колонок.

Важный нюанс: Проблема несохраненных и измененных данных

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

Из-за этого архитектурного поведения платформы возникают две серьезные проблемы:

Как же решить эту проблему, если нам обязательно нужно использовать запрос (например, для соединения табличной части с другими таблицами или регистрами), но при этом учесть текущие, еще не сохраненные изменения пользователя?

Решением является передача ТЧ в запрос в качестве временной таблицы. Мы берем данные из оперативной памяти клиента/сервера, выгружаем их в таблицу значений и передаем в менеджер временных таблиц запроса:


// 1. Выгружаем данные табличной части из оперативной памяти в Таблицу Значений
ТЗ_Ресурсы = Объект.РесурсыПредприятия.Выгрузить();

// 2. Создаем запрос и менеджер временных таблиц
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;

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

Запрос.УстановитьПараметр("ТаблицаРесурсов", ТЗ_Ресурсы);

// Выполняем пакет запросов (помещение в ВТ)
Запрос.Выполнить();

// 4. Теперь мы можем работать с ВТ_Ресурсы, соединяя ее с любыми таблицами СУБД
// Например, выберем данные из временной таблицы
Запрос.Текст = 
    "ВЫБРАТЬ
    |	ВТ_Ресурсы.Сотрудник,
    |	ВТ_Ресурсы.ПроцентПремии
    |ИЗ
    |	ВТ_Ресурсы КАК ВТ_Ресурсы";

РезультатЗапроса = Запрос.Выполнить().Выгрузить();
Объект.НачислениеПремии.Загрузить(РезультатЗапроса);

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

Решение 2. Когда запрос вообще не нужен (работа в оперативной памяти)

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

Гораздо проще и производительнее обработать данные в оперативной памяти. Рассмотрим два способа реализации.

Вариант А. Прямой обход коллекции в цикле

Этот вариант идеален, если имена колонок в табличных частях отличаются, или требуется дополнительная логика обработки строк "на лету":


// Очищаем целевую ТЧ перед заполнением
Объект.НачислениеПремии.Очистить();

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

Вариант Б. Использование методов Выгрузить() и Загрузить()

Если имена колонок и их типы в обеих табличных частях полностью совпадают, перенос данных можно выполнить буквально в одну строку:

Объект.НачислениеПремии.Загрузить(Объект.РесурсыПредприятия.Выгрузить());

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

Анализ синтаксических ошибок из обсуждения

Давайте проанализируем частые ошибки, которые совершают программисты при реализации подобных задач.

Ошибка 1. Попытка сравнения с Объект.Ссылка внутри текста запроса.
В Сообщении 9 упоминается неудачная попытка написать условие вида: ЗаказНаПроизводствоРесурсыПредприятия.Ссылка = Объект.Ссылка непосредственно в теле запроса. Почему это вызывает ошибку компиляции? Текст запроса — это строка, передаваемая СУБД. База данных ничего не знает о контексте исполнения кода в 1С и переменных вроде Объект.Ссылка. Для передачи значений из кода 1С в СУБД используются исключительно параметры, предваряемые символом &.

Ошибка 2. Опечатка в проверке на пустую ссылку.
В Сообщении 8 предлагается проверка: Если Не Объект.Ссылка.Пустая Тогда. Встроенный язык 1С жестко требует скобок при вызове методов объектов. Метод Пустая() — это функция ссылочного типа, поэтому правильный синтаксис выглядит так:


Если Не Объект.Ссылка.Пустая() Тогда // Обязательны круглые скобки
    // Код выполнения запроса
КонецЕсли;

Если код выполняется в модуле самого объекта (а не в форме или внешней обработке), для проверки новизны документа правильнее и лаконичнее использовать метод:


Если Не ЭтоНовый() Тогда
    // Код выполнения запроса
КонецЕсли;

Контекст исполнения кода в Управляемых формах

Необходимо помнить, где именно выполняется ваш код. В современных управляемых формах 1С реквизит Объект имеет тип ДанныеФормыСтруктура, а его табличные части — тип ДанныеФормыКоллекция.

На клиенте (в процедурах с директивой &НаКлиенте) выполнять запросы категорически запрещено платформой. Любые манипуляции с классом Запрос должны происходить строго на сервере (в процедурах &НаСервере или &НаСервереБезКонтекста).

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

Альтернативный синтаксис: вложенные таблицы запросов

В платформе 1С существует еще один интересный синтаксис запросов к табличным частям — обращение к ним как к вложенным таблицам через объект-владелец. Этот способ можно применять, когда документ гарантированно записан в базу данных:


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

Запрос.УстановитьПараметр("Ссылка", Объект.Ссылка);
ВыборкаШапки = Запрос.Выполнить().Выбрать();

Пока ВыборкаШапки.Следующий() Цикл
    // Получаем вложенный результат запроса по ТЧ
    ВыборкаТЧ = ВыборкаШапки.ТаблицаРесурсов.Выбрать();
    Пока ВыборкаТЧ.Следующий() Цикл
        // Обработка строк табличной части
        ТекущийСотрудник = ВыборкаТЧ.Сотрудник;
        ТекущаяМощность = ВыборкаТЧ.ПроцентПремии;
    КонецЦикла;
КонецЦикла;

Этот подход возвращает одну строку документа (шапку), в одной из колонок которой находится полноценный вложенный результат выборки по строкам табличной части. Несмотря на элегантность синтаксиса, в повседневной практике для простых задач перегрузки данных чаще используют классический плоский запрос к ТЧ, рассмотренный в первом решении.

← На главную