Как работает предложение ИМЕЮЩИЕ и функция КОЛИЧЕСТВО(*) в запросах 1С?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При работе с языком запросов 1С программисты часто сталкиваются с необходимостью фильтрации данных, которые уже прошли процесс группировки. Для этих целей используется предложение ИМЕЮЩИЕ (аналог SQL-конструкции HAVING). Однако у новичков часто возникает вопрос: почему при использовании КОЛИЧЕСТВО(*) в этом блоке результат может казаться избыточным или нелогичным? Разберем эту ситуацию на конкретном примере и проанализируем внутреннюю механику выполнения запроса.

Суть проблемы: почему "схлопнутые" строки все равно считаются?

Рассмотрим ситуацию, когда в табличной части документа есть записи с одинаковыми значениями в полях группировки. Например, у нас есть две идентичные строки для сотрудника "Петрова Марина" с причиной "Развод". При выполнении запроса с СГРУППИРОВАТЬ ПО эти строки визуально объединяются в одну в результирующей таблице. Возникает закономерный вопрос: если они "схлопнулись", то почему условие ИМЕЮЩИЕ КОЛИЧЕСТВО(*) > 1 их находит?

Разберем это на примере кода запроса:


Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ
               |    ДанныеСотрудниковДанные.ФизЛицо КАК ФизЛицо,
               |    ДанныеСотрудниковДанные.ПричинаИзменения КАК ПричинаИзменения,
               |    КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ДанныеСотрудниковДанные.Фамилия) КАК Фамилия
               |ИЗ
               |    Документ.ДанныеСотрудников.Данные КАК ДанныеСотрудниковДанные
               |ГДЕ
               |    ДанныеСотрудниковДанные.Ссылка = &Ссылка
               |
               |СГРУППИРОВАТЬ ПО
               |    ДанныеСотрудниковДанные.ФизЛицо,
               |    ДанныеСотрудниковДанные.ПричинаИзменения
               |
               |ИМЕЮЩИЕ
               |    КОЛИЧЕСТВО(*) > 1";

Выясним причину такого поведения: КОЛИЧЕСТВО(*) работает не с итоговыми строками выборки, а с исходными записями, которые попали в "корзину" (группу) при выполнении операции СГРУППИРОВАТЬ ПО. Если в одну группу попало две строки из исходной таблицы, то КОЛИЧЕСТВО(*) будет равно 2, даже если в блоке ВЫБРАТЬ вы выводите только уникальные значения.

Логический порядок выполнения запроса

Чтобы понять, почему фильтрация происходит именно так, нам нужно рассмотреть порядок, в котором СУБД (и движок запросов 1С) обрабатывает инструкции. Проанализируем стандартную последовательность:

  1. ИЗ (FROM) — Сначала выбирается источник данных (таблица документа).
  2. ГДЕ (WHERE) — Происходит первичная фильтрация строк. На этом этапе отсекаются все записи, не соответствующие условию (например, по конкретной ссылке).
  3. СГРУППИРОВАТЬ ПО (GROUP BY) — Оставшиеся строки распределяются по группам. Все записи с одинаковыми значениями в указанных полях (ФизЛицо, ПричинаИзменения) помещаются в один набор.
  4. ИМЕЮЩИЕ (HAVING) — Важный момент! Именно сейчас применяется условие к каждой группе. Если в группе "Петрова - Развод" оказалось 2 строки, условие КОЛИЧЕСТВО(*) > 1 вернет истину.
  5. ВЫБРАТЬ (SELECT) — Только теперь формируются финальные колонки. Если у вас стоит КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Фамилия), система посмотрит внутрь группы и посчитает только уникальные фамилии.
  6. УПОРЯДОЧИТЬ ПО (ORDER BY) — Финальная сортировка готового результата.

Таким образом, ИМЕЮЩИЕ видит структуру групп "изнутри", до того как они окончательно превратятся в плоскую таблицу результата.

Различия между КОЛИЧЕСТВО(*), КОЛИЧЕСТВО(Поле) и КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Поле)

