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