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