Как найти строку в таблице значений по нескольким параметрам и записать в нее данные?

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

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

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

Базовое решение: Использование метода НайтиСтроки()

Для поиска строк в таблице значений по одному или нескольким условиям в платформе 1С предусмотрен стандартный метод НайтиСтроки(). Этот метод принимает в качестве параметра структуру отбора, где ключами являются имена колонок таблицы значений, а значениями — искомые данные.

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

Давайте разберем базовый пример реализации этой задачи по шагам:

  1. Выполняем запрос для получения актуальных остатков товаров.
  2. Организуем цикл обхода выборки результата запроса.
  3. На каждой итерации цикла создаем структуру отбора с ключевыми полями.
  4. Вызываем метод НайтиСтроки() у нашей исходной таблицы.
  5. Обходим циклом полученный массив найденных строк и записываем в них новое значение остатка.

Посмотрим на практический пример кода, который решает эту задачу:


// Предположим, что рез2 — это наша исходная таблица значений,
// в которой колонки "Номенклатура" и "Характеристика" уже заполнены,
// а колонку "Остаток" нам необходимо заполнить.

ВыборкаОстатков = Запрос.Выполнить().Выбрать();

Пока ВыборкаОстатков.Следующий() Цикл
    
    // Формируем структуру отбора по ключевым полям
    ПараметрыОтбора = Новый Структура;
    ПараметрыОтбора.Вставить("Номенклатура", ВыборкаОстатков.Номенклатура);
    ПараметрыОтбора.Вставить("Характеристика", ВыборкаОстатков.Характеристика);
    
    // Выполняем поиск строк в исходной таблице значений
    НайденныеСтроки = рез2.НайтиСтроки(ПараметрыОтбора);
    
    // Обходим массив найденных строк и напрямую записываем в них данные
    Для Каждого ТекущаяСтрока Из НайденныеСтроки Цикл
        ТекущаяСтрока.Остаток = ВыборкаОстатков.Остаток;
    КонецЦикла;
    
КонецЦикла;

Этот код абсолютно работоспособен и прост в понимании. Поскольку в цикле Для Каждого ТекущаяСтрока Из НайденныеСтроки переменная ТекущаяСтрока указывает на конкретную строку исходной таблицы рез2, присвоение значения ТекущаяСтрока.Остаток = ВыборкаОстатков.Остаток напрямую меняет данные в исходной таблице.

Важное различие между методами Найти() и НайтиСтроки()

У начинающих разработчиков часто возникает вопрос: почему для решения этой задачи нельзя использовать более простой метод таблицы значений Найти()? Разберем ключевые отличия этих двух методов:

Если вам гарантированно известно, что в таблице значений может быть только одна уникальная строка с комбинацией таких параметров, вы можете обратиться к первому элементу возвращаемого массива напрямую без использования вложенного цикла:


НайденныеСтроки = рез2.НайтиСтроки(ПараметрыОтбора);
Если НайденныеСтроки.Количество() > 0 Тогда
    НайденныеСтроки[0].Остаток = ВыборкаОстатков.Остаток;
КонецЕсли;

Оптимизация производительности: Индексация таблицы значений

Рассмотренный выше базовый алгоритм имеет один существенный недостаток: при больших объемах данных в таблице значений рез2 (более 1000 строк) время выполнения кода начнет лавинообразно расти. Дело в том, что метод НайтиСтроки() по умолчанию осуществляет последовательный полный перебор всей таблицы значений при каждом вызове. В терминах теории алгоритмов сложность такого процесса составляет $O(N \times M)$, где N — количество строк в исходной таблице, а M — количество итераций внешнего цикла.

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

Рассмотрим пример добавления индекса:


// Добавляем индекс по ключевым колонкам ОДИН РАЗ перед началом цикла.
// Колонки указываются в виде строки через запятую.
рез2.Индексы.Добавить("Номенклатура, Характеристика");

ВыборкаОстатков = Запрос.Выполнить().Выбрать();

Пока ВыборкаОстатков.Следующий() Цикл
    ПараметрыОтбора = Новый Структура;
    ПараметрыОтбора.Вставить("Номенклатура", ВыборкаОстатков.Номенклатура);
    ПараметрыОтбора.Вставить("Характеристика", ВыборкаОстатков.Характеристика);
    
    НайденныеСтроки = рез2.НайтиСтроки(ПараметрыОтбора);
    Для Каждого ТекущаяСтрока Из НайденныеСтроки Цикл
        ТекущаяСтрока.Остаток = ВыборкаОстатков.Остаток;
    КонецЦикла;
КонецЦикла;

Важные нюансы работы с индексами:

Архитектурно верный подход: Перенос вычислений в запрос

В стандартах разработки фирмы "1С" четко закреплено правило: любые массовые операции над данными и соединения коллекций эффективнее выполнять на стороне СУБД с помощью механизма запросов, а не во встроенном языке. Вместо того чтобы перебирать строки во встроенном языке и выполнять множество поисков в памяти, правильнее передать исходную таблицу значений в запрос, соединить её с таблицей остатков и выгрузить готовый результат.

