При разработке отчетов, обработок и печатных форм в 1С часто возникает задача: найти строки, в которых совпадают комбинации определенных значений (например, Номенклатура и Размер или Характеристика), и как-то их обработать — вывести признак повтора, отфильтровать или подсветить в печатной форме. Разберем различные варианты реализации этой задачи как на уровне языка запросов 1С, так и средствами встроенного языка.
Рассмотрим классический и наиболее производительный подход в языке запросов. Идея заключается в следующем: мы берем исходный набор данных, создаем на его основе вторую временную таблицу с группировкой по ключевым полям, отбираем только те комбинации, количество которых больше единицы (ИМЕЮЩИЕ КОЛИЧЕСТВО(*) > 1), и затем соединяем полученные дубли с исходными данными через ЛЕВОЕ СОЕДИНЕНИЕ.
Разберем реализацию по шагам:
ВТ_ИсходныеДанные и индексируем ее по полям сопоставления.ВТ_Дубли, сгруппировав записи по проверяемым колонкам с отбором строк, повторяющихся более одного раза.ВЫБОР вычисляем признак совпадения.Посмотрим на пример текста запроса:
ВЫБРАТЬ
кфПеренос.Ссылка КАК Документ,
кфПеренос.Номенклатура КАК Номенклатура,
кфПеренос.Размер КАК Размер,
кфПеренос.Расцветка КАК Расцветка
ПОМЕСТИТЬ ВТ_ИсходныеДанные
ИЗ
Документ.кфПереносМеждуСкладами.Перенос КАК кфПеренос
ГДЕ
кфПеренос.Ссылка.Дата >= &Дата
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВТ.Документ КАК Документ,
ВТ.Номенклатура КАК Номенклатура,
ВТ.Размер КАК Размер
ПОМЕСТИТЬ ВТ_Дубли
ИЗ
ВТ_ИсходныеДанные КАК ВТ
ГДЕ
НЕ ВТ.Номенклатура ЕСТЬ NULL
И НЕ ВТ.Размер ЕСТЬ NULL
СГРУППИРОВАТЬ ПО
ВТ.Документ,
ВТ.Номенклатура,
ВТ.Размер
ИМЕЮЩИЕ
КОЛИЧЕСТВО(*) > 1
ИНДЕКСИРОВАТЬ ПО
Документ,
Номенклатура,
Размер
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВТ.Документ КАК Документ,
ВТ.Номенклатура КАК Номенклатура,
ВТ.Размер КАК Размер,
ВТ.Расцветка КАК Расцветка,
ВЫБОР
КОГДА Дубли.Номенклатура ЕСТЬ NULL ТОГДА ЗНАЧЕНИЕ(Справочник.Номенклатура.ПустаяСсылка)
ИНАЧЕ ВТ.Номенклатура
КОНЕЦ КАК ПолеУсловие,
ВЫБОР
КОГДА Дубли.Номенклатура ЕСТЬ NULL ТОГДА ЛОЖЬ
ИНАЧЕ ИСТИНА
КОНЕЦ КАК ЭтоДубль
ИЗ
ВТ_ИсходныеДанные КАК ВТ
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Дубли КАК Дубли
ПО ВТ.Документ = Дубли.Документ
И ВТ.Номенклатура = Дубли.Номенклатура
И ВТ.Размер = Дубли.Размер
Важный нюанс: обратим внимание на поле Документ (или Ссылка). Если данные выбираются сразу по нескольким документам или заказам, группировку и соединение обязательно нужно выполнять с учетом ссылки на документ. В противном случае одинаковые товары из разных документов будут ошибочно помечены как повторяющиеся.
Также проанализируем обработку пустых значений: если поле Размер не заполнено, группировка СУБД посчитает все пустые строки дублями друг друга. Чтобы этого избежать, мы добавили проверку в секцию ГДЕ.
Если нам не требуется сохранять исходные строки без изменений, а нужно только выбрать строки, содержащие повторяющиеся позиции, мы можем использовать конструкцию сравнения кортежа полей через оператор В.
Посмотрим на пример такого запроса:
ВЫБРАТЬ
ВТ.Номенклатура КАК Номенклатура,
ВТ.Размер КАК Размер,
ВТ.Расцветка КАК Расцветка
ИЗ
ВТ КАК ВТ
ГДЕ
(ВТ.Номенклатура, ВТ.Размер) В
(ВЫБРАТЬ
Дубли.Номенклатура,
Дубли.Размер
ИЗ
ВТ КАК Дубли
СГРУППИРОВАТЬ ПО
Дубли.Номенклатура,
Дубли.Размер
ИМЕЮЩИЕ
КОЛИЧЕСТВО(*) > 1)
Такой вариант короче в написании, однако на больших объемах данных левое соединение с предварительно проиндексированной временной таблицей отрабатывает значительно быстрее и предсказуемее для оптимизатора СУБД.
Если выборка уже выгружена в коллекцию ТаблицаЗначений, найти повторяющиеся строки можно средствами встроенного языка 1С без повторного обращения к базе данных.
Разберем вариант со сворачиванием вспомогательной таблицы:
// Предположим, что ТаблицаДанных уже заполнена результатами запроса
ТаблицаДублей = ТаблицаДанных.Скопировать();
ТаблицаДублей.Колонки.Добавить("КоличествоПовторов", Новый ОписаниеТипов("Число"));
ТаблицаДублей.ЗаполнитьЗначения(1, "КоличествоПовторов");
// Сворачиваем таблицу по ключевым колонкам с суммированием количества
ТаблицаДублей.Свернуть("Документ, Номенклатура, Размер", "КоличествоПовторов");
// Удаляем уникальные строки, оставляем только дубли
Индекс = ТаблицаДублей.Количество() - 1;
Пока Индекс >= 0 Цикл
Если ТаблицаДублей[Индекс].КоличествоПовторов <= 1 Тогда
ТаблицаДублей.Удалить(Индекс);
КонецЕсли;
Индекс = Индекс - 1;
КонецЦикла;
// Индексируем вспомогательную таблицу для быстрого поиска
ТаблицаДублей.Индексы.Добавить("Документ, Номенклатура, Размер");
// Обходим исходную таблицу и маркируем дубли
Для Каждого Стр Из ТаблицаДанных Цикл
СтруктураПоиска = Новый Структура("Документ, Номенклатура, Размер", Стр.Документ, Стр.Номенклатура, Стр.Размер);
НайденнаяСтрока = ТаблицаДублей.НайтиСтроки(СтруктураПоиска);
Если НайденнаяСтрока.Количество() > 0 Тогда
// Строка повторяется в документе, выполняем целевое действие (например, выделение)
Стр.ЭтоДубль = Истина;
Иначе
Стр.ЭтоДубль = Ложь;
КонецЕсли;
КонецЦикла;
Также, если исходную таблицу менять не требуется, можно использовать прямой вызов метода ТаблицаДанных.НайтиСтроки() со структурой отбора по текущей строке. Если возвращенный массив содержит больше одного элемента (МассивСтрок.Количество() > 1), значит, в таблице есть дублирующиеся записи.
Если задача заключается только в визуальном выделении полей (например, подсветка цветом ячейки Расцветка в отчете или печатной форме на СКД), модифицировать запрос не обязательно. Выясним, как это настроить в СКД:
Номенклатура и Размер, а в детальные записи помещаем поле Расцветка.Количество() > 1 или используем выражение на встроенном языке СКД через функцию ВычислитьВыражение для проверки количества записей в текущей группе.Таким образом, для простых отчетов на СКД достаточно штатного механизма группировок и оформления, а для сложных алгоритмов и печатных форм оптимальным решением остается временная таблица с секцией ИМЕЮЩИЕ КОЛИЧЕСТВО(*) > 1.