В практике разработчика 1С часто возникает задача: есть таблица значений (ТЗ), заполненная данными из внешнего источника (Excel, XML, JSON), и в ней необходимо заполнить или обновить колонку (например, «Количество» или «Остаток»), получив актуальную информацию из базы данных через запрос. Рассмотрим, почему стандартный подход с вложенными циклами является неэффективным, и разберем наиболее правильные и быстрые способы решения этой задачи.
Рассмотрим ситуацию, с которой столкнулся автор темы. Имеется таблица ДанныеФайла и результат запроса РезультатЗапроса. Попытка сопоставить их через Для каждого внутри другого Для каждого — это классическая ошибка производительности. Если в таблице 1000 строк и в результате запроса 1000 строк, системе придется выполнить 1 000 000 операций сравнения. Это приводит к значительному замедлению работы интерфейса.
Кроме того, в коде автора была логическая ошибка, из-за которой цикл мог «пропускать» значения или работать некорректно. Проанализируем причины:
Если.Самый эффективный способ с точки зрения платформы 1С — это передать вашу таблицу значений прямо в тело запроса и выполнить соединение на стороне SQL-сервера. Это избавляет от необходимости писать циклы вообще. Рассмотрим этот метод по шагам.
Важное условие: для передачи ТЗ в запрос ее колонки должны быть жестко типизированы. Если вы создавали таблицу программно, убедитесь, что указали ОписаниеТипов.
// Подготовим таблицу к передаче в запрос
ДанныеФайла.Колонки.КаталожныйНомер.ТипЗнч = Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(50));
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
// Помещаем ТЗ во временную таблицу
Запрос.Текст = "ВЫБРАТЬ * ПОМЕСТИТЬ ВТ_ДанныеФайла ИЗ &ТЗ КАК ТЗ";
Запрос.УстановитьПараметр("ТЗ", ДанныеФайла);
Запрос.Выполнить();
// Основной запрос с левым соединением
Запрос.Текст =
"ВЫБРАТЬ
| ВТ_ДанныеФайла.КаталожныйНомер КАК КаталожныйНомер,
| ЕСТЬNULL(ТоварыНаСкладахОстатки.КоличествоОстаток, 0) КАК Количество
|ИЗ
| ВТ_ДанныеФайла КАК ВТ_ДанныеФайла
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки(
| ,
| Номенклатура.Артикул В
| (ВЫБРАТЬ
| ВТ.КаталожныйНомер
| ИЗ
| ВТ_ДанныеФайла КАК ВТ)) КАК ТоварыНаСкладахОстатки
| ПО ВТ_ДанныеФайла.КаталожныйНомер = ТоварыНаСкладахОстатки.Номенклатура.Артикул";
ДанныеФайла = Запрос.Выполнить().Выгрузить();
Преимущества: Вся работа выполняется одной операцией. Нет риска ошибиться в логике перебора строк. Высокая скорость на больших объемах данных.
Если вы не хотите использовать временные таблицы (например, данных очень мало и вызов tempdb избыточен), воспользуйтесь встроенными механизмами поиска 1С, но обязательно с предварительной индексацией. Посмотрим на пример оптимизированного кода.
Выясним причину, почему простой поиск может тормозить: без индекса метод Найти() или НайтиСтроки() каждый раз сканирует таблицу с начала до конца. Индексация создает «карту» значений в памяти.
// 1. Получаем данные из регистра по списку артикулов
СписокАртикулов = ДанныеФайла.ВыгрузитьКолонку("КаталожныйНомер");
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ТоварыНаСкладахОстатки.Номенклатура.Артикул КАК Артикул,
| ТоварыНаСкладахОстатки.КоличествоОстаток КАК Количество
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки(, Номенклатура.Артикул В (&СписокАртикулов)) КАК ТоварыНаСкладахОстатки";
Запрос.УстановитьПараметр("СписокАртикулов", СписокАртикулов);
РезультатЗапроса = Запрос.Выполнить().Выбрать();
// 2. Индексируем колонку для мгновенного поиска
ДанныеФайла.Индексы.Добавить("КаталожныйНомер");
// 3. Обходим результат запроса и обновляем ТЗ
Пока РезультатЗапроса.Следующий() Цикл
СтруктураПоиска = Новый Структура("КаталожныйНомер", РезультатЗапроса.Артикул);
НайденныеСтроки = ДанныеФайла.НайтиСтроки(СтруктураПоиска);
Для Каждого СтрокаТЗ Из НайденныеСтроки Цикл
СтрокаТЗ.Количество = РезультатЗапроса.Количество;
КонецЦикла;
КонецЦикла;
Разберем подробнее: мы использовали НайтиСтроки(), так как в исходной таблице может быть несколько строк с одним и тем же артикулом. Если артикул гарантированно уникален, можно использовать метод Найти().
Проанализируем ситуацию, когда данных очень много (десятки тысяч строк), а таблицу нельзя передать в запрос. В этом случае наиболее производительным инструментом в коде 1С будет объект Соответствие. Поиск по ключу в Соответствии происходит значительно быстрее, чем поиск в таблице значений даже с индексом.
Рассмотрим алгоритм действий:
Соответствие, где Ключ — Артикул, а Значение — Количество.
// Сначала соберем данные запроса в "карту"
КартаОстатков = Новый Соответствие;
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
КартаОстатков.Вставить(Выборка.Артикул, Выборка.Количество);
КонецЦикла;
// Теперь обновляем основную таблицу
Для Каждого СтрокаТЗ Из ДанныеФайла Цикл
Остаток = КартаОстатков.Получить(СтрокаТЗ.КаталожныйНомер);
Если Остаток <> Неопределено Тогда
СтрокаТЗ.Количество = Остаток;
КонецЕсли;
КонецЦикла;
Если данные все равно не проставляются, выполните следующие проверки:
Проверка на пробелы: Часто артикулы " 123" и "123 " не совпадают. Используйте функцию СокрЛП() как в запросе (через ПОДСТРОКА или предварительную обработку справочника), так и при заполнении таблицы.
Регистр символов: Артикулы "ABC" и "abc" для платформы могут быть разными строками. Рекомендуется приводить их к одному регистру функцией ВРег().
Типизация: Если в таблице значений в колонке КаталожныйНомер лежит число, а запрос возвращает строку, сопоставление не произойдет. Проверьте типы в отладчике через ТипЗнч().
Подводя итог, можно сказать: забудьте о вложенных циклах. Используйте Способ 1 для надежности и Способ 3 для максимальной скорости при работе внутри кода. Это сделает вашу программу профессиональной и отзывчивой для пользователя.