Как гарантировать порядок строк при использовании НайтиСтроки() в алгоритмах партионного учета?

Программист 1С v8.3 (Обычные формы) Управленческий учет
← На главную

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

В данной статье мы разберем, почему возникает эта проблема, проанализируем различные способы её решения и выберем наиболее оптимальные алгоритмы для сохранения порядка обхода данных.

Почему НайтиСтроки() не гарантирует порядок?

Разберем ситуацию: метод НайтиСтроки() возвращает массив ссылок на строки исходной таблицы. Если для таблицы значений не заданы индексы, платформа выполняет линейный поиск. В этом случае порядок строк в массиве часто совпадает с физическим порядком в таблице, но это лишь побочный эффект реализации, который может измениться. Если же для таблицы построен индекс, метод использует его для ускорения поиска. Поскольку внутренние структуры индекса (хеш-таблицы или деревья) ориентированы на скорость доступа, а не на сохранение последовательности добавления, результат в массиве может оказаться «перемешанным».

В задачах партионного учета, где мы сопоставляем строки документа с остатками (связь «многие-ко-многим», если в документе одна номенклатура встречается по разным ценам), хаотичный порядок может привести к некорректному распределению сумм и остатков.

Решение 1: Ручная досортировка массива через вспомогательную таблицу

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


Функция НайтиСтрокиССохранениемПорядка(ТЗ, СтруктураПоиска)
    // Получаем стандартный массив найденных строк
    НайденныеСтроки = ТЗ.НайтиСтроки(СтруктураПоиска);
    
    Если НайденныеСтроки.Количество() = 0 Тогда
        Возврат НайденныеСтроки;
    КонецЕсли;

    // Создаем временную таблицу для сортировки
    ТЗ_Сортировка = Новый ТаблицаЗначений();
    ТЗ_Сортировка.Колонки.Добавить("Индекс", Новый ОписаниеТипов("Число"));
    ТЗ_Сортировка.Колонки.Добавить("СсылкаНаСтроку");

    Для Каждого СтрокаМассива Из НайденныеСтроки Цикл
        НоваяСтр = ТЗ_Сортировка.Добавить();
        // Метод Индекс() возвращает порядковый номер строки в исходной ТЗ
        НоваяСтр.Индекс = ТЗ.Индекс(СтрокаМассива);
        НоваяСтр.СсылкаНаСтроку = СтрокаМассива;
    КонецЦикла;

    // Сортируем временную таблицу по возрастанию индекса
    ТЗ_Сортировка.Сортировать("Индекс Возр");

    // Формируем результирующий массив в правильном порядке
    Результат = Новый Массив;
    Для Каждого СтрСорт Из ТЗ_Сортировка Цикл
        Результат.Добавить(СтрСорт.СсылкаНаСтроку);
    КонецЦикла;

    Возврат Результат;
КонецФункции

Важное замечание по производительности: Метод ТЗ.Индекс(Строка) внутри цикла работает за линейное время $O(n)$. Это означает, что для больших таблиц общая сложность алгоритма составит $O(n^2)$, что может существенно замедлить проведение документа. Если строк в таблице много, этот метод лучше оптимизировать.

Решение 2: Использование вспомогательной колонки «НомерСтроки»

Чтобы избежать медленного метода Индекс(), мы можем заранее подготовить исходную таблицу значений. Проанализируем этот алгоритм по шагам:

  1. Добавим в таблицу значений ТаблицаДокумента колонку СлужебныйПорядок с типом «Число».
  2. Заполним её простым счетчиком (1, 2, 3...) перед началом всех манипуляций.
  3. После вызова НайтиСтроки() выгрузим найденные строки в копию таблицы и отсортируем по колонке СлужебныйПорядок.

Посмотрим на пример кода:


// 1. Подготовка таблицы
ТЗ.Колонки.Добавить("СлужебныйПорядок", Новый ОписаниеТипов("Число"));
Для Счетчик = 0 По ТЗ.Количество() - 1 Цикл
    ТЗ[Счетчик].СлужебныйПорядок = Счетчик;
КонецЦикла;

// ... где-то в алгоритме списания ...
НайденныеСтроки = ТЗ.НайтиСтроки(НоваяСтруктура);
// Если порядок критичен, выгружаем в промежуточную ТЗ для сортировки
Если НайденныеСтроки.Количество() > 1 Тогда
    ВремТЗ = ТЗ.Скопировать(НайденныеСтроки);
    ВремТЗ.Сортировать("СлужебныйПорядок Возр");
    // Работаем далее с ВремТЗ
КонецЕсли;

Решение 3: Использование запросов для связи таблиц

Если в алгоритме списания партий возникает связь «многие-ко-многим» (например, одна номенклатура в остатках по разным партиям и та же номенклатура в документе по разным ценам), лучшим решением будет использование запроса с временными таблицами. Это позволит платформе самой позаботиться об очередности и группировке данных.

Рассмотрим логику этого процесса:

  1. Выгружаем таблицу документа во временную таблицу (ВТ). Обязательно сохраняем НомерСтроки документа.
  2. Получаем остатки и помещаем их во вторую ВТ.
  3. Соединяем таблицы по Номенклатуре.
  4. В запросе применяем УПОРЯДОЧИТЬ ПО: сначала по номеру строки документа, затем по дате партии (для FIFO).

Этот способ исключает использование НайтиСтроки() в цикле и считается наиболее производительным и «правильным» с точки зрения архитектуры 1С.

Решение 4: Алгоритм Merge Join (Слияние двух отсортированных списков)

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

Алгоритм действий:

Такой подход гарантирует строгий порядок обработки и исключает неопределенность, которую вносит НайтиСтроки().

Резюме

Подведем итоги. Метод НайтиСтроки() удобен, но опасен для партионного учета из-за непредсказуемости порядка. Для надежной работы рекомендуется:

  1. При использовании в циклах — добавлять колонку с порядковым номером и сортировать результат.
  2. При больших объемах данных — переносить логику списания в запрос с использованием временных таблиц.
  3. Если важна производительность в памяти — использовать алгоритм слияния (Merge Join) на двух предварительно отсортированных таблицах.

Использование данных подходов позволит избежать трудноуловимых ошибок в расчетах себестоимости и обеспечит корректное списание партий в любой конфигурации.

← На главную