Работа с запросами в 1С:Предприятие — одна из ключевых задач разработчика, и два оператора, ГДЕ и ИМЕЮЩИЕ, часто становятся источником путаницы. На первый взгляд оба фильтруют данные, но их логика и область применения принципиально разные. Ошибка в выборе между ними может привести к некорректным отчётам, медленной работе системы или даже потере данных.

В этой статье разберём фундаментальные отличия между ГДЕ и ИМЕЮЩИЕ: как они работают на уровне СУБД, в каких сценариях каждый из них незаменим, и почему неправильный выбор оператора может «сломать» ваш запрос. Также рассмотрим практические примеры с кодом, типичные ошибки новичков и оптимизацию запросов для крупных баз данных.

Если вы когда-либо сталкивались с ситуацией, когда запрос возвращает неожиданные результаты или работает слишком долго — эта статья поможет разобраться в корне проблемы.

1. Базовый синтаксис: как работают «ГДЕ» и «ИМЕЮЩИЕ»

Оператор ГДЕ применяется к отдельным строкам таблицы или виртуальной таблицы запроса. Он фильтрует записи до группировки данных, то есть отсеивает те строки, которые не соответствуют условию. Например:

ВЫБРАТЬ

Товар.Наименование,

Товар.Цена

ИЗ

Справочник.Товары КАК Товар

ГДЕ

Товар.Цена > 1000

Здесь условие Цена > 1000 проверяется для каждой строки справочника Товары индивидуально. Если цена товара меньше или равна 1000, строка не попадёт в результат.

Оператор ИМЕЮЩИЕ, напротив, работает после группировки. Он фильтрует уже сгруппированные данные по агрегированным значениям (например, суммам, количествам, средним). Синтаксически он всегда идёт после блока СГРУППИРОВАТЬ ПО:

ВЫБРАТЬ

Клиент.Наименование,

СУММА(Документ.СуммаДокумента) КАК ОбщаяСумма

ИЗ

Документ.РеализацияТоваровУслуг КАК Документ

ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Клиенты КАК Клиент

ПО Документ.Клиент = Клиент.Ссылка

СГРУППИРОВАТЬ ПО

Клиент.Наименование

ИМЕЮЩИЕ

СУММА(Документ.СуммаДокумента) > 50000

В этом примере сначала формируются группы по клиентам, рассчитывается сумма их заказов, и только затем отсеиваются клиенты с общей суммой менее 50 000. Использование «ГДЕ» вместо «ИМЕЮЩИЕ» здесь приведёт к ошибке, так как агрегированное поле «ОбщаяСумма» ещё не существует на этапе фильтрации строк.

📊 Как часто вы используете оператор ИМЕЮЩИЕ в запросах 1С?
Часто, в большинстве отчётов
Иногда, когда нужна группировка
Рядом не стоял, не знаю что это
Никогда, обхожусь без него

2. Ключевые различия: когда что применять

Основное правило: ГДЕ фильтрует исходные данные, а ИМЕЮЩИЕрезультаты группировки. Но есть нюансы, которые важно учитывать:

  • 🔍 Уровень применения: ГДЕ работает на уровне отдельных записей (до СГРУППИРОВАТЬ ПО), ИМЕЮЩИЕ — на уровне групп (после СГРУППИРОВАТЬ ПО).
  • 📊 Агрегатные функции: В ИМЕЮЩИЕ можно использовать СУММА(), КОЛИЧЕСТВО(), МАКСИМУМ() и т.д. В ГДЕ они недопустимы (если не используются в подзапросах).
  • Производительность: ГДЕ обычно эффективнее, так как сокращает объём данных до группировки. ИМЕЮЩИЕforced обрабатывать все группы, что может быть ресурсоёмко для больших баз.
  • 🔄 Логика отбора: ГДЕ исключает строки, не соответствующие условию, ИМЕЮЩИЕ — целые группы.

Пример ошибки: если в запросе с группировкой по клиентам вы хотите отфильтровать только тех, кто сделал заказы на сумму больше 10 000, но пишете условие в ГДЕ, система либо выдаст ошибку (если используете агрегатную функцию), либо вернёт неверный результат (если условие применимо к исходным строкам).

⚠️ Внимание: В некоторых СУБД (например, PostgreSQL или MS SQL) оператор HAVING (аналог ИМЕЮЩИЕ) может оптимизироваться лучше, чем в 1С. В платформе 1С всегда проверяйте план выполнения запроса через ОбъяснитьЗапрос().
Критерий Оператор ГДЕ Оператор ИМЕЮЩИЕ
Уровень фильтрации Отдельные строки Группы строк
Использование агрегатных функций Нет (кроме подзапросов) Да
Позиция в запросе После ИЗ, до СГРУППИРОВАТЬ ПО После СГРУППИРОВАТЬ ПО
Пример условия Товар.Цена > 1000 СУММА(СуммаДокумента) > 50000

