В процессе разработки на платформе 1С:Предприятие 8.3 программисты часто сталкиваются с задачей сопоставления данных из разных источников. Типичный сценарий: у нас есть таблица значений на форме обработки и данные, полученные запросом из документа или регистра. Нам необходимо найти пересечения этих данных, чтобы либо подкрасить строки, либо отфильтровать список. Рассмотрим подробно, как реализовать это в обычном приложении, проанализируем различные подходы от «наивных» циклов до оптимизированных запросов.
Если объем данных невелик (до нескольких сотен строк), мы можем воспользоваться простым перебором. Рассмотрим алгоритм, в котором мы обходим таблицу-источник и ищем каждое значение во второй таблице. Проанализируем пример кода, использующий метод НайтиЗначение() или Найти().
// Предположим, ТЗ_Обработка — наша основная таблица
// ТЗ_Запрос — таблица с данными из документа
Для Каждого СтрокаИзЗапроса Из ТЗ_Запрос Цикл
ПараметрыПоиска = Новый Структура("Номенклатура", СтрокаИзЗапроса.Номенклатура);
НайденнаяСтрока = ТЗ_Обработка.Найти(СтрокаИзЗапроса.Номенклатура, "Номенклатура");
Если НайденнаяСтрока <> Неопределено Тогда
// Здесь мы нашли совпадение.
// Можем установить какой-то флаг для дальнейшей подкраски
НайденнаяСтрока.Совпадает = Истина;
КонецЕсли;
КонецЦикла;
Важный нюанс: при использовании этого метода скорость работы напрямую зависит от количества строк. Чтобы ускорить процесс, обязательно проанализируем возможность индексации. Перед началом цикла добавим индекс по колонке поиска. Детальное сравнение производительности способов обхода таблицы показывает, насколько важен правильный выбор алгоритма при росте объемов.
ТЗ_Обработка.Индексы.Добавить("Номенклатура");
Это позволит платформе использовать механизм быстрого поиска, что значительно сократит время выполнения при росте количества строк.
Разберем более продвинутый способ сопоставления в памяти без использования запросов. Объект Соответствие (Map) работает по принципу хеш-таблицы, что делает поиск по ключу практически мгновенным. Рассмотрим, как это применить для нашей задачи:
Соответствие.
// Шаг 1: Заполняем соответствие
Совпадения = Новый Соответствие;
Для Каждого СтрокаДок Из ТЗ_Запрос Цикл
Совпадения.Вставить(СтрокаДок.Номенклатура, Истина);
КонецЦикла;
// Шаг 2: Проверяем основную таблицу
Для Каждого СтрокаТЗ Из ТЗ_Обработка Цикл
Если Совпадения.Получить(СтрокаТЗ.Номенклатура) <> Неопределено Тогда
СтрокаТЗ.Маркер = Истина; // Помечаем для оформления
КонецЕсли;
КонецЦикла;
Этот метод является самым быстрым при работе с коллекциями значений в оперативной памяти компьютера.
Теперь рассмотрим наиболее профессиональный и гибкий подход — использование языка запросов 1С. Этот метод идеален, если одна из таблиц уже находится в базе данных, а вторая передается как параметр. Выясним причину, почему это удобно: запрос позволяет нам выполнить ВНУТРЕННЕЕ СОЕДИНЕНИЕ (INNER JOIN) или ЛЕВОЕ СОЕДИНЕНИЕ сразу на уровне сервера, что позволяет проанализировать нагрузку на SQL и убедиться в эффективности операции.
Рассмотрим реализацию, которую предложил автор темы. Здесь мы помещаем имеющуюся таблицу значений во временную таблицу, а затем соединяем её с табличной частью документа.
Запрос = Новый Запрос;
// Передаем выгруженную таблицу как параметр.
// Важно: колонки в ТЗ должны иметь типы данных!
Запрос.УстановитьПараметр("ТаблицаТовары", Товары.Выгрузить());
Запрос.УстановитьПараметр("СсылкаДокумент", ВыбранныйДокумент);
Запрос.Текст =
"ВЫБРАТЬ
| ВТ.Номенклатура КАК Номенклатура,
| ВТ.Цена КАК Цена,
| ВТ.КодПЛУ КАК КодПЛУ
|ПОМЕСТИТЬ ВТ_ДанныеОбъекта
|ИЗ
| &ТаблицаТовары КАК ВТ
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ.Номенклатура,
| ВТ.Цена,
| ВТ.КодПЛУ,
| ИСТИНА КАК ЭтоСовпадение
|ИЗ
| ВТ_ДанныеОбъекта КАК ВТ
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.ПоступлениеТоваровУслуг.Товары КАК ДокТовары
| ПО ВТ.Номенклатура = ДокТовары.Номенклатура
|ГДЕ
| ДокТовары.Ссылка = &СсылкаДокумент";
РезультатЗапроса = Запрос.Выполнить().Выгрузить();
Обратите внимание: если в документе одна и та же номенклатура встречается в нескольких строках, ВНУТРЕННЕЕ СОЕДИНЕНИЕ может продублировать строки вашей исходной таблицы. Чтобы этого избежать, используйте конструкцию ВЫБРАТЬ РАЗЛИЧНЫЕ во вложенном запросе к документу. Для отладки подобных конструкций разработчики часто используют специализированные консоли запросов,
Если наша цель — не просто отфильтровать данные, а подкрасить их в интерфейсе (как просил автор в начале), нам нужно использовать события табличного поля. В обычном приложении 1С 8.3 за это отвечает событие ПриВыводеСтроки.
Проанализируем шаги реализации подкраски:
Совпадает типа Булево), которую мы заполнили одним из методов выше.
Процедура ТабличноеПолеТоварыПриВыводеСтроки(Элемент, ОформлениеСтроки, ДанныеСтроки)
// Проверяем наш флаг совпадения
Если ДанныеСтроки.Совпадает Тогда
// Устанавливаем цвет фона для всей строки
ОформлениеСтроки.ЦветФона = WebЦвета.СветлоЗеленый;
// Или можно выделить только конкретную ячейку
// ОформлениеСтроки.Ячейки.Номенклатура.ЦветТекста = WebЦвета.Красный;
КонецЕсли;
КонецПроцедуры
Полезный совет: событие ПриВыводеСтроки вызывается очень часто (при каждой прокрутке). Поэтому внутри него категорически нельзя выполнять запросы или тяжелые поиски. Все данные для раскраски (флаги совпадения) должны быть подготовлены заранее одним из методов сравнения таблиц. Если вы столкнулись с тем, что у пользователя «тормозит» интерфейс, то замер производительности и поиск узких мест часто указывает именно на тяжелый код в подобных событиях.
Разберем ситуации, из-за которых сравнение может не сработать (та самая «лажа», о которой писал автор):
Номенклатура может лежать СправочникСсылка, а в другой — Строка (артикул или наименование). Сравнение ссылки со строкой всегда будет давать Ложь.Новый ОписаниеТипов), запрос упадет с ошибкой.Подводя итог, можно сказать: для оперативной подкраски лучше всего подходит метод с использованием Соответствие или индексации Найти(), а для получения нового набора данных (фильтрации) оптимальным решением будет запрос с использованием МенеджерВременныхТаблиц.