Как передать массив в параметр запроса 1С и не потерять в скорости

· 9 мин

Содержание

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

Всё ниже относится к платформе 1С:Предприятие 8.3 и не зависит от конфигурации: одинаково работает в БП, ЗУП, УТ и в самописных базах. Примеры проверены на типовой логике языка запросов, имена объектов замените на свои.

Какие коллекции можно передать в параметр и как это записать

Параметр запроса принимает не только одиночное значение. Платформа понимает несколько видов коллекций, и от вида зависит, где их можно использовать в тексте.

Что передаёмГде работает в запросеКомментарий
Массив, ФиксированныйМассивв условии В (&Параметр), В ИЕРАРХИИ (&Параметр)самый частый вариант
СписокЗначенийтам жеудобно, если список уже есть на форме
ТаблицаЗначенийкак источник: ИЗ &Параметр КАК Тнужна типизация колонок

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

Минимальный рабочий пример:

МассивНоменклатуры = Новый Массив;
МассивНоменклатуры.Добавить(Товар1);
МассивНоменклатуры.Добавить(Товар2);

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

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

Имя параметра в УстановитьПараметр пишется без амперсанда, в тексте запроса — с ним. Регистр букв роли не играет.

💡 Массив часто получают из другого запроса. Не перебирайте выборку циклом: Запрос.Выполнить().Выгрузить().ВыгрузитьКолонку("Ссылка") вернёт готовый массив одной строкой.

Фильтрация оператором В: варианты условий

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

Варианты, которые встречаются в практике:

  • ✅ Поле В (&Список) — входит в список;
  • 🚫 НЕ Поле В (&Список) — исключить значения из списка;
  • 🌳 Поле В ИЕРАРХИИ (&Список) — для иерархических справочников: в выборку попадут переданные группы и всё их содержимое;
  • 🔗 (Поле1, Поле2) В (ВЫБРАТЬ ...) — сравнение пары полей с результатом вложенного запроса.

Последний вариант с массивом не работает : массив одномерный. Когда нужен фильтр по паре «номенклатура + склад», собирайте таблицу значений с двумя колонками и используйте временную таблицу (следующий раздел).

Фильтр внутри параметров виртуальной таблицы

Если читаете регистр через виртуальную таблицу (Остатки, ОстаткиИОбороты, СрезПоследних), ставьте условие с массивом в её параметры, а не в ГДЕ:

"ВЫБРАТЬ
|	Остатки.Номенклатура КАК Номенклатура,
|	Остатки.КоличествоОстаток КАК Остаток
|ИЗ
|	РегистрНакопления.ТоварыНаСкладах.Остатки(
|			&Дата,
|			Номенклатура В (&СписокТоваров)) КАК Остатки"

Так отбор применяется до расчёта остатков. При условии в ГДЕ система сначала посчитает остатки по всем товарам и только потом отбросит лишние. Имя регистра в примере условное, в вашей конфигурации оно своё.

Пустой массив в условии

Пустой массив в В (&Список) синтаксической ошибки не вызывает. Условие просто не выполнится ни для одной строки, и запрос вернёт пустой результат.

Чаще всего это не то, что ждёт пользователь. Если пустой отбор означает «без отбора», проверяйте это в коде до запроса:

Если МассивНоменклатуры.Количество() = 0 Тогда
	Запрос.Текст = СтрЗаменить(Запрос.Текст, "Номенклатура.Ссылка В (&СписокТоваров)", "ИСТИНА");
Иначе
	Запрос.УстановитьПараметр("СписокТоваров", МассивНоменклатуры);
КонецЕсли;

Временная таблица из массива для больших объёмов

Когда значений много, длинный список внутри SQL-запроса становится узким местом. Текст запроса к СУБД разрастается, оптимизатору сложнее строить план, а при повторном использовании того же списка в нескольких запросах он каждый раз передаётся заново.

Решение — превратить массив в таблицу значений, поместить её во временную таблицу с индексом и дальше соединяться с ней.

Шаг 1. Собрать типизированную таблицу значений

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

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

ТаблицаОтбора.Свернуть("Номенклатура");

Свернуть убирает дубли. В условии В дубли безвредны, а в соединении каждая повторённая строка умножит строки результата.

Колонке обязательно задайте тип через ОписаниеТипов. Колонка без типа в запросе не принимается.

Шаг 2. Поместить во временную таблицу и проиндексировать

МенеджерВТ = Новый МенеджерВременныхТаблиц;

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

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

Внутри одного пакета временная таблица доступна всем следующим запросам. Через МенеджерВременныхТаблиц она доступна и другим объектам Запрос, пока менеджер существует.

Шаг 3. Использовать таблицу в нескольких местах

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

"... ГДЕ Продажи.Номенклатура В (ВЫБРАТЬ ВТОтбор.Номенклатура ИЗ ВТОтбор КАК ВТОтбор)"

И даже в параметры виртуальной таблицы: Остатки(&Дата, Номенклатура В (ВЫБРАТЬ ВТОтбор.Номенклатура ИЗ ВТОтбор КАК ВТОтбор)).

💡 Когда временная таблица больше не нужна, удалите её запросом УНИЧТОЖИТЬ ВТОтбор или закройте менеджер методом Закрыть(). Особенно это важно в длинных обработках с большими таблицами: иначе они живут до конца работы менеджера.

Что выбрать: В со списком или временная таблица

Точного порога, после которого список «слишком большой», платформа не задаёт: многое зависит от СУБД, индексов и того, сколько раз список используется. Ниже — практический ориентир, а не норматив.

