При разработке отчетов в 1С часто возникает задача подготовить данные в таблице значений, а затем найти в ней нужные строки. Однако стандартный метод НайтиСтроки() может не сработать, даже если, на первый взгляд, искомые значения совпадают. Разберем, почему так происходит и как исправить эту распространенную ошибку на примере подготовки данных для отчета на СКД.
Рассмотрим ситуацию: мы формируем отчет, в котором нужно сопоставить данные из двух источников. Сначала получаем оплаты по заказам из регистра накопления, а затем для каждого заказа ищем информацию о распределении ("дележке") сумм по менеджерам из регистра сведений. Код выглядит примерно так: в цикле по оплатам мы пытаемся найти соответствующие строки в таблице с "дележками", что является неоптимальной практикой, на которую могут указать статические анализаторы кода.
// ... получаем выборку из регистра РасчетыСКлиентами ...
ТабДележки = Запрос1.Выполнить().Выгрузить(); // Таблица с менеджерами и процентами по заказам
Пока Выборка.Следующий() Цикл
// Проблема возникает здесь:
стрОтбор = Новый Структура("Заказ", Выборка.ЗаказКлиента);
МассивСтрок = ТабДележки.НайтиСтроки(стрОтбор); // МассивСтрок всегда пустой
Если МассивСтрок.Количество() > 0 Тогда
// Этот код никогда не выполняется
КонецЕсли;
КонецЦикла;
Основная причина, по которой НайтиСтроки() не находит совпадений, — это несоответствие типов данных. Метод выполняет поиск с точным сравнением не только значения, но и его типа.
В нашем примере в первом запросе поле РасчетыСКлиентами.ОбъектРасчетов (которое мы назвали ЗаказКлиента) часто имеет составной тип данных. Это значит, что оно может хранить ссылки на объекты разных типов (например, на разные виды документов). Во втором же запросе мы получаем конкретную ссылку ДП_УчастникиЗаказа.ЗаказКлиента.Ссылка, которая имеет тип ДокументСсылка.ЗаказКлиента.
В итоге, система пытается сравнить значение составного типа со значением типа "ссылка на документ". Поскольку типы не совпадают, метод НайтиСтроки() не находит совпадений, и отбор не работает.
Самый простой способ исправить ситуацию — это привести данные к одному типу прямо в запросах. Для этого нужно в первом запросе получить из поля составного типа конкретную ссылку на документ.
Посмотрим, как изменить первый запрос. Вместо того чтобы выбирать поле ОбъектРасчетов напрямую, получим из него ссылку:
Было:
| РасчетыСКлиентами.ОбъектРасчетов КАК ЗаказКлиента,
Стало:
| РасчетыСКлиентами.ОбъектРасчетов.Ссылка КАК ЗаказКлиента,
После такого изменения в обоих источниках данных у нас будет поле одного типа — ДокументСсылка.ЗаказКлиента, и метод НайтиСтроки() заработает корректно.
Хотя предыдущее решение исправляет ошибку, сам подход с выполнением поиска в цикле (так называемый "запрос в цикле") является крайне непроизводительным, особенно на больших объемах данных. На каждую итерацию цикла происходит поиск по таблице, что создает лишнюю нагрузку.
Правильный и наиболее эффективный способ — объединить два запроса в один с помощью левого соединения (ЛЕВОЕ СОЕДИНЕНИЕ). Вся обработка данных при этом переносится на уровень СУБД, что работает гораздо быстрее, а оценить реальную нагрузку и найти узкие места помогает анализ SQL сервера глазами 1С-ника.
Разберем, как будет выглядеть итоговый запрос, который сразу готовит все необходимые данные (для его построения и отладки удобно использовать консоль запросов):
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| РасчетыСКлиентами.ОбъектРасчетов.Ссылка КАК ЗаказКлиента,
| ВЫБОР
| КОГДА РасчетыСКлиентами.Оплачивается <> 0
| ТОГДА РасчетыСКлиентами.Оплачивается
| ИНАЧЕ РасчетыСКлиентами.Сумма
| КОНЕЦ КАК СуммаДележки,
| // Используем ЕСТЬNULL, чтобы подставить менеджера из заказа, если "дележки" нет
| ЕСТЬNULL(ДП_УчастникиЗаказа.Менеджер, РасчетыСКлиентами.ОбъектРасчетов.Менеджер) КАК Менеджер,
| // Если "дележки" нет, считаем, что процент равен 100
| ЕСТЬNULL(ДП_УчастникиЗаказа.Процент, 100) КАК ПроцентДележки
|ИЗ
| РегистрНакопления.РасчетыСКлиентами КАК РасчетыСКлиентами
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ДП_УчастникиЗаказа КАК ДП_УчастникиЗаказа
| ПО РасчетыСКлиентами.ОбъектРасчетов = ДП_УчастникиЗаказа.ЗаказКлиента
|ГДЕ
| РасчетыСКлиентами.Период МЕЖДУ &НачалоПериода И &КонецПериода
| И РасчетыСКлиентами.ХозяйственнаяОперация = &ХозОперация";
// Устанавливаем параметры и выполняем запрос
// ...
Результат = Запрос.Выполнить().Выгрузить();
// Таблица "Результат" уже содержит все нужные данные и готова для передачи в СКД
В этом запросе мы соединяем данные из двух регистров по заказу клиента. Функция ЕСТЬNULL() позволяет элегантно решить нашу задачу: если для заказа не найдено записей в регистре ДП_УчастникиЗаказа (т.е. "дележки" нет), мы подставляем значения по умолчанию: менеджера из самого заказа и 100%.
Этот подход не только решает проблему с типами, но и является стандартом качественной разработки в 1С, так как он значительно производительнее.
Чтобы избежать подобных ошибок в будущем и сделать код более надежным, рекомендуется всегда явно указывать типы колонок при создании таблицы значений. Это делается с помощью объекта ОписаниеТипов.
Посмотрим на пример правильного объявления таблицы:
Дележка = Новый ТаблицаЗначений;
Дележка.Колонки.Добавить("Менеджер", Новый ОписаниеТипов("СправочникСсылка.Пользователи"));
Дележка.Колонки.Добавить("ПроцентДележки", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(5, 2)));
Дележка.Колонки.Добавить("СуммаДележки", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2)));
Дележка.Колонки.Добавить("ЗаказКлиента", Новый ОписаниеТипов("ДокументСсылка.ЗаказКлиента"));
Главный вывод: если отбор в таблице значений не работает, в первую очередь проверьте типы данных в искомом и исходном полях. А для задач сопоставления данных из разных источников всегда старайтесь использовать соединения в запросах вместо обработки в циклах.