3. Практические примеры: когда «ГДЕ» не подходит

Рассмотрим реальные задачи, где ИМЕЮЩИЕ — единственно верное решение.

Задача 1: Найти клиентов, которые сделали более 5 заказов за месяц. Здесь нельзя использовать ГДЕ, так как нужно считать количество заказов по каждому клиенту:

ВЫБРАТЬ

Клиент.Наименование,

КОЛИЧЕСТВО(Документ.Ссылка) КАК КоличествоЗаказов

ИЗ

Документ.ЗаказКлиента КАК Документ

ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Клиенты КАК Клиент

ПО Документ.Клиент = Клиент.Ссылка

ГДЕ

Документ.Дата МЕЖДУ &НачалоМесяца И &КонецМесяца

СГРУППИРОВАТЬ ПО

Клиент.Наименование

ИМЕЮЩИЕ

КОЛИЧЕСТВО(Документ.Ссылка) > 5

Задача 2: Вывести номенклатуру, по которой средняя цена продажи выше средней цены закупки. Здесь требуется сравнение агрегированных значений:

ВЫБРАТЬ

Номенклатура.Наименование,

СРЕДНЕЕ(Продажи.Цена) КАК СредняяЦенаПродажи,

СРЕДНЕЕ(Закупки.Цена) КАК СредняяЦенаЗакупки

ИЗ

Документ.РеализацияТоваровУслуг.Товары КАК Продажи

ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.ПоступлениеТоваровУслуг.Товары КАК Закупки

ПО Продажи.Номенклатура = Закупки.Номенклатура

ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК Номенклатура

ПО Продажи.Номенклатура = Номенклатура.Ссылка

СГРУППИРОВАТЬ ПО

Номенклатура.Наименование

ИМЕЮЩИЕ

СРЕДНЕЕ(Продажи.Цена) > СРЕДНЕЕ(Закупки.Цена)

💡

Если в запросе с ИМЕЮЩИЕ вы получаете пустой результат, проверьте, не забыли ли вы указать СГРУППИРОВАТЬ ПО. Без группировки оператор ИМЕЮЩИЕ не имеет смысла и будет проигнорирован (или вызовет ошибку).

4. Типичные ошибки и как их избежать

Даже опытные разработчики иногда путают ГДЕ и ИМЕЮЩИЕ. Вот наиболее распространённые ошибки:

  • 🚫 Агрегатные функции в «ГДЕ»: Попытка написать ГДЕ СУММА(Сумма) > 1000 приведёт к ошибке. Агрегаты разрешены только в ИМЕЮЩИЕ или подзапросах.
  • 🔄 Лишняя фильтрация в «ГДЕ»: Если вы отфильтруете строки в ГДЕ, а затем сгруппируете их, некоторые группы могут исчезнуть из результата, что исказит итоговые суммы.
  • ⚠️ Отсутствие «СГРУППИРОВАТЬ ПО»: Без группировки ИМЕЮЩИЕ не имеет смысла — запрос либо упадёт, либо проигнорирует условие.
  • 📉 Неэффективные условия: Условия в ИМЕЮЩИЕ выполняются после группировки, поэтому они могут значительно замедлить запрос на больших данных.

Пример ошибочного запроса:

ВЫБРАТЬ

Клиент.Наименование,

СУММА(Документ.СуммаДокумента) КАК Итог

ИЗ

Документ.РеализацияТоваровУслуг КАК Документ

ГДЕ

СУММА(Документ.СуммаДокумента) > 10000 // ОШИБКА!

СГРУППИРОВАТЬ ПО

Клиент.Наименование

Исправленный вариант:

ВЫБРАТЬ

Клиент.Наименование,

СУММА(Документ.СуммаДокумента) КАК Итог

ИЗ

Документ.РеализацияТоваровУслуг КАК Документ

СГРУППИРОВАТЬ ПО

Клиент.Наименование

ИМЕЮЩИЕ

СУММА(Документ.СуммаДокумента) > 10000

⚠️ Внимание: В некоторых версиях 1С (особенно до 8.3.10) оптимизатор запросов мог некорректно обрабатывать сложные условия в ИМЕЮЩИЕ. Если запрос работает медленно, попробуйте разбить его на подзапросы или использовать временные таблицы.
Почему нельзя использовать агрегаты в ГДЕ?

На этапе фильтрации строк (оператор ГДЕ) СУБД ещё не вычислила агрегированные значения (суммы, количества и т.д.), поэтому не может их сравнить. Агрегаты становятся доступны только после группировки, когда данные уже сгруппированы и подсчитаны.

5. Оптимизация запросов: когда «ИМЕЮЩИЕ» тормозит систему