Рассмотрим подробнее, как разные виды агрегатной функции КОЛИЧЕСТВО влияют на результат в блоке ИМЕЮЩИЕ:

1. КОЛИЧЕСТВО(*)
Эта функция подсчитывает общее число физических записей, попавших в группу. Ей неважно, содержат ли поля значения NULL или являются ли они дубликатами. Если в табличной части было две одинаковые строки, КОЛИЧЕСТВО(*) честно вернет 2.

2. КОЛИЧЕСТВО(Фамилия)
Подсчитывает количество записей, в которых поле "Фамилия" заполнено (не равно NULL). Если у одной из строк фамилия не заполнена, результат будет 1.

3. КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Фамилия)
Сначала внутри группы отбираются только уникальные значения поля "Фамилия", а затем подсчитывается их количество. Если Петрова Марина меняла фамилию дважды на одну и ту же (например, фиктивные браки), то КОЛИЧЕСТВО(РАЗЛИЧНЫЕ) вернет 1, в то время как КОЛИЧЕСТВО(*) вернет 2.

Практические советы по использованию ИМЕЮЩИЕ

Рассмотрим несколько сценариев применения этой конструкции для решения типовых задач программиста 1С:

Сценарий 1: Поиск дублей

Если вам нужно найти записи, которые были ошибочно введены дважды, используйте следующий шаблон:


ВЫБРАТЬ
    Т.ФизЛицо,
    Т.ПричинаИзменения,
    КОЛИЧЕСТВО(*) КАК КоличествоСтрок
ИЗ
    Документ.ДанныеСотрудников.Данные КАК Т
СГРУППИРОВАТЬ ПО
    Т.ФизЛицо,
    Т.ПричинаИзменения
ИМЕЮЩИЕ
    КОЛИЧЕСТВО(*) > 1

Этот запрос покажет только те комбинации сотрудника и причины, которые встречаются в базе более одного раза.

Сценарий 2: Фильтрация по сумме

Часто требуется отобрать только тех контрагентов, сумма оборотов по которым превысила определенный порог. В этом случае ИМЕЮЩИЕ незаменим:


ВЫБРАТЬ
    Продажи.Контрагент,
    СУММА(Продажи.СуммаОборот) КАК ОбщаяСумма
ИЗ
    РегистрНакопления.Продажи.Обороты КАК Продажи
СГРУППИРОВАТЬ ПО
    Продажи.Контрагент
ИМЕЮЩИЕ
    СУММА(Продажи.СуммаОборот) > 100000

Сравнение производительности: ГДЕ против ИМЕЮЩИЕ

Проанализируем ситуацию с точки зрения эффективности. Важно помнить правило: все, что можно отсечь в ГДЕ, должно быть отсечено в ГДЕ.

Условие ГДЕ фильтрует строки до того, как СУБД начнет тратить ресурсы на их группировку и подсчет агрегатов. Условие ИМЕЮЩИЕ работает уже после того, как вся тяжелая работа по группировке выполнена. Используйте ИМЕЮЩИЕ исключительно для условий на результаты функций СУММА, КОЛИЧЕСТВО, СРЕДНЕЕ, МАКСИМУМ, МИНИМУМ.

Важное ограничение в 1С: В секции ИМЕЮЩИЕ нельзя использовать псевдонимы полей (те, что вы указали через слово КАК в ВЫБРАТЬ). Вы всегда должны прописывать агрегатную функцию полностью, например: ИМЕЮЩИЕ СУММА(СуммаДокумента) > 0.

Подводя итог нашему разбору, можно сказать: ИМЕЮЩИЕ — это мощный инструмент постобработки групп. Понимая, что КОЛИЧЕСТВО(*) считает "сырые" строки внутри группы, вы сможете легко диагностировать причины попадания лишних данных в ваши отчеты и запросы. Для интерактивной проверки и отладки подобных конструкций подойдёт инженерная консоль запросов для 1С.

← На главную