При разработке отчетов и обработок в 1С часто возникает задача: пользователь выделяет несколько строк в табличной части или табличном поле на форме, нажимает кнопку, и программа должна сформировать выборку данных именно по этим выбранным позициям. Однако начинающие разработчики нередко сталкиваются с ошибками или пустым результатом запроса при попытке передать выделенные строки напрямую в качестве параметра.
Разберем типичные ошибки в исходном коде, проанализируем причины их возникновения и рассмотрим несколько надежных способов передачи списка документов в запрос 1С.
Проанализируем фрагмент кода, вызвавший затруднение:
МассивСтрок = Новый Массив;
Для Каждого Строка Из ЭлементыФормы.ТаблицаЗаказов.ВыделенныеСтроки Цикл
МассивСтрок.Добавить(Строка);
КонецЦикла;
// ...
Запрос.УстановитьПараметр("Заказ", МассивСтрок);
В этом коде допущены сразу две принципиальные ошибки:
ВыделенныеСтроки содержит строки табличного поля (объекты типа СтрокаТабличнойЧасти или строки таблицы значений). Передавая в массив сам элемент Строка, разработчик передает внутренний интерфейсный объект формы. База данных ничего не знает о строках экранной формы. Для фильтрации в запросе по полю Ссылка необходимо передавать именно ссылки на документы (тип ДокументСсылка.ЗаказПокупателя), хранящиеся в соответствующей колонке строки.ЗаказПокупателяТовары.Ссылка В(&Заказ), а в третьем блоке (во вложенном запросе) ошибочно указал оператор равенства: ЗаказПокупателяТовары.Ссылка = &Заказ. Оператор = в языке запросов 1С рассчитан только на одиночное значение. При передаче массива в условие со знаком равенства запрос либо выдаст синтаксическую ошибку, либо вернет неверный результат. Для списков и массивов всегда используется оператор В (&Параметр).Выясним причину первой ошибки подробнее: если в точке останова отладчика проинспектировать элемент массива, видно выражение МассивСтрок[0].Заказ, где хранится ссылка на документ ДокументСсылка.ЗаказПокупателя. Следовательно, в массив нужно помещать значение конкретной колонки — Строка.Заказ.
Посмотрим на исправленный вариант заполнения массива и установки параметров:
МассивЗаказов = Новый Массив;
Для Каждого Строка Из ЭлементыФормы.ТаблицаЗаказов.ВыделенныеСтроки Цикл
МассивЗаказов.Добавить(Строка.Заказ);
КонецЦикла;
// Проверяем, выбрал ли пользователь хотя бы один заказ
Если МассивЗаказов.Количество() = 0 Тогда
Предупреждение("Не выбрано ни одного заказа!");
Возврат;
КонецЕсли;
Запрос = Новый Запрос;
// В тексте запроса обязательно во всех объединениях пишем:
// ГДЕ ЗаказПокупателяТовары.Ссылка В (&Заказ)
Запрос.УстановитьПараметр("Заказ", МассивЗаказов);
Обратим внимание на обязательную проверку МассивЗаказов.Количество() = 0. Если пользователь не выделил ни одной строки, массив будет пустым. При передаче пустого массива в конструкцию В (&Параметр) платформа 1С транслирует условие в СУБД как заведомую ложь (например, 1 = 0). Ошибки времени выполнения не произойдет, но запрос выполнится вхолостую и нагрузит сервер, поэтому выполнение кода лучше прерывать заранее.
Если в таблице заказов один и тот же документ может встречаться в нескольких строках, то при выделении строк в массив попадут повторяющиеся ссылки. Чтобы избежать передачи лишних данных и возможных ошибок в расчетах, исключим дубликаты с помощью объекта Соответствие:
СоответствиеЗаказов = Новый Соответствие;
Для Каждого Строка Из ЭлементыФормы.ТаблицаЗаказов.ВыделенныеСтроки Цикл
СоответствиеЗаказов.Вставить(Строка.Заказ, Истина);
КонецЦикла;
МассивЗаказов = Новый Массив;
Для Каждого КлючИЗначение Из СоответствиеЗаказов Цикл
МассивЗаказов.Добавить(КлючИЗначение.Ключ);
КонецЦикла;
Разберем ситуацию, когда требуется обработать не просто выделенные пользователем строки, а вообще все строки таблицы, находящейся на форме обработки. В этом случае цикл обхода не требуется: у объекта ТаблицаЗначений или ТабличнаяЧасть есть встроенный метод ВыгрузитьКолонку(), который сразу возвращает одномерный массив значений:
// Если ТаблицаЗаказов — это таблица значений в реквизитах формы:
МассивЗаказов = ТаблицаЗаказов.ВыгрузитьКолонку("Заказ");
Запрос.УстановитьПараметр("Заказ", МассивЗаказов);
Конструкция Ссылка В (&Заказ) отлично работает при небольшом количестве документов (до нескольких десятков или сотен). Однако при увеличении объема выборки возникают технические ограничения СУБД:
IN (...) приводит к тому, что оптимизатор СУБД тратит много ресурсов на компиляцию плана запроса и может отказаться от использования индекса по полю Ссылка.Рассмотрим профессиональный подход: передачу таблицы значений во временную таблицу с последующим соединением.
Код подготовки таблицы на встроенном языке:
ТаблицаДляЗапроса = Новый ТаблицаЗначений;
ТаблицаДляЗапроса.Колонки.Добавить("Ссылка", Новый ОписаниеТипов("ДокументСсылка.ЗаказПокупателя"));
Для Каждого Строка Из ЭлементыФормы.ТаблицаЗаказов.ВыделенныеСтроки Цикл
НоваяСтрока = ТаблицаДляЗапроса.Добавить();
НоваяСтрока.Ссылка = Строка.Заказ;
КонецЦикла;
// Сворачиваем таблицу, чтобы гарантированно убрать дубликаты
ТаблицаДляЗапроса.Свернуть("Ссылка");
Запрос = Новый Запрос;
Запрос.УстановитьПараметр("ТаблицаЗаказов", ТаблицаДляЗапроса);
Текст запроса перестраиваем с использованием пакетного режима:
ВЫБРАТЬ
Таблица.Ссылка КАК Ссылка
ПОМЕСТИТЬ Вт_ВыбранныеЗаказы
ИЗ
&ТаблицаЗаказов КАК Таблица
ИНДЕКСИРОВАТЬ ПО
Ссылка
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ЗаказПокупателяТовары.Номенклатура КАК Номенклатура,
СУММА(ЗаказПокупателяТовары.КоличествоКоробок) КАК КолКороб
ИЗ
Вт_ВыбранныеЗаказы КАК Вт_ВыбранныеЗаказы
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.ЗаказПокупателя.Товары КАК ЗаказПокупателяТовары
ПО Вт_ВыбранныеЗаказы.Ссылка = ЗаказПокупателяТовары.Ссылка
СГРУППИРОВАТЬ ПО
ЗаказПокупателяТовары.Номенклатура
Использование временной таблицы с ключевым словом ИНДЕКСИРОВАТЬ ПО и последующее ВНУТРЕННЕЕ СОЕДИНЕНИЕ гарантируют максимальную скорость выполнения независимо от количества выделенных строк.
В исходном тексте запроса автора темы есть две скрытые проблемы, влияющие на производительность и надежность:
ЗначенияСвойствОбъектов.Значение.ЕдиницаДляОтчетов.Объем идет через точку к реквизиту Значение регистра сведений. Поскольку реквизит Значение имеет составной тип (ПланВидовХарактеристик), платформа неявно строит левые соединения со всеми возможными таблицами типов. Необходимо использовать явное приведение типов:
ВЫРАЗИТЬ(ЗначенияСвойствОбъектов.Значение КАК Справочник.Номенклатура).ЕдиницаДляОтчетов.Объем
СГРУППИРОВАТЬ ПО ПРЕДСТАВЛЕНИЕ(ЗаказПокупателяТовары.Номенклатура) нарушает стандарты разработки 1С. Группировать данные следует строго по ссылкам (полю Номенклатура), а функцию ПРЕДСТАВЛЕНИЕ() вызывать только в итоговом списке полей блока ВЫБРАТЬ.