Оператор ИМЕЮЩИЕ может стать узким местом в производительности, особенно если:

  • 📈 В базе миллионы записей, а группировка образует десятки тысяч групп.
  • 🔄 Условие в ИМЕЮЩИЕ содержит сложные вычисления (например, вложенные агрегаты).
  • 🖥️ Сервер 1С или СУБД имеет ограниченные ресурсы (ОЗУ, CPU).

Как ускорить такие запросы:

  1. Перенесите часть фильтрации в ГДЕ: Отсекайте ненужные строки до группировки. Например, если вам нужны клиенты с суммой заказов > 50 000, сначала отфильтруйте заказы за нужный период в ГДЕ.
  2. Используйте подзапросы: Иногда разбиение большого запроса на несколько маленьких работает быстрее.
  3. Применяйте индексы: Убедитесь, что поля, по которым идёт группировка или фильтрация, проиндексированы в СУБД.
  4. Ограничивайте период: Если возможно, добавьте условие по дате в ГДЕ, чтобы сократить объём обрабатываемых данных.

Пример оптимизированного запроса:

ВЫБРАТЬ

Клиент.Наименование,

СУММА(Документ.СуммаДокумента) КАК Итог

ИЗ

Документ.РеализацияТоваровУслуг КАК Документ

ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Клиенты КАК Клиент

ПО Документ.Клиент = Клиент.Ссылка

ГДЕ

Документ.Дата >= &НачалоПериода

И Документ.Дата <= &КонецПериода

И Документ.СуммаДокумента > 1000 // Отсекаем мелкие заказы заранее

СГРУППИРОВАТЬ ПО

Клиент.Наименование

ИМЕЮЩИЕ

СУММА(Документ.СуммаДокумента) > 50000

Есть ли условие по дате в ГДЕ?|Отфильтрованы ли заведомо ненужные строки?|Проиндексированы ли поля для группировки?|Можно ли разбить запрос на подзапросы?|Проверен ли план выполнения через ОбъяснитьЗапрос()?

-->

6. Сложные случаи: вложенные агрегаты и многоуровневая группировка

Иногда требуется фильтрация по агрегатам агрегатов. Например: «найти регионы, где средняя сумма заказа клиентов выше средней по всем регионам». Здесь придётся использовать подзапросы или временные таблицы.

Пример с подзапросом:

ВЫБРАТЬ

Регион.Наименование,

СРЕДНЕЕ(КлиентСумма.Итог) КАК СредняяПоРегиону

ИЗ

Справочник.Регионы КАК Регион

ЛЕВОЕ СОЕДИНЕНИЕ (

ВЫБРАТЬ

Клиент.Регион КАК Регион,

СУММА(Документ.СуммаДокумента) КАК Итог

ИЗ

Документ.РеализацияТоваровУслуг КАК Документ

ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Клиенты КАК Клиент

ПО Документ.Клиент = Клиент.Ссылка

ГДЕ

Документ.Дата МЕЖДУ &НачалоГода И &КонецГода

СГРУППИРОВАТЬ ПО

Клиент.Регион

ИМЕЮЩИЕ

СУММА(Документ.СуммаДокумента) > 0

) КАК КлиентСумма

ПО Регион.Ссылка = КлиентСумма.Регион

ГДЕ

СРЕДНЕЕ(КлиентСумма.Итог) > (

ВЫБРАТЬ

СРЕДНЕЕ(СуммаПоРегиону.Итог)

ИЗ

(

ВЫБРАТЬ

СУММА(Документ.СуммаДокумента) КАК Итог

ИЗ

Документ.РеализацияТоваровУслуг КАК Документ

ГДЕ

Документ.Дата МЕЖДУ &НачалоГода И &КонецГода

) КАК СуммаПоРегиону

)

Такой запрос сложен для восприятия, но он демонстрирует, как можно комбинировать ГДЕ и ИМЕЮЩИЕ на разных уровнях вложенности. Для упрощения рекомендуется:

  • 🧩 Разбивать запрос на части с использованием временных таблиц (ВТ).
  • 📝 Документировать логику каждого блока комментариями.
  • ⚡ Тестировать производительность на реальных данных.
⚠️ Внимание: Вложенные агрегаты могут приводить к неожиданным результатам, если не учитывать NULL-значения. Например, СРЕДНЕЕ игнорирует NULL, а СУММА — нет. Всегда проверяйте промежуточные результаты.

7. Альтернативы: когда можно обойтись без «ИМЕЮЩИЕ»

В некоторых случаях ИМЕЮЩИЕ можно заменить другими конструкциями:

  • 🔄 Подзапросы в ИЗ: Если нужно отфильтровать группы по агрегату, иногда проще сначала получить агрегированные данные во временной таблице, а затем присоединить её.
  • 📌 Функция ВЫРАЗИТЬ: В новых версиях 1С (8.3.15+) можно использовать ВЫРАЗИТЬ для создания вычисляемых полей с агрегатами.
  • 🗃️ Виртуальные таблицы: Например, РегистрНакопления.ОстаткиИОбороты уже содержат агрегированные данные, что избавляет от необходимости группировки.