Для реализации этого подхода необходимо соблюсти важное условие: колонки исходной таблицы значений обязательно должны быть типизированы. Если типы колонок не определены (имеют тип "Произвольный"), то при попытке передать такую ТЗ в параметры запроса платформа сгенерирует критическую ошибку "Тип не может быть выбран в запросе".

Давайте проанализируем пример правильной реализации этой схемы:


// 1. Создаем таблицу значений с явным указанием типов данных
ОписаниеНоменклатуры = Новый ОписаниеТипов("СправочникСсылка.Номенклатура");
ОписаниеХарактеристики = Новый ОписаниеТипов("СправочникСсылка.ХарактеристикиНоменклатуры");
ОписаниеЧисла = Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 3));

ТаблицаДанных = Новый ТаблицаЗначений;
ТаблицаДанных.Колонки.Добавить("Номенклатура", ОписаниеНоменклатуры);
ТаблицаДанных.Колонки.Добавить("Характеристика", ОписаниеХарактеристики);
ТаблицаДанных.Колонки.Добавить("Остаток", ОписаниеЧисла);

// ... Здесь происходит заполнение таблицы данными ...

// 2. Описываем запрос с использованием временных таблиц
ЗапросОбъединения = Новый Запрос;
ЗапросОбъединения.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;

// Передаем нашу ТЗ в качестве параметра запроса
ЗапросОбъединения.УстановитьПараметр("ТаблицаДанных", ТаблицаДанных);
ЗапросОбъединения.УстановитьПараметр("Период", ТекущаяДата());

ЗапросОбъединения.Текст = 
    "ВЫБРАТЬ
    |	ИсходнаяТЗ.Номенклатура КАК Номенклатура,
    |	ИсходнаяТЗ.Характеристика КАК Характеристика
    |ПОМЕСТИТЬ ВТ_ИсходныеДанные
    |ИЗ
    |	&ТаблицаДанных КАК ИсходнаяТЗ
    |;
    |
    |////////////////////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ
    |	ВТ_ИсходныеДанные.Номенклатура КАК Номенклатура,
    |	ВТ_ИсходныеДанные.Характеристика КАК Характеристика,
    |	ЕСТЬNULL(ТоварыНаСкладахОстатки.ВНаличииОстаток, 0) КАК Остаток
    |ИЗ
    |	ВТ_ИсходныеДанные КАК ВТ_ИсходныеДанные
    |		ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки(&Период, ) КАК ТоварыНаСкладахОстатки
    |		ПО ВТ_ИсходныеДанные.Номенклатура = ТоварыНаСкладахОстатки.Номенклатура
    |			И ВТ_ИсходныеДанные.Характеристика = ТоварыНаСкладахОстатки.Характеристика";

// Выгружаем итоговый результат в новую, полностью заполненную таблицу значений
Рез2 = ЗапросОбъединения.Выполнить().Выгрузить();

Этот подход является наиболее масштабируемым и быстрым. База данных оптимизирует выполнение соединения на физическом уровне, исключая лишние циклы во встроенном коде программы.

Альтернатива для быстрой работы в памяти: Объект "Соответствие"

Если по каким-то причинам вы не можете использовать механизм запросов (например, обработка происходит на клиенте в тонком клиенте или данные не хранятся в базе данных), но вам нужна максимальная скорость работы алгоритма без индексации ТЗ, можно применить кэширование с помощью объекта Соответствие (хэш-мап). Поиск элементов в объекте Соответствие выполняется за константное время $O(1)$.

Поскольку ключом соответствия не может выступать структура отбора напрямую, разработчики формируют уникальный строковый ключ-идентификатор на основе ключевых полей. Посмотрим, как это выглядит на практике:


// Создаем соответствие для быстрого поиска строк в памяти
СоответствиеСтрок = Новый Соответствие;

Для Каждого СтрТЗ Из рез2 Цикл
    // Формируем уникальный текстовый ключ из уникальных идентификаторов ссылок
    КлючСтроки = Строка(СтрТЗ.Номенклатура.УникальныйИдентификатор()) + "_" + Строка(СтрТЗ.Характеристика.УникальныйИдентификатор());
    СоответствиеСтрок.Вставить(КлючСтроки, СтрТЗ);
КонецЦикла;

// Теперь обходим выборку с остатками и мгновенно получаем нужные строки по ключу
ВыборкаОстатков = Запрос.Выполнить().Выбрать();
Пока ВыборкаОстатков.Следующий() Цикл
    
    КлючПоиска = Строка(ВыборкаОстатков.Номенклатура.УникальныйИдентификатор()) + "_" + Строка(ВыборкаОстатков.Характеристика.УникальныйИдентификатор());
    НайденнаяСтрока = СоответствиеСтрок.Получить(КлючПоиска);
    
    Если НайденнаяСтрока <> Неопределено Тогда
        НайденнаяСтрока.Остаток = ВыборкаОстатков.Остаток;
    КонецЕсли;
    
КонецЦикла;

Этот прием позволяет эффективно обойти ограничения платформы при работе со сложными структурами данных и обеспечить превосходное быстродействие кода без необходимости взаимодействия с базой данных СУБД на этапе сопоставления.

Резюме

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

← На главную