Как правильно передать массив выделенных строк из таблицы формы в запрос 1С?

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

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

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

В чем заключаются ошибки исходного кода

Проанализируем фрагмент кода, вызвавший затруднение:


МассивСтрок = Новый Массив;
Для Каждого Строка Из ЭлементыФормы.ТаблицаЗаказов.ВыделенныеСтроки Цикл
    МассивСтрок.Добавить(Строка);    
КонецЦикла;

// ...
Запрос.УстановитьПараметр("Заказ", МассивСтрок);

В этом коде допущены сразу две принципиальные ошибки:

  1. Передача объекта строки вместо ссылки: коллекция ВыделенныеСтроки содержит строки табличного поля (объекты типа СтрокаТабличнойЧасти или строки таблицы значений). Передавая в массив сам элемент Строка, разработчик передает внутренний интерфейсный объект формы. База данных ничего не знает о строках экранной формы. Для фильтрации в запросе по полю Ссылка необходимо передавать именно ссылки на документы (тип ДокументСсылка.ЗаказПокупателя), хранящиеся в соответствующей колонке строки.
  2. Синтаксис сравнения с массивом в тексте запроса: в первых двух блоках объединения автор написал ЗаказПокупателяТовары.Ссылка В(&Заказ), а в третьем блоке (во вложенном запросе) ошибочно указал оператор равенства: ЗаказПокупателяТовары.Ссылка = &Заказ. Оператор = в языке запросов 1С рассчитан только на одиночное значение. При передаче массива в условие со знаком равенства запрос либо выдаст синтаксическую ошибку, либо вернет неверный результат. Для списков и массивов всегда используется оператор В (&Параметр).

Шаг 1. Исправление сбора данных и параметров запроса

Выясним причину первой ошибки подробнее: если в точке останова отладчика проинспектировать элемент массива, видно выражение МассивСтрок[0].Заказ, где хранится ссылка на документ ДокументСсылка.ЗаказПокупателя. Следовательно, в массив нужно помещать значение конкретной колонки — Строка.Заказ.

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


МассивЗаказов = Новый Массив;

Для Каждого Строка Из ЭлементыФормы.ТаблицаЗаказов.ВыделенныеСтроки Цикл
    МассивЗаказов.Добавить(Строка.Заказ);    
КонецЦикла;

// Проверяем, выбрал ли пользователь хотя бы один заказ
Если МассивЗаказов.Количество() = 0 Тогда
    Предупреждение("Не выбрано ни одного заказа!");
    Возврат;
КонецЕсли;

Запрос = Новый Запрос;
// В тексте запроса обязательно во всех объединениях пишем:
// ГДЕ ЗаказПокупателяТовары.Ссылка В (&Заказ)

Запрос.УстановитьПараметр("Заказ", МассивЗаказов);

Обратим внимание на обязательную проверку МассивЗаказов.Количество() = 0. Если пользователь не выделил ни одной строки, массив будет пустым. При передаче пустого массива в конструкцию В (&Параметр) платформа 1С транслирует условие в СУБД как заведомую ложь (например, 1 = 0). Ошибки времени выполнения не произойдет, но запрос выполнится вхолостую и нагрузит сервер, поэтому выполнение кода лучше прерывать заранее.

Шаг 2. Исключение дубликатов в массиве

Если в таблице заказов один и тот же документ может встречаться в нескольких строках, то при выделении строк в массив попадут повторяющиеся ссылки. Чтобы избежать передачи лишних данных и возможных ошибок в расчетах, исключим дубликаты с помощью объекта Соответствие:


СоответствиеЗаказов = Новый Соответствие;

Для Каждого Строка Из ЭлементыФормы.ТаблицаЗаказов.ВыделенныеСтроки Цикл
    СоответствиеЗаказов.Вставить(Строка.Заказ, Истина);    
КонецЦикла;

МассивЗаказов = Новый Массив;
Для Каждого КлючИЗначение Из СоответствиеЗаказов Цикл
    МассивЗаказов.Добавить(КлючИЗначение.Ключ);
КонецЦикла;

Шаг 3. Альтернатива для всех строк таблицы

Разберем ситуацию, когда требуется обработать не просто выделенные пользователем строки, а вообще все строки таблицы, находящейся на форме обработки. В этом случае цикл обхода не требуется: у объекта ТаблицаЗначений или ТабличнаяЧасть есть встроенный метод ВыгрузитьКолонку(), который сразу возвращает одномерный массив значений:


// Если ТаблицаЗаказов — это таблица значений в реквизитах формы:
МассивЗаказов = ТаблицаЗаказов.ВыгрузитьКолонку("Заказ");
Запрос.УстановитьПараметр("Заказ", МассивЗаказов);

Шаг 4. Оптимизация: временная таблица вместо условия «В (&Параметр)»

Конструкция Ссылка В (&Заказ) отлично работает при небольшом количестве документов (до нескольких десятков или сотен). Однако при увеличении объема выборки возникают технические ограничения СУБД:

  1. В MS SQL Server установлено жесткое ограничение на количество параметров в одном запросе (не более 2100). Передача массива большего размера приведет к аварийному завершению запроса.
  2. Большой список литералов в операторе IN (...) приводит к тому, что оптимизатор СУБД тратит много ресурсов на компиляцию плана запроса и может отказаться от использования индекса по полю Ссылка.

Рассмотрим профессиональный подход: передачу таблицы значений во временную таблицу с последующим соединением.

Код подготовки таблицы на встроенном языке:


ТаблицаДляЗапроса = Новый ТаблицаЗначений;
ТаблицаДляЗапроса.Колонки.Добавить("Ссылка", Новый ОписаниеТипов("ДокументСсылка.ЗаказПокупателя"));

Для Каждого Строка Из ЭлементыФормы.ТаблицаЗаказов.ВыделенныеСтроки Цикл
    НоваяСтрока = ТаблицаДляЗапроса.Добавить();
    НоваяСтрока.Ссылка = Строка.Заказ;
КонецЦикла;

// Сворачиваем таблицу, чтобы гарантированно убрать дубликаты
ТаблицаДляЗапроса.Свернуть("Ссылка");

Запрос = Новый Запрос;
Запрос.УстановитьПараметр("ТаблицаЗаказов", ТаблицаДляЗапроса);

Текст запроса перестраиваем с использованием пакетного режима:


ВЫБРАТЬ
    Таблица.Ссылка КАК Ссылка
ПОМЕСТИТЬ Вт_ВыбранныеЗаказы
ИЗ
    &ТаблицаЗаказов КАК Таблица
ИНДЕКСИРОВАТЬ ПО
    Ссылка
;

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

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

Дополнительные рекомендации по качеству запроса

В исходном тексте запроса автора темы есть две скрытые проблемы, влияющие на производительность и надежность:

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