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