МетодКогда подходитМинусы и риски
В (&Массив)десятки и сотни значений, один запроспри тысячах значений растёт текст SQL и падает качество плана
В (&Массив) в параметрах виртуальной таблицыотбор по измерению регистратолько для полей, доступных в условии виртуальной таблицы
Временная таблица с ИНДЕКСИРОВАТЬ ПОтысячи значений, повторное использование, отбор по паре полейнужна типизированная таблица значений, чуть больше кода
Соединение с исходным запросом без массивасписок и так получается запросомтребует переписать логику: массив вообще не нужен

Последняя строка — самый недооценённый приём. Он не про синтаксис, а про архитектуру кода. Частый антипаттерн: первым запросом получить массив ссылок, выгрузить его в память, затем передать во второй запрос. Если оба запроса идут к базе, объедините их в один пакет через временную таблицу. Данные не будут гоняться между сервером СУБД и сервером 1С.

Как проверить на своей базе

Не доверяйте догадкам о скорости — замерьте:

  1. В конфигураторе включите замер производительности через меню отладки и выполните код с одним вариантом.
  2. Посмотрите строку с Запрос.Выполнить(): время и число вызовов.
  3. Повторите с другим вариантом на тех же данных.
  4. В клиент-серверной базе для детального разбора включите технологический журнал с событиями запросов к СУБД — там будет виден итоговый SQL-текст.

Сравнивайте на реальном объёме, а не на десяти тестовых элементах: на маленьких списках разница между методами не видна.

Составные типы и ссылки разных видов в массиве

Массив в 1С может хранить значения разных типов. Для запроса это допустимо: Субконто1 В (&Список), где в списке лежат и контрагенты, и физические лица, отработает корректно. Значение просто сравнивается с каждым элементом.

С таблицей значений строже. Если в колонке должны лежать ссылки нескольких видов, перечислите их все в описании типов:

ТаблицаОтбора.Колонки.Добавить("Объект",
	Новый ОписаниеТипов("СправочникСсылка.Контрагенты, СправочникСсылка.ФизическиеЛица"));

Попытка записать в колонку значение, тип которого не указан, не даст ошибки сразу: значение приведётся к типу колонки и превратится в пустую ссылку или Неопределено. Отбор после этого молча вернёт не те строки.

Строки, числа и даты

Для примитивных типов задайте квалификаторы:

  • 🔤 строки — с ограниченной длиной: Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(50)). Строку неограниченной длины в запросе нельзя сравнивать и индексировать;
  • 🔢 числа — с разрядностью: Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2));
  • 📅 даты — с указанием частей: Новый ОписаниеТипов("Дата", , , Новый КвалификаторыДаты(ЧастиДаты.Дата)).

Массив строк в В (&Список) передавать можно, например список кодов или артикулов. Но строковое сравнение чувствительно к лишним символам: пробел в начале кода, кириллическая «С» вместо латинской — и элемент не найдётся. Для отбора по кодам надёжнее сначала одним запросом получить ссылки, а потом фильтровать уже по ним. Заодно сразу видно, какие коды в базе не нашлись.

Пустые ссылки и NULL

Пустая ссылка — обычное значение, условие В с ней работает. А вот NULL в список не «попадает»: сравнение с NULL никогда не даёт истину. Если поле из левого соединения может быть NULL, оберните его в ЕСТЬNULL(Поле, ЗначениеПоУмолчанию) или вынесите проверку в отдельное условие ИЛИ Поле ЕСТЬ NULL.

Массив в параметре схемы компоновки данных

В СКД тот же приём настраивается без кода. На вкладке «Параметры» у нужного параметра установите флажок «Доступен список значений». После этого пользователь в отчёте сможет выбрать несколько значений, а в тексте набора данных используйте то же В (&Параметр). Из кода параметр со списком заполняют через КомпоновщикНастроек — передаётся СписокЗначений или массив.

Частые ошибки при передаче массива в запрос

Большинство проблем выдают себя одинаково: запрос либо не выполняется, либо возвращает пустой или неверный результат. Выберите симптом:

Диагностика: выберите симптом

Ещё одна ловушка — передача массива с клиента. Массив ссылок, собранный на клиенте, уходит на сервер при каждом вызове серверного метода. Для больших отборов собирайте его сразу на сервере, а с клиента передавайте только то, что выбрал пользователь.

Частые вопросы

Можно ли передать в В (&Параметр) таблицу значений целиком?
Для фильтра по одному полю надёжнее передать массив: ТаблицаЗначений.ВыгрузитьКолонку("Имя"). Таблицу значений используйте как источник временной таблицы — так она работает предсказуемо, в том числе для отбора по нескольким полям.
Есть ли ограничение на число элементов массива в параметре?
Жёсткого лимита со стороны языка запросов на практике не встретишь, но с ростом списка растёт и SQL-текст, а скорость падает. На тысячах значений переходите на временную таблицу с индексом.
Работает ли массив в условии соединения ПО?
Да, условие вида ПО Т1.Ссылка = Т2.Ссылка И Т2.Склад В (&Склады) допустимо. Но для читаемости и плана запроса такой отбор обычно лучше перенести в ГДЕ или в параметры виртуальной таблицы.
Чем ФиксированныйМассив отличается от Массива в запросе?
Для запроса ничем: оба передаются одинаково. Фиксированный массив нельзя изменить после создания, поэтому его удобно хранить в параметрах сеанса и повторно использованных значениях.
Можно ли передать массив массивов для отбора по двум полям?
Нет, вложенные массивы в условии В не сработают. Соберите таблицу значений с двумя типизированными колонками, поместите её во временную таблицу и используйте (Поле1, Поле2) В (ВЫБРАТЬ ...) или соединение.