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