При работе с объектом ТаблицаЗначений в платформе 1С 7.7 нередко возникает задача выборки только первой (или последней) уникальной строки для каждой группы товаров. Типичный пример: из таблицы всех реализаций нужно оставить только самый свежий документ по каждой номенклатуре.
Рассмотрим исходные условия. Пусть исходная ТаблицаЗначений предварительно сгруппирована по товару и отсортирована по полю Товар по возрастанию, а по полю Документ (или его дате) — по убыванию. В таком случае наиболее актуальный документ для каждой позиции оказывается на первой строчке своей группы. Разберем различные способы решения задачи: от базовых алгоритмов платформы до использования внешних компонент и архитектурных фишек.
Разберем наиболее оптимальный с точки зрения алгоритмической сложности способ. Если таблица уже отсортирована по схеме Товар (по возрастанию) + Документ (по убыванию), нам не нужно делать вызовы поиска по всей таблице. Достаточно пройти по таблице ровно один раз, запоминая значение номенклатуры из предыдущей строки.
Проанализируем пример кода:
ТекущийТовар = "";
ТЗ.ВыбратьСтроки();
Пока ТЗ.ПолучитьСтроку() = 1 Цикл
Если Не (ТЗ.Товар = ТекущийТовар) Тогда
ТекущийТовар = ТЗ.Товар;
Сообщить(ТекущийТовар + " Последний документ: " + ТЗ.ТекДок);
КонецЕсли;
КонецЦикла;
Выясним причину, почему этот способ работает быстрее остальных: его временная сложность составляет O(N), где N — количество строк в ТЗ. Мы не тратим ресурсы процессора на повторные поисковые операции, а просто фильтруем дублирующие строки прямо во время прохода цикла.
Рассмотрим вариант, когда у нас есть отдельный список товаров и требуется найти первую подходящую строку в ТЗ с помощью штатного метода НайтиЗначение.
При использовании функции НайтиЗначение важно помнить особенность платформы 1С 7.7: третье поле (индекс строки) работает как входной и выходной параметр. Если перед началом поиска по следующему товару не обнулить переменную индекса, система продолжит поиск, начиная с ранее найденной позиции, и результат окажется некорректным.
Посмотрим на правильную реализацию поиска:
ИндексНайдено = 0;
Для Сч = 1 По СписокТоваров.РазмерСписка() Цикл
// Обязательно сбрасываем индекс в 0 перед каждым вызовом НайтиЗначение!
ИндексНайдено = 0;
Если ТЗ.НайтиЗначение(СписокТоваров.ПолучитьЗначение(Сч), ИндексНайдено, "Товар") = 1 Тогда
Сообщить(ТЗ.ПолучитьЗначение(ИндексНайдено, "Товар") + " -> " + ТЗ.ПолучитьЗначение(ИндексНайдено, "ТекДок"));
КонецЕсли;
КонецЦикла;
Стоит учесть, что метод НайтиЗначение при каждом вызове выполняет сканирование таблицы. На больших объемах данных (десятки тысяч строк) этот подход уступает по скорости однопроходному обходу.
Если требуется выгрузить уникальные строки в новую таблицу значений, удобным фильтром служит объект СписокЗначений. Метод Принадлежит выполняет быструю проверку наличия элемента.
Разберем по шагам алгоритм фильтрации:
СписокЗначений для хранения уже обработанных номенклатурных позиций.ТаблицаЗначений для итогового результата.Посмотрим на пример реализации:
ОбработанныеТовары = СоздатьОбъект("СписокЗначений");
ТЗРезультат = СоздатьОбъект("ТаблицаЗначений");
// Копируем колонки исходной ТЗ
ТЗРезультат.НоваяКолонка("Товар");
ТЗРезультат.НоваяКолонка("ТекДок");
ТЗ.ВыбратьСтроки();
Пока ТЗ.ПолучитьСтроку() = 1 Цикл
Товар = ТЗ.Товар;
Если ОбработанныеТовары.Принадлежит(Товар) = 0 Тогда
ОбработанныеТовары.ДобавитьЗначение(Товар);
ТЗРезультат.НоваяСтрока();
ТЗРезультат.Товар = Товар;
ТЗРезультат.ТекДок = ТЗ.ТекДок;
КонецЕсли;
КонецЦикла;
Когда необходимо оставить только первые уникальные строки в исходной таблице значений без создания копий, применяется стратегия удаления строк с конца таблицы.
Проанализируем алгоритм:
Товар+ (по возрастанию) и ДатаDoc+ (по возрастанию). При такой сортировке самый свежий документ оказывается в конце группы каждого товара.Для Сч = -ТЗ.КоличествоСтрок() По -1 Цикл.УдалитьСтроку().Посмотрим на пример кода:
// Сортируем: товар по возрастанию, дата по возрастанию
ТЗ.Сортировать("Товар+,ДатаDoc+");
ТекущийТовар = "";
ВсегоСтрок = ТЗ.КоличествоСтрок();
Для Сч = -ВсегоСтрок По -1 Цикл
Индекс = -Сч;
ТЗ.ПолучитьСтрокуПоНомеру(Индекс);
Если ТЗ.Товар = ТекущийТовар Тогда
// Строка дублируется и содержит более старый документ — удаляем
ТЗ.УдалитьСтроку(Индекс);
Иначе
ТекущийТовар = ТЗ.Товар;
КонецЕсли;
КонецЦикла;
Если таблица значений изначально формируется из базы данных через встроенный язык запросов 1С 7.7, отбор первой записи можно выполнить прямо на этапе обхода результатов запроса без последующей обработки ТЗ.
Рассмотрим структуру такого запроса:
Запрос = СоздатьОбъект("Запрос");
ТекстЗапроса =
"//{{ЗАПРОС(СрезПоследних)
|Товар = Документ.Реализация.Товар;
|Док = Документ.Реализация.ТекущийДокумент;
|Группировка Товар Без Групп;
|Группировка Док;
|";
Если Запрос.Выполнить(ТекстЗапроса) = 1 Тогда
Пока Запрос.Группировка("Товар") = 1 Цикл
// Заходим во вложенную группировку строго один раз!
Если Запрос.Группировка("Док") = 1 Тогда
// В этой точке мы моментально получаем первый (самый актуальный) документ
Сообщить("Товар: " + Запрос.Товар + " -> Последний документ: " + Запрос.Док);
КонецЕсли;
КонецЦикла;
КонецЕсли;
Такой подход экономит память и время выполнения, так как обработка происходит на уровне позиционирования курсора выборки.
Платформа 1С 7.7 позволяет создавать колонки таблицы значений с типом ТаблицаЗначений. Этот подход удобен, когда для каждого товара требуется хранить не только последний документ, но и иметь быстрый доступ ко всем остальным документам без использования сплошных плоских таблиц.
Посмотрим на структуру построения вложенных ТЗ:
ТЗГлавная = СоздатьОбъект("ТаблицаЗначений");
ТЗГлавная.НоваяКолонка("Товар");
ТЗГлавная.НоваяКолонка("ТабДокументов"); // Колонка для хранения подчиненной ТЗ
// Заполнение структуры
ТЗГлавная.НоваяСтрока();
ТЗГлавная.Товар = ВыбранныйТовар;
ТЗДокументов = СоздатьОбъект("ТаблицаЗначений");
ТЗДокументов.НоваяКолонка("Документ");
ТЗДокументов.НоваяКолонка("Дата");
// Помещаем подчиненную таблицу в ячейку
ТЗГлавная.ТабДокументов = ТЗДокументов;
Чтобы извлечь самый свежий документ для любого товара, достаточно обратиться к первой строке подчиненной таблицы значений конкретного товара: ТЗГлавная.ТабДокументов.ПолучитьЗначение(1, "Документ").
Если в системе установлена библиотека 1С++, решение задачи получения среза последних данных ускоряется в десятки раз за счет следующих инструментов:
ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...)).GROUP BY прямо по объекту ТаблицаЗначений в оперативной памяти.Мы проанализировали основные способы получения первых уникальных записей по группам в 1С 7.7. Для большинства стандартных задач наилучшим выбором остается линейный однопроходный обход (Метод 1): он гарантирует максимальное быстродействие, не требует применения сторонних компонент и прост в поддержке.