Работа с массивами в запросах 1С — одна из тех тем, которые разделяет разработчиков на два лагеря: одни используют их ежедневно и не видят сложностей, другие годами обходятся без них, считая избыточным инструментом. Между тем, умение грамотно передавать массивы как параметры запросов открывает возможности для гибкой фильтрации данных, динамического формирования условий и даже оптимизации производительности в сложных отчетах. Но здесь кроется и главная ловушка: неверное применение массивов может не просто замедлить выполнение запроса, а привести к непредсказуемым ошибкам или некорректным результатам.
В этой статье мы разберём три критических аспекта работы с массивами в запросах 1С, которые редко освещают в стандартной документации: как именно платформа интерпретирует массивы в условиях ГДЕ, почему иногда лучше использовать временные таблицы вместо массивов, и как обойти ограничения при работе с большими наборами данных (10 000+ элементов). Особое внимание уделим типичным ошибкам, которые допускают даже опытные программисты — например, попытке передать массив объектов вместо массива значений или игнорированию особенностей сравнения NULL в условиях.
1. Синтаксис передачи массива в запрос: базовые правила
Начнём с самого простого: как технически передать массив в запрос. В 1С:Предприятие 8.3 для этого используется конструкция В() (или IN в англоязычной нотации), которая позволяет указать список значений для сравнения. Например, чтобы выбрать документы с определёнными номерами, можно написать:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Наименование КАК Наименование
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Ссылка В(&МассивСсылок)";
МассивСсылок = Новый Массив;
МассивСсылок.Добавить(Справочники.Номенклатура.НайтиПоНаименованию("Стул"));
МассивСсылок.Добавить(Справочники.Номенклатура.НайтиПоНаименованию("Стол"));
Запрос.УстановитьПараметр("МассивСсылок", МассивСсылок);
Результат = Запрос.Выполнить();
Важно понимать, что платформа автоматически преобразует массив в список значений при передаче в запрос. Это означает, что:
- 🔹 Массив должен содержать данные одного типа (например, только ссылки, только строки или только числа). Смешанные типы приводят к ошибке.
- 🔹 Если массив пуст, условие
В()вернётЛОЖЬдля всех строк (т.е. результат будет пустым). Это поведение отличается от SQL, где пустойIN ()часто считается синтаксической ошибкой. - 🔹 Для массивов объектов (например,
Массив Из ДокументОбъект.СтрокаТабличнойЧасти) требуется предварительное извлечение значений или ссылок.
⚠️ Внимание: При передаче массива ссылок на объекты убедитесь, что все элементы принадлежат одной базе данных. Попытка использовать ссылки из другой базы (например, через RAS или HTTP-сервисы) приведёт к ошибке выполнения запроса.
2. Массив vs временная таблица: когда что использовать
Один из самых частых вопросов: стоит ли передавать массив напрямую в запрос или лучше загрузить его во временную таблицу? Ответ зависит от трёх факторов:
| Критерий | Массив в условии В() |
Временная таблица |
|---|---|---|
| Размер данных | До 1 000 элементов | От 1 000 элементов |
| Производительность | Быстрее для маленьких наборов | Эффективнее при сложных соединениях |
| Гибкость | Ограничен условия ГДЕ |
Позволяет использовать СОЕДИНЕНИЕ, агрегацию |
| Память | Низкое потребление | Высокое при больших объёмах |
Например, если вам нужно отфильтровать документы по списку контрагентов (50 элементов), массив будет оптимальным решением. Но если этот же список используется в нескольких соединениях или требуется агрегировать данные по группам из массива, временная таблица станет более удачным выбором.
Рассмотрим практический пример с временной таблицей:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Товары.Наименование,
| Товары.Цена
|ИЗ
| Документ.ПоступлениеТоваров.Товары КАК Товары
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТКакТаблица КАК Фильтр
| ПО Товары.Номенклатура = Фильтр.Ссылка";
// Создаём временную таблицу
ВТ = Новый ВременнаяТаблица;
ВТ.Колонки.Добавить("Ссылка", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"));
// Заполняем данными из массива
Для Каждого Элемент Из МассивНоменклатуры Цикл
Строка = ВТ.Добавить();
Строка.Ссылка = Элемент;
КонецЦикла;
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
Запрос.МенеджерВременныхТаблиц.ДобавитьВременнуюТаблицу(ВТ, "ВТКакТаблица");
Результат = Запрос.Выполнить();
3. Типичные ошибки и как их избежать
Даже опытные разработчики иногда сталкиваются с неожиданными проблемами при работе с массивами в запросах. Вот три самых распространённых ошибки и способы их решения:
- 🚨 Ошибка типа данных: Попытка передать в запрос массив, содержащий разные типы (например, строки и числа). Решение — предварительная проверка типов или приведение к общему типу с помощью
ЗначениеВТип(). - 🚨 Пустой массив: Запрос с условием
В(&ПустойМассив)вернёт пустой результат, что может быть неочевидно. Решение — явная проверкаМассив.Количество() > 0перед выполнением. - 🚨 NULL-значения: Если массив содержит
NULL, сравнениеВ()может работать некорректно. Решение — явная обработкаЕСТЬNULLили фильтрация массива перед передачей.
Особенно коварна ошибка с NULL. Например, следующий код может вернуть неожиданные результаты:
Массив = Новый Массив;
Массив.Добавить(Неопределено); // NULL
Массив.Добавить(Справочники.Номенклатура.ПустаяСсылка());
Запрос.Текст = "ВЫБРАТЬ ... ГДЕ Ссылка В(&Массив)";
// В результате могут быть исключены даже не-NULL значения!
⚠️ Внимание: При работе с массивами ссылок на объекты (например, документы или справочники) всегда проверяйте, что ссылки существуют в базе. Передача несуществующих ссылок не вызовет ошибку, но может исказить результаты запроса.
Почему запрос с массивом работает медленно?
Если массив содержит более 1 000 элементов, платформа 1С может оптимизировать его обработку неэффективно. В этом случае временная таблица или разбиение массива на части (по 500-800 элементов) даст значительный прирост производительности.
4. Оптимизация производительности: массивы и большие данные
Когда речь идёт о массивах с тысячами элементов, стандартный подход с В() перестаёт быть эффективным. Платформа 1С:Предприятие может тратить значительное время на разбор такого условия, особенно если запрос выполняется на сервере с ограниченными ресурсами. Вот несколько приёмов для оптимизации:
- Разбиение массива: Если массив содержит 10 000 элементов, разбейте его на части по 1 000 и выполните несколько запросов с объединением результатов.
- Использование временных таблиц: Как показано ранее, загрузка массива во временную таблицу часто работает быстрее за счёт оптимизации движка запросов.
- Индексирование: Убедитесь, что поля, по которым идёт фильтрация массивом, проиндексированы в базе данных. Например, для справочников проверьте наличие индекса по полю
Ссылка.
Пример разбиения массива на части:
РезультатОбщий = Новый ТаблицаЗначений;
РазмерЧасти = 1000;
Для Позиция = 0 По Массив.ВГраница() Шаг РазмерЧасти Цикл
ЧастьМассива = Массив.ПолучитьОбъект(Позиция, Минимальное(Позиция + РазмерЧасти, Массив.ВГраница()));
Запрос.УстановитьПараметр("Часть", ЧастьМассива);
ЧастьРезультата = Запрос.Выполнить().Выгрузить();
РезультатОбщий.Объединить(ЧастьРезультата);
КонецЦикла;
Ещё один нюанс: если вы работаете с управляемыми формами и передаёте массив из клиентской части на сервер, убедитесь, что данные передаются по значению, а не по ссылке. В противном случае может возникнуть избыточный сетевой трафик:
// На клиенте:
Параметры = Новый Структура("Массив", Массив.Скопировать());
// На сервере:
Запрос.УстановитьПараметр("Массив", Параметры.Массив);
Для массивов числовых значений (например, ID документов) рассмотрите возможность использования диапазонов вместо точных совпадений. Например, условие ГДЕ Номер МЕЖДУ 1000 И 2000 выполнится быстрее, чем ГДЕ Номер В(&МассивИз1000Чисел).
5. Продвинутые техники: динамическое формирование запросов
Иногда требуется не просто передать массив в запрос, а динамически формировать сам запрос на основе содержимого массива. Например, если нужно построить условие с разными операторами для разных элементов. Для этого можно использовать СтрокаЗапроса и функции работы со строками.
Пример: формирование условия ГДЕ с разными операторами (=, <>) для элементов массива:
Условия = Новый Массив;
Для Каждого Элемент Из МассивУсловий Цикл
Если Элемент.Тип = ТипУсловия.Равно Тогда
Условия.Добавить(СтрШаблон("Поле = %1", Элемент.Значение));
ИначеЕсли Элемент.Тип = ТипУсловия.НеРавно Тогда
Условия.Добавить(СтрШаблон("Поле <> %1", Элемент.Значение));
КонецЕсли;
КонецЦикла;
ТекстЗапроса = "ВЫБРАТЬ ... ГДЕ " + СтрСоединить(Условия, " И ");
Запрос.Текст = ТекстЗапроса;
Однако такой подход требует осторожности:
- 🔧 Всегда экранируйте значения, чтобы избежать SQL-инъекций (даже в 1С это актуально при работе с внешними СУБД).
- 🔧 Проверяйте длину итогового запроса — некоторые СУБД (например, PostgreSQL) имеют ограничения на длину SQL-запроса.
- 🔧 Для сложных условий рассмотрите использование
ПОМЕСТИТЬ ВТвместо динамического формирования текста.
Экранированы все специальные символы в значениях|Проверена длина итогового запроса|Учтёны ограничения СУБД на количество параметров|Протестировано на пустом массиве-->
6. Альтернативные подходы: когда массивы не подходят
Иногда передача массива в запрос — не лучшее решение. Рассмотрим альтернативы:
- Подзапросы: Если массив формируется на основе данных из базы, можно заменить его подзапросом. Например, вместо передачи массива ссылок на контрагентов используйте:
ГДЕ Контрагент В (ВЫБРАТЬ КонтрагентыСписок.Ссылка
ИЗ Справочник.Контрагенты КАК КонтрагентыСписок
ГДЕ КонтрагентыСписок.ПометкаУдаления = ЛОЖЬ
)
- Глобальные параметры: Для часто используемых фильтров (например, "текущий пользователь") целесообразно завести константу или параметр сеанса вместо передачи массива.
- Внешние источники: Если данные хранятся во внешней системе (например, Excel или JSON), рассмотрите загрузку их во временную таблицу через
ЗагрузкаДанных.
Пример с подзапросом вместо массива:
Запрос.Текст =
"ВЫБРАТЬ
| Документы.Номер,
| Документы.Дата
|ИЗ
| Документ.РеализацияТоваровУслуг КАК Документы
|ГДЕ
| Документы.Контрагент В (
| ВЫБРАТЬ Контрагенты.Ссылка
| ИЗ Справочник.Контрагенты КАК Контрагенты
| ГДЕ Контрагенты.Группа = &ГруппаКонтрагентов
| )";
Запрос.УстановитьПараметр("ГруппаКонтрагентов", Справочники.ГруппыКонтрагентов.Оптовики);
Результат = Запрос.Выполнить();
⚠️ Внимание: Подзапросы могут значительно замедлить выполнение, если внутренний запрос возвращает большое количество строк. В таких случаях временная таблица часто оказывается эффективнее.
7. Особенности работы в разных версиях 1С
Поведение массивов в запросах может отличаться в зависимости от версии платформы и конфигурации. Вот ключевые различия:
| Версия платформы | Особенности работы с массивами |
|---|---|
| 8.2 | Ограничение на размер массива в условии В() — до 1 000 элементов. Превышение приводит к ошибке. |
| 8.3.6–8.3.12 | Поддержка массивов до 10 000 элементов, но производительность падает после 1 000. |
| 8.3.13+ | Оптимизированная обработка больших массивов, поддержка МАССИВ() в тексте запроса. |
| Управляемые формы | При передаче массива с клиента на сервер требуется явное копирование (.Скопировать()). |
В версиях 8.3.13 и новее появилась возможность использовать функцию МАССИВ() непосредственно в тексте запроса, что упрощает работу с небольшими статическими наборами данных:
Запрос.Текст =
"ВЫБРАТЬ
| Товары.Наименование
|ИЗ
| Справочник.Номенклатура КАК Товары
|ГДЕ
| Товары.ВидНоменклатуры В МАССИВ(Перечисление.ВидыНоменклатуры.Товар, Перечисление.ВидыНоменклатуры.Услуга)";
Для управляемых приложений также важно помнить о контексте выполнения: если массив формируется на клиенте, его нужно явным образом передавать на сервер, иначе он будет недоступен в серверных процедурах.
В версиях 8.3.13+ для статических массивов удобно использовать функцию МАССИВ() прямо в тексте запроса, что избавляет от необходимости передавать параметры.
FAQ: Частые вопросы по работе с массивами в запросах 1С
Можно ли передать в запрос массив структур или объектов?
Нет, напрямую — нельзя. Запрос 1С работает только с простыми типами данных (числа, строки, даты, ссылки, булевы значения). Если вам нужно отфильтровать данные по сложному критерию (например, по нескольким полям объекта), предварительно извлеките нужные значения в массив простых типов или используйте временную таблицу.
Пример ошибки:
МассивОбъектов = Новый Массив;
МассивОбъектов.Добавить(Документы.ЗаказПокупателя.СоздатьДокумент());
Запрос.УстановитьПараметр("Объекты", МассивОбъектов); // Ошибка!
Почему запрос с массивом из 5 000 элементов выполняется очень долго?
Это связано с особенностями оптимизации запросов в 1С. При большом количестве элементов в условии В() платформа может:
- 🔍 Не использовать индексы (переключаться на полное сканирование таблицы).
- 🔍 Формировать чрезмерно сложный план выполнения.
Решения:
- Разбейте массив на части по 500–1 000 элементов и выполните несколько запросов.
- Загрузите массив во временную таблицу и используйте
СОЕДИНЕНИЕ. - Проверьте наличие индексов на фильтруемых полях.
Как передать массив дат в запрос?
Массив дат передаётся так же, как и любой другой массив простых типов. Главное — убедиться, что все элементы имеют тип Дата. Пример:
МассивДат = Новый Массив;
МассивДат.Добавить(Дата(2023, 1, 1));
МассивДат.Добавить(Дата(2023, 1, 15));
Запрос.Текст = "ВЫБРАТЬ ... ГДЕ Дата ДОКУМЕНТА В(&МассивДат)";
Запрос.УстановитьПараметр("МассивДат", МассивДат);
Обратите внимание: если в массиве есть Неопределено или пустые даты, они будут проигнорированы при сравнении.
Можно ли использовать массив в условии ПОДОБНО?
Нет, конструкция В() работает только с точными совпадениями (=). Для условия ПОДОБНО придётся либо:
- 🔠 Формировать динамический текст запроса с многократным
ИЛИ. - 🔠 Использовать временную таблицу с последующим соединением.
Пример с динамическим формированием:
УсловияПоиска = Новый Массив;
Для Каждого Шаблон Из МассивШаблонов Цикл
УсловияПоиска.Добавить(СтрШаблон("Поле ПОДОБНО '%%1%%'", Шаблон));
КонецЦикла;
Запрос.Текст = "ВЫБРАТЬ ... ГДЕ " + СтрСоединить(УсловияПоиска, " ИЛИ ");
Как передать массив в запрос, если он формируется в цикле?
Если массив формируется динамически (например, в цикле по табличной части документа), важно:
- Сначала полностью сформировать массив, а затем передавать его в запрос.
- Избегать изменения массива после передачи в запрос (это не повлияет на уже выполненный запрос, но может привести к логическим ошибкам).
Пример корректного подхода:
МассивФильтра = Новый Массив;
Для Каждого Строка Из Документ.Товары Цикл
Если Строка.Количество > 10 Тогда
МассивФильтра.Добавить(Строка.Номенклатура);
КонецЕсли;
КонецЦикла;
Запрос.УстановитьПараметр("Фильтр", МассивФильтра); // Массив уже полностью сформирован