Как получить первую уникальную запись по группе из отсортированной таблицы значений в 1С 7.7?

Программист 1С v7.7 IT и автоматизация бизнеса
← На главную

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

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

Метод 1. Линейный однопроходный обход (Самый эффективный и простой метод)

Разберем наиболее оптимальный с точки зрения алгоритмической сложности способ. Если таблица уже отсортирована по схеме Товар (по возрастанию) + Документ (по убыванию), нам не нужно делать вызовы поиска по всей таблице. Достаточно пройти по таблице ровно один раз, запоминая значение номенклатуры из предыдущей строки.

Проанализируем пример кода:


ТекущийТовар = "";
ТЗ.ВыбратьСтроки();
Пока ТЗ.ПолучитьСтроку() = 1 Цикл
    Если Не (ТЗ.Товар = ТекущийТовар) Тогда
        ТекущийТовар = ТЗ.Товар;
        Сообщить(ТекущийТовар + " Последний документ: " + ТЗ.ТекДок);
    КонецЕсли;
КонецЦикла;

Выясним причину, почему этот способ работает быстрее остальных: его временная сложность составляет O(N), где N — количество строк в ТЗ. Мы не тратим ресурсы процессора на повторные поисковые операции, а просто фильтруем дублирующие строки прямо во время прохода цикла.

Метод 2. Поиск через метод НайтиЗначение и сброс индекса

Рассмотрим вариант, когда у нас есть отдельный список товаров и требуется найти первую подходящую строку в ТЗ с помощью штатного метода НайтиЗначение.

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

Посмотрим на правильную реализацию поиска:


ИндексНайдено = 0; 

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

Стоит учесть, что метод НайтиЗначение при каждом вызове выполняет сканирование таблицы. На больших объемах данных (десятки тысяч строк) этот подход уступает по скорости однопроходному обходу.

Метод 3. Использование объекта СписокЗначений в качестве хэш-таблицы

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

Разберем по шагам алгоритм фильтрации:

  1. Создаем вспомогательный объект СписокЗначений для хранения уже обработанных номенклатурных позиций.
  2. Создаем новую ТаблицаЗначений для итогового результата.
  3. В цикле обходим отсортированную исходную ТЗ.
  4. Если текущий товар еще не находится в списке — добавляем его в список и копируем строку в итоговую ТЗ. Все последующие строки с этим же товаром пропускаем.

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


ОбработанныеТовары = СоздатьОбъект("СписокЗначений");
ТЗРезультат = СоздатьОбъект("ТаблицаЗначений");

// Копируем колонки исходной ТЗ
ТЗРезультат.НоваяКолонка("Товар");
ТЗРезультат.НоваяКолонка("ТекДок");

ТЗ.ВыбратьСтроки();
Пока ТЗ.ПолучитьСтроку() = 1 Цикл
    Товар = ТЗ.Товар;
    Если ОбработанныеТовары.Принадлежит(Товар) = 0 Тогда
        ОбработанныеТовары.ДобавитьЗначение(Товар);
        
        ТЗРезультат.НоваяСтрока();
        ТЗРезультат.Товар = Товар;
        ТЗРезультат.ТекДок = ТЗ.ТекДок;
    КонецЕсли;
КонецЦикла;

Метод 4. Обратный перебор таблицы и удаление лишних строк

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

Проанализируем алгоритм:

  1. Сортируем исходную ТЗ по правилу: Товар+ (по возрастанию) и ДатаDoc+ (по возрастанию). При такой сортировке самый свежий документ оказывается в конце группы каждого товара.
  2. Запускаем цикл от конца таблицы к ее началу, используя обратный счетчик Для Сч = -ТЗ.КоличествоСтрок() По -1 Цикл.
  3. Если товар в текущей строке совпадает с товаром предыдущей (более свежей) строки, удаляем текущую строку с помощью УдалитьСтроку().

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


// Сортируем: товар по возрастанию, дата по возрастанию
ТЗ.Сортировать("Товар+,ДатаDoc+");

ТекущийТовар = "";
ВсегоСтрок = ТЗ.КоличествоСтрок();

Для Сч = -ВсегоСтрок По -1 Цикл
    Индекс = -Сч;
    ТЗ.ПолучитьСтрокуПоНомеру(Индекс);
    
    Если ТЗ.Товар = ТекущийТовар Тогда
        // Строка дублируется и содержит более старый документ — удаляем
        ТЗ.УдалитьСтроку(Индекс);
    Иначе
        ТекущийТовар = ТЗ.Товар;
    КонецЕсли;
КонецЦикла;

Метод 5. Оптимизация на уровне Черного Запроса 1С 7.7

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

Рассмотрим структуру такого запроса:


Запрос = СоздатьОбъект("Запрос");
ТекстЗапроса = 
"//{{ЗАПРОС(СрезПоследних)
|Товар = Документ.Реализация.Товар;
|Док = Документ.Реализация.ТекущийДокумент;
|Группировка Товар Без Групп;
|Группировка Док;
|";

Если Запрос.Выполнить(ТекстЗапроса) = 1 Тогда
    Пока Запрос.Группировка("Товар") = 1 Цикл
        // Заходим во вложенную группировку строго один раз!
        Если Запрос.Группировка("Док") = 1 Тогда
            // В этой точке мы моментально получаем первый (самый актуальный) документ
            Сообщить("Товар: " + Запрос.Товар + " -> Последний документ: " + Запрос.Док);
        КонецЕсли;
    КонецЦикла;
КонецЕсли;

Такой подход экономит память и время выполнения, так как обработка происходит на уровне позиционирования курсора выборки.

Метод 6. Вложенные Таблицы Значений

Платформа 1С 7.7 позволяет создавать колонки таблицы значений с типом ТаблицаЗначений. Этот подход удобен, когда для каждого товара требуется хранить не только последний документ, но и иметь быстрый доступ ко всем остальным документам без использования сплошных плоских таблиц.

Посмотрим на структуру построения вложенных ТЗ:


ТЗГлавная = СоздатьОбъект("ТаблицаЗначений");
ТЗГлавная.НоваяКолонка("Товар");
ТЗГлавная.НоваяКолонка("ТабДокументов"); // Колонка для хранения подчиненной ТЗ

// Заполнение структуры
ТЗГлавная.НоваяСтрока();
ТЗГлавная.Товар = ВыбранныйТовар;

ТЗДокументов = СоздатьОбъект("ТаблицаЗначений");
ТЗДокументов.НоваяКолонка("Документ");
ТЗДокументов.НоваяКолонка("Дата");

// Помещаем подчиненную таблицу в ячейку
ТЗГлавная.ТабДокументов = ТЗДокументов;

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

Метод 7. Использование внешних компонент (1С++)

Если в системе установлена библиотека 1С++, решение задачи получения среза последних данных ускоряется в десятки раз за счет следующих инструментов:

Подведение итогов

Мы проанализировали основные способы получения первых уникальных записей по группам в 1С 7.7. Для большинства стандартных задач наилучшим выбором остается линейный однопроходный обход (Метод 1): он гарантирует максимальное быстродействие, не требует применения сторонних компонент и прост в поддержке.

← На главную