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