В процессе разработки на платформе 1С:Предприятие 8.3 часто возникает задача сопоставления данных, находящихся в оперативной памяти (в таблице значений), с данными в базе данных. Типичный сценарий: пользователь распределил товары или иные объекты по колонкам на форме, и теперь нам нужно программно подсчитать итоги или найти соответствия. Рассмотрим ситуацию, когда в таблице порядка 9 000 строк, и стандартный запрос начинает «тормозить».
Разберем подробно, почему классический подход через ВЫБОР КОГДА может работать медленно, и какие альтернативные методы позволят нам добиться мгновенного отклика системы.
Проанализируем ситуацию: когда мы передаем Таблицу Значений (ТЗ) в запрос в качестве параметра, платформа создает в СУБД временную таблицу. Если мы используем конструкцию типа СУММА(ВЫБОР КОГДА...), мы сталкиваемся со следующими техническими проблемами:
ВЫБОР СУБД вынуждена выполнять полное сканирование таблицы (Full Scan). При 9 000 строк и сопоставлении с тысячами записей в регистре количество операций поиска растет в геометрической прогрессии.Строка) сама по себе является ресурсоемкой. Если типы данных не совпадают (например, строка неограниченной длины в ТЗ против фиксированной длины в базе), СУБД может игнорировать даже существующие индексы основных таблиц.ВЫБОР КОГДА прописано неоптимально, SQL-сервер может строить неэффективный план запроса, пытаясь перебрать все возможные комбинации строк.Рассмотрим пример неоптимального кода, который приводит к зависаниям:
// Неоптимальный подход
"ВЫБРАТЬ
| СУММА(ВЫБОР КОГДА Регистр.Номенклатура.Наименование = &НаименованиеИзТЗ Тогда 1 ИНАЧЕ 0 КОНЕЦ) КАК Итог
|ИЗ
| РегистрСведений.ЦеныНоменклатуры КАК Регистр"
Если мы хотим оставить логику в запросе (что правильно с точки зрения клиент-серверной архитектуры), нам необходимо пересмотреть структуру запроса. Вместо перебора условий ВЫБОР КОГДА, воспользуемся механизмом Левого соединения.
Разберем по шагам правильный алгоритм:
Посмотрим на пример кода:
МенеджерВТ = Новый МенеджерВременныхТаблиц;
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = МенеджерВТ;
// Передаем ТЗ в запрос
Запрос.Текст =
"ВЫБРАТЬ
| ТЗ.Наименование КАК Наименование,
| ТЗ.ИмяКолонки КАК ИмяКолонки
|ПОМЕСТИТЬ ВТ_ДанныеФормы
|ИЗ
| &ТЗ КАК ТЗ
|;
|////////////////////////////////////////////////////////////////////////////////
|ИНДЕКСИРОВАТЬ ПО
| Наименование
|;
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ.ИмяКолонки,
| КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Регистр.Номенклатура) КАК Итог
|ИЗ
| ВТ_ДанныеФормы КАК ВТ
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ОстаткиТоваров КАК Регистр
| ПО ВТ.Наименование = Регистр.Номенклатура.Наименование
|СГРУППИРОВАТЬ ПО
| ВТ.ИмяКолонки";
Запрос.УстановитьПараметр("ТЗ", ВашаТаблицаЗначений);
Результат = Запрос.Выполнить();
Такой подход перекладывает всю работу на оптимизатор SQL. Наличие индекса во временной таблице позволит серверу БД выполнить Index Seek или Merge Join, что в десятки раз быстрее полного перебора строк.
Выясним, что делать, если запрос все равно работает недостаточно быстро или логика распределения слишком сложна для языка запросов. В 1С самым быстрым способом поиска в памяти является объект Соответствие.
Важный нюанс: Поиск методом НайтиСтроки() в таблице значений имеет сложность O(N), что при 9 000 строк в цикле даст ужасающую производительность. Поиск в Соответствии работает по принципу хэш-таблицы и выполняется практически мгновенно (сложность O(1)).
Рассмотрим алгоритм реализации:
Соответствие.Таблицу Значений один раз, заполняя Ключ (Наименование) и Значение (Имя колонки или номер группы).Соответствии.
// 1. Подготовка индекса в памяти
КартаРаспределения = Новый Соответствие;
Для Каждого СтрокаТЗ Из ТаблицаНаФорме Цикл
КартаРаспределения.Вставить(СтрокаТЗ.Наименование, СтрокаТЗ.ИмяГруппы);
КонецЦикла;
// 2. Получение данных из БД
Запрос = Новый Запрос("ВЫБРАТЬ Номенклатура.Наименование КАК Наим ИЗ Справочник.Номенклатура");
Выборка = Запрос.Выполнить().Выбрать();
// 3. Быстрый поиск
Пока Выборка.Следующий() Цикл
Группа = КартаРаспределения.Получить(Выборка.Наим);
Если Группа <> Неопределено Тогда
// Выполняем расчет итогов
КонецЕсли;
КонецЦикла;
Проанализируем ситуацию с передачей данных между клиентом и сервером. В сообщениях форума упоминалось, что 9 000 строк — это «не айс» для передачи. Действительно, при работе с управляемыми формами объект ДанныеФормыКоллекция должен быть преобразован в ТаблицуЗначений на сервере.
Наши рекомендации:
Итог() прямо на сервере без сложных запросов.РеквизитФормыВЗначение только в момент нажатия кнопки расчета.ВЫБОР КОГДА приемлема только для небольших списков (до 100–200 элементов).Таким образом, мы выяснили, что причина «тормозов» не в самой 1С, а в способе взаимодействия с СУБД. Используя правильную индексацию или быстрые коллекции в памяти, вы сможете сократить время выполнения операции с нескольких минут до долей секунды.