Как реализовать приоритетный поиск в Таблице Значений 1С (сначала по двум колонкам, затем по одной)?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

Часто в практике разработки на 1С возникает задача сопоставления данных из двух Таблиц Значений (ТЗ). Проанализируем классическую ситуацию: у нас есть первая таблица с затратами (колонки: счет затрат, статья, сумма, коррсчет) и вторая таблица с правилами распределения (колонки: раздел, статья, коррсчет затрат). Нам необходимо найти правило для каждой строки затрат. При этом алгоритм поиска должен быть приоритетным: сначала мы ищем точное совпадение по ключу Статья + Коррсчет, а если такого правила нет, используем резервное совпадение только по ключу Статья.

Многие разработчики сразу задумываются о переборе в цикле, однако это не самый оптимальный подход. Рассмотрим подробнее, как реализовать такую логику элегантно и быстро с помощью запросов и объектной модели 1С.

Решение 1: Использование запроса с двойным левым соединением

Разберем по шагам решение задачи с использованием языка запросов. Основная идея заключается в том, чтобы поместить обе таблицы во временные таблицы (ВТ) базы данных, а затем соединить основную таблицу затрат с таблицей правил дважды: первый раз по полному ключу, второй раз — по частичному.

Посмотрим на пример реализации такого запроса:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |    ПравилаФормированияФХДСтатьиЗатрат.Ссылка КАК Ссылка,
    |    ПравилаФормированияФХДСтатьиЗатрат.СтатьяЗатрат КАК СтатьяЗатрат,
    |    ПравилаФормированияФХДСчетУчета.СчетУчета КАК СчетУчета
    |ПОМЕСТИТЬ ВТПравила
    |ИЗ
    |    Справочник.ПравилаФормированияФХД.СтатьиЗатрат КАК ПравилаФормированияФХДСтатьиЗатрат
    |        ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.ПравилаФормированияФХД.СчетУчета КАК ПравилаФормированияФХДСчетУчета
    |        ПО ПравилаФормированияФХДСтатьиЗатрат.Ссылка = ПравилаФормированияФХДСчетУчета.Ссылка
    |;
    |
    |////////////////////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ
    |    Источник.Счет КАК Счет,
    |    Источник.СтатьяЗатрат КАК СтатьяЗатрат,
    |    Источник.КоррСчет КАК КоррСчет,
    |    Источник.СуммаОборотДт КАК СуммаОборотДт
    |ПОМЕСТИТЬ ВТЗатраты
    |ИЗ
    |    &ВнешнийИсточник КАК Источник
    |;
    |
    |////////////////////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ
    |    ВТЗатраты.СтатьяЗатрат КАК СтатьяЗатрат,
    |    ВТЗатраты.СуммаОборотДт КАК СуммаОборотДт,
    |    ЕСТЬNULL(ВТПравила.Ссылка, ВТПравила1.Ссылка) КАК Правило
    |ИЗ
    |    ВТЗатраты КАК ВТЗатраты
    |        ЛЕВОЕ СОЕДИНЕНИЕ ВТПравила КАК ВТПравила
    |        ПО ВТЗатраты.СтатьяЗатрат = ВТПравила.СтатьяЗатрат
    |            И ВТЗатраты.КоррСчет = ВТПравила.СчетУчета
    |        ЛЕВОЕ СОЕДИНЕНИЕ ВТПравила КАК ВТПравила1
    |        ПО ВТЗатраты.СтатьяЗатрат = ВТПравила1.СтатьяЗатрат
    |            И ВТПравила1.СчетУчета = ЗНАЧЕНИЕ(ПланСчетов.Хозрасчетный.ПустаяСсылка)";

Что здесь происходит? Мы используем функцию ЕСТЬNULL. Она проверяет: если первое соединение (ВТПравила, где совпали и статья, и счет) вернуло пустой результат (NULL), то платформа возьмет значение из второго соединения (ВТПравила1, где совпала только статья).