Пример с временной таблицей:

// Сначала получаем агрегированные данные

Запрос = Новый Запрос;

Запрос.Текст =

"ВЫБРАТЬ

Клиент.Ссылка КАК Клиент,

СУММА(Документ.СуммаДокумента) КАК Итог

ИЗ

Документ.РеализацияТоваровУслуг КАК Документ

ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Клиенты КАК Клиент

ПО Документ.Клиент = Клиент.Ссылка

ГДЕ

Документ.Дата МЕЖДУ &НачалоМесяца И &КонецМесяца

СГРУППИРОВАТЬ ПО

Клиент.Ссылка";

Результат = Запрос.Выполнить();

ВТ_Клиенты = Результат.Выгрузить();

// Затем фильтруем по агрегату

Запрос = Новый Запрос;

Запрос.Текст =

"ВЫБРАТЬ

Клиенты.Итог КАК СуммаЗаказов

ИЗ

&ВТ_Клиенты КАК Клиенты

ГДЕ

Клиенты.Итог > 50000";

Запрос.УстановитьПараметр("ВТ_Клиенты", ВТ_Клиенты);

Результат = Запрос.Выполнить();

Такой подход может быть эффективнее, особенно если агрегированные данные нужны в нескольких местах кода.

💡

Если запрос с ИМЕЮЩИЕ работает медленно, попробуйте заменить его на подзапрос с временной таблицей. Это часто ускоряет выполнение за счёт уменьшения объёма данных на каждом этапе.

FAQ: Ответы на частые вопросы

Можно ли использовать ИМЕЮЩИЕ без СГРУППИРОВАТЬ ПО?

Нет, это бессмысленно. Оператор ИМЕЮЩИЕ предназначен для фильтрации групп, а без группировки групп просто нет. В лучшем случае условие будет проигнорировано, в худшем — запрос упадёт с ошибкой.

Почему мой запрос с ИМЕЮЩИЕ возвращает пустой результат, хотя данные есть?

Вероятные причины:

  1. Условие в ИМЕЮЩИЕ слишком жёсткое (например, СУММА > 1 000 000, хотя максимальная сумма в данных — 500 000).
  2. Ошибка в логике группировки: возможно, вы сгруппировали данные не по тому полю.
  3. Фильтрация в ГДЕ отсекла все строки, которые могли бы образовать группы.

Проверьте промежуточные результаты без условия ИМЕЮЩИЕ, чтобы понять, какие группы формируются.

Как узнать, что лучше использовать — ГДЕ или ИМЕЮЩИЕ?

Ответьте на два вопроса:

  1. Нужно ли вам фильтровать отдельные строки (до группировки)? → Используйте ГДЕ.
  2. Нужно ли фильтровать группы строк по агрегированным значениям (суммам, количествам и т.д.)? → Используйте ИМЕЮЩИЕ.

Если сомневаетесь, попробуйте написать запрос обоими способами и сравните результаты.

Можно ли в одном запросе использовать и ГДЕ, и ИМЕЮЩИЕ?

Да, это стандартная практика. Например:

ВЫБРАТЬ

Клиент.Наименование,

СУММА(Документ.СуммаДокумента) КАК Итог

ИЗ

Документ.РеализацияТоваровУслуг КАК Документ

ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Клиенты КАК Клиент

ПО Документ.Клиент = Клиент.Ссылка

ГДЕ

Документ.Дата > &НачалоГода // Фильтр строк

СГРУППИРОВАТЬ ПО

Клиент.Наименование

ИМЕЮЩИЕ

СУММА(Документ.СуммаДокумента) > 10000 // Фильтр групп

Здесь сначала отфильтровываются документы за текущий год (ГДЕ), а затем среди клиентов отбираются те, чья сумма заказов превышает 10 000 (ИМЕЮЩИЕ).

Есть ли разница в производительности между ГДЕ и ИМЕЮЩИЕ?

Да, и она существенная:

  • ГДЕ обычно работает быстрее, так как сокращает объём данных до группировки.
  • ИМЕЮЩИЕ вынужден обрабатывать все группы, что может быть ресурсоёмко для больших наборов данных.

Если возможно, переносите часть условий из ИМЕЮЩИЕ в ГДЕ. Например, вместо:

ИМЕЮЩИЕ СУММА(Сумма) > 1000 И КОЛИЧЕСТВО(*) > 3

Лучше написать:

ГДЕ Сумма > 100 // Отсекаем мелкие строки заранее

...

ИМЕЮЩИЕ СУММА(Сумма) > 1000 И КОЛИЧЕСТВО(*) > 3