Как передать массив в параметр запроса 1С и не потерять в скорости
Содержание
Массив передают в параметр запроса 1С обычным Запрос.УстановитьПараметр("Список", Массив), а в тексте запроса пишут условие ГДЕ Поле В (&Список). Скобки вокруг параметра обязательны. Для сотен значений этого хватает. Если значений тысячи или фильтр нужен в нескольких запросах подряд, выгрузите их в таблицу значений и положите во временную таблицу с индексом.
Всё ниже относится к платформе 1С:Предприятие 8.3 и не зависит от конфигурации: одинаково работает в БП, ЗУП, УТ и в самописных базах. Примеры проверены на типовой логике языка запросов, имена объектов замените на свои.
Какие коллекции можно передать в параметр и как это записать
Параметр запроса принимает не только одиночное значение. Платформа понимает несколько видов коллекций, и от вида зависит, где их можно использовать в тексте.
| Что передаём | Где работает в запросе | Комментарий |
|---|---|---|
Массив, ФиксированныйМассив | в условии В (&Параметр), В ИЕРАРХИИ (&Параметр) | самый частый вариант |
СписокЗначений | там же | удобно, если список уже есть на форме |
ТаблицаЗначений | как источник: ИЗ &Параметр КАК Т | нужна типизация колонок |
Массив нельзя указать после ИЗ как источник данных. Источником может быть только таблица значений. Это главное отличие, которое определяет выбор метода дальше.
Минимальный рабочий пример:
МассивНоменклатуры = Новый Массив;
МассивНоменклатуры.Добавить(Товар1);
МассивНоменклатуры.Добавить(Товар2);
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка,
| Номенклатура.Наименование КАК Наименование
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Ссылка В (&СписокТоваров)";
Запрос.УстановитьПараметр("СписокТоваров", МассивНоменклатуры);
Выборка = Запрос.Выполнить().Выбрать();
Имя параметра в УстановитьПараметр пишется без амперсанда, в тексте запроса — с ним. Регистр букв роли не играет.
💡 Массив часто получают из другого запроса. Не перебирайте выборку циклом: Запрос.Выполнить().Выгрузить().ВыгрузитьКолонку("Ссылка") вернёт готовый массив одной строкой.
Фильтрация оператором В: варианты условий
Оператор В проверяет, входит ли значение поля в переданный список. На уровне СУБД платформа превращает это условие в SQL-конструкцию со списком значений, поэтому на небольших списках оно работает быстро и не требует дополнительных действий.
Варианты, которые встречаются в практике:
- ✅
Поле В (&Список)— входит в список; - 🚫
НЕ Поле В (&Список)— исключить значения из списка; - 🌳
Поле В ИЕРАРХИИ (&Список)— для иерархических справочников: в выборку попадут переданные группы и всё их содержимое; - 🔗
(Поле1, Поле2) В (ВЫБРАТЬ ...)— сравнение пары полей с результатом вложенного запроса.
Последний вариант с массивом не работает : массив одномерный. Когда нужен фильтр по паре «номенклатура + склад», собирайте таблицу значений с двумя колонками и используйте временную таблицу (следующий раздел).
Фильтр внутри параметров виртуальной таблицы
Если читаете регистр через виртуальную таблицу (Остатки, ОстаткиИОбороты, СрезПоследних), ставьте условие с массивом в её параметры, а не в ГДЕ:
"ВЫБРАТЬ
| Остатки.Номенклатура КАК Номенклатура,
| Остатки.КоличествоОстаток КАК Остаток
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки(
| &Дата,
| Номенклатура В (&СписокТоваров)) КАК Остатки"
Так отбор применяется до расчёта остатков. При условии в ГДЕ система сначала посчитает остатки по всем товарам и только потом отбросит лишние. Имя регистра в примере условное, в вашей конфигурации оно своё.
Пустой массив в условии
Пустой массив в В (&Список) синтаксической ошибки не вызывает. Условие просто не выполнится ни для одной строки, и запрос вернёт пустой результат.
Чаще всего это не то, что ждёт пользователь. Если пустой отбор означает «без отбора», проверяйте это в коде до запроса:
Если МассивНоменклатуры.Количество() = 0 Тогда
Запрос.Текст = СтрЗаменить(Запрос.Текст, "Номенклатура.Ссылка В (&СписокТоваров)", "ИСТИНА");
Иначе
Запрос.УстановитьПараметр("СписокТоваров", МассивНоменклатуры);
КонецЕсли;
Временная таблица из массива для больших объёмов
Когда значений много, длинный список внутри SQL-запроса становится узким местом. Текст запроса к СУБД разрастается, оптимизатору сложнее строить план, а при повторном использовании того же списка в нескольких запросах он каждый раз передаётся заново.
Решение — превратить массив в таблицу значений, поместить её во временную таблицу с индексом и дальше соединяться с ней.
Шаг 1. Собрать типизированную таблицу значений
ТаблицаОтбора = Новый ТаблицаЗначений;
ТаблицаОтбора.Колонки.Добавить("Номенклатура",
Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
Для Каждого Элемент Из МассивНоменклатуры Цикл
ТаблицаОтбора.Добавить().Номенклатура = Элемент;
КонецЦикла;
ТаблицаОтбора.Свернуть("Номенклатура");
Свернуть убирает дубли. В условии В дубли безвредны, а в соединении каждая повторённая строка умножит строки результата.
Колонке обязательно задайте тип через ОписаниеТипов. Колонка без типа в запросе не принимается.
Шаг 2. Поместить во временную таблицу и проиндексировать
МенеджерВТ = Новый МенеджерВременныхТаблиц;
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = МенеджерВТ;
Запрос.Текст =
"ВЫБРАТЬ
| Т.Номенклатура КАК Номенклатура
|ПОМЕСТИТЬ ВТОтбор
|ИЗ
| &ТаблицаОтбора КАК Т
|
|ИНДЕКСИРОВАТЬ ПО
| Номенклатура
|;
|
|////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| Продажи.Номенклатура КАК Номенклатура,
| СУММА(Продажи.Сумма) КАК Сумма
|ИЗ
| ВТОтбор КАК ВТОтбор
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрНакопления.Продажи КАК Продажи
| ПО ВТОтбор.Номенклатура = Продажи.Номенклатура
|ГДЕ
| Продажи.Период МЕЖДУ &Начало И &Окончание
|
|СГРУППИРОВАТЬ ПО
| Продажи.Номенклатура";
Запрос.УстановитьПараметр("ТаблицаОтбора", ТаблицаОтбора);
Запрос.УстановитьПараметр("Начало", НачалоПериода);
Запрос.УстановитьПараметр("Окончание", КонецПериода);
Результат = Запрос.Выполнить();
Внутри одного пакета временная таблица доступна всем следующим запросам. Через МенеджерВременныхТаблиц она доступна и другим объектам Запрос, пока менеджер существует.
Шаг 3. Использовать таблицу в нескольких местах
Временную таблицу можно подставлять не только в соединение, но и во вложенный запрос:
"... ГДЕ Продажи.Номенклатура В (ВЫБРАТЬ ВТОтбор.Номенклатура ИЗ ВТОтбор КАК ВТОтбор)"
И даже в параметры виртуальной таблицы: Остатки(&Дата, Номенклатура В (ВЫБРАТЬ ВТОтбор.Номенклатура ИЗ ВТОтбор КАК ВТОтбор)).
💡 Когда временная таблица больше не нужна, удалите её запросом УНИЧТОЖИТЬ ВТОтбор или закройте менеджер методом Закрыть(). Особенно это важно в длинных обработках с большими таблицами: иначе они живут до конца работы менеджера.
Что выбрать: В со списком или временная таблица
Точного порога, после которого список «слишком большой», платформа не задаёт: многое зависит от СУБД, индексов и того, сколько раз список используется. Ниже — практический ориентир, а не норматив.
| Метод | Когда подходит | Минусы и риски |
|---|---|---|
В (&Массив) | десятки и сотни значений, один запрос | при тысячах значений растёт текст SQL и падает качество плана |
В (&Массив) в параметрах виртуальной таблицы | отбор по измерению регистра | только для полей, доступных в условии виртуальной таблицы |
Временная таблица с ИНДЕКСИРОВАТЬ ПО | тысячи значений, повторное использование, отбор по паре полей | нужна типизированная таблица значений, чуть больше кода |
| Соединение с исходным запросом без массива | список и так получается запросом | требует переписать логику: массив вообще не нужен |
Последняя строка — самый недооценённый приём. Он не про синтаксис, а про архитектуру кода. Частый антипаттерн: первым запросом получить массив ссылок, выгрузить его в память, затем передать во второй запрос. Если оба запроса идут к базе, объедините их в один пакет через временную таблицу. Данные не будут гоняться между сервером СУБД и сервером 1С.
Как проверить на своей базе
Не доверяйте догадкам о скорости — замерьте:
- В конфигураторе включите замер производительности через меню отладки и выполните код с одним вариантом.
- Посмотрите строку с
Запрос.Выполнить(): время и число вызовов. - Повторите с другим вариантом на тех же данных.
- В клиент-серверной базе для детального разбора включите технологический журнал с событиями запросов к СУБД — там будет виден итоговый SQL-текст.
Сравнивайте на реальном объёме, а не на десяти тестовых элементах: на маленьких списках разница между методами не видна.
Составные типы и ссылки разных видов в массиве
Массив в 1С может хранить значения разных типов. Для запроса это допустимо: Субконто1 В (&Список), где в списке лежат и контрагенты, и физические лица, отработает корректно. Значение просто сравнивается с каждым элементом.
С таблицей значений строже. Если в колонке должны лежать ссылки нескольких видов, перечислите их все в описании типов:
ТаблицаОтбора.Колонки.Добавить("Объект",
Новый ОписаниеТипов("СправочникСсылка.Контрагенты, СправочникСсылка.ФизическиеЛица"));
Попытка записать в колонку значение, тип которого не указан, не даст ошибки сразу: значение приведётся к типу колонки и превратится в пустую ссылку или Неопределено. Отбор после этого молча вернёт не те строки.
Строки, числа и даты
Для примитивных типов задайте квалификаторы:
- 🔤 строки — с ограниченной длиной:
Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(50)). Строку неограниченной длины в запросе нельзя сравнивать и индексировать; - 🔢 числа — с разрядностью:
Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2)); - 📅 даты — с указанием частей:
Новый ОписаниеТипов("Дата", , , Новый КвалификаторыДаты(ЧастиДаты.Дата)).
Массив строк в В (&Список) передавать можно, например список кодов или артикулов. Но строковое сравнение чувствительно к лишним символам: пробел в начале кода, кириллическая «С» вместо латинской — и элемент не найдётся. Для отбора по кодам надёжнее сначала одним запросом получить ссылки, а потом фильтровать уже по ним. Заодно сразу видно, какие коды в базе не нашлись.
Пустые ссылки и NULL
Пустая ссылка — обычное значение, условие В с ней работает. А вот NULL в список не «попадает»: сравнение с NULL никогда не даёт истину. Если поле из левого соединения может быть NULL, оберните его в ЕСТЬNULL(Поле, ЗначениеПоУмолчанию) или вынесите проверку в отдельное условие ИЛИ Поле ЕСТЬ NULL.
Массив в параметре схемы компоновки данных
В СКД тот же приём настраивается без кода. На вкладке «Параметры» у нужного параметра установите флажок «Доступен список значений». После этого пользователь в отчёте сможет выбрать несколько значений, а в тексте набора данных используйте то же В (&Параметр). Из кода параметр со списком заполняют через КомпоновщикНастроек — передаётся СписокЗначений или массив.
Частые ошибки при передаче массива в запрос
Большинство проблем выдают себя одинаково: запрос либо не выполняется, либо возвращает пустой или неверный результат. Выберите симптом:
В &Список. Должно быть В (&Список). Без скобок разбор текста запроса не проходит.УстановитьПараметр не совпадает с именем в тексте (опечатка, лишний амперсанд) или установка параметра стоит после Выполнить(). Сверьте имена и порядок строк.Количество() и тип первого элемента через ТипЗнч.ОписаниеТипов с квалификаторами.Свернуть или выберите с РАЗЛИЧНЫЕ.В. Переведите отбор на индексированную временную таблицу или в параметры виртуальной таблицы.Ещё одна ловушка — передача массива с клиента. Массив ссылок, собранный на клиенте, уходит на сервер при каждом вызове серверного метода. Для больших отборов собирайте его сразу на сервере, а с клиента передавайте только то, что выбрал пользователь.
Частые вопросы
Можно ли передать в В (&Параметр) таблицу значений целиком?
ТаблицаЗначений.ВыгрузитьКолонку("Имя"). Таблицу значений используйте как источник временной таблицы — так она работает предсказуемо, в том числе для отбора по нескольким полям.Есть ли ограничение на число элементов массива в параметре?
Работает ли массив в условии соединения ПО?
ПО Т1.Ссылка = Т2.Ссылка И Т2.Склад В (&Склады) допустимо. Но для читаемости и плана запроса такой отбор обычно лучше перенести в ГДЕ или в параметры виртуальной таблицы.Чем ФиксированныйМассив отличается от Массива в запросе?
Можно ли передать массив массивов для отбора по двум полям?
В не сработают. Соберите таблицу значений с двумя типизированными колонками, поместите её во временную таблицу и используйте (Поле1, Поле2) В (ВЫБРАТЬ ...) или соединение.