Важный нюанс: защита от задвоения строк. Обратите внимание на условие второго левого соединения: И ВТПравила1.СчетУчета = ЗНАЧЕНИЕ(ПланСчетов.Хозрасчетный.ПустаяСсылка). Если его не добавить, запрос присоединит к одной строке затрат все доступные правила для данной статьи, что приведет к эффекту "декартова произведения" (строки затрат задвоятся или затроятся). Мы явно указываем, что резервное правило — это то, у которого коррсчет не заполнен.

Обязательная типизация: Передавая Таблицу Значений как параметр &ВнешнийИсточник, обязательно убедитесь, что все колонки в ТЗ имеют строгое Описание Типов. Иначе запрос завершится ошибкой при попытке создать временную таблицу.

Решение 2: Использование явного приоритета в запросе

Рассмотрим альтернативный способ внутри запроса, который избавляет нас от двойного левого соединения. Мы можем добавить вычисляемое поле с приоритетом при формировании временной таблицы правил:


ВЫБОР 
    КОГДА СчетУчета <> ЗНАЧЕНИЕ(ПланСчетов.Хозрасчетный.ПустаяСсылка) ТОГДА 1 
    ИНАЧЕ 2 
КОНЕЦ КАК Приоритет

При таком подходе мы соединяем таблицы только один раз по ключу СтатьяЗатрат. В результате к одной строке затрат может прикрепиться несколько правил (с приоритетом 1 и приоритетом 2). Чтобы оставить только самое подходящее, мы используем группировку и отбираем строку с МИНИМУМ(Приоритет). Этот метод делает текст запроса более читаемым при увеличении количества условий поиска.

Решение 3: Объектный поиск без запросов

Проанализируем ситуацию, когда объемы данных небольшие. Инициализация менеджера временных таблиц и выполнение запроса могут оказаться избыточными. В таком случае эффективнее использовать встроенный платформенный метод НайтиСтроки.

Реализуем алгоритм последовательного поиска через структуру:


СтруктураПоиска = Новый Структура("СтатьяЗатрат, Коррсчет");

Для Каждого СтрокаЗатрат Из ТЗЗатраты Цикл
    
    // Попытка 1: Ищем по двум ключам
    СтруктураПоиска.СтатьяЗатрат = СтрокаЗатрат.СтатьяЗатрат;
    СтруктураПоиска.Коррсчет = СтрокаЗатрат.Коррсчет;
    
    НайденныеПравила = ТЗПравила.НайтиСтроки(СтруктураПоиска);
    
    // Попытка 2: Если не нашли, ищем только по статье
    Если НайденныеПравила.Количество() = 0 Тогда
        СтруктураПоиска.Удалить("Коррсчет"); // Убираем счет из условий
        НайденныеПравила = ТЗПравила.НайтиСтроки(СтруктураПоиска);
        // Возвращаем ключ обратно для следующей итерации цикла
        СтруктураПоиска.Вставить("Коррсчет"); 
    КонецЕсли;
    
    Если НайденныеПравила.Количество() > 0 Тогда
        // Берем первое найденное правило
        НужноеПравило = НайденныеПравила[0];
        СтрокаЗатрат.Правило = НужноеПравило.Ссылка;
    КонецЕсли;
    
КонецЦикла;

Оптимизация объектного поиска: Чтобы метод НайтиСтроки работал максимально быстро и не сканировал всю таблицу линейно, перед началом цикла мы обязательно должны проиндексировать колонки поиска. Выполним следующую команду:

ТЗПравила.Индексы.Добавить("СтатьяЗатрат, Коррсчет");

Создание индекса выстраивает бинарное дерево значений в оперативной памяти сервера 1С, благодаря чему поиск даже по таблицам на десятки тысяч строк происходит практически мгновенно.

Подводя итог, можно сказать, что выбор между запросом и объектной моделью зависит от контекста вашей задачи. Если данные в дальнейшем участвуют в сложных выборках базы данных — смело используйте Временные Таблицы и функцию ЕСТЬNULL. Если же задача решается в модуле обработки на уже сформированных коллекциях — проиндексируйте таблицу и примените метод НайтиСтроки со структурой ключей.

← На главную