Работа с запросами в 1С:Предприятие — одна из ключевых задач разработчика, и два оператора, ГДЕ и ИМЕЮЩИЕ, часто становятся источником путаницы. На первый взгляд оба фильтруют данные, но их логика и область применения принципиально разные. Ошибка в выборе между ними может привести к некорректным отчётам, медленной работе системы или даже потере данных.
В этой статье разберём фундаментальные отличия между ГДЕ и ИМЕЮЩИЕ: как они работают на уровне СУБД, в каких сценариях каждый из них незаменим, и почему неправильный выбор оператора может «сломать» ваш запрос. Также рассмотрим практические примеры с кодом, типичные ошибки новичков и оптимизацию запросов для крупных баз данных.
Если вы когда-либо сталкивались с ситуацией, когда запрос возвращает неожиданные результаты или работает слишком долго — эта статья поможет разобраться в корне проблемы.
1. Базовый синтаксис: как работают «ГДЕ» и «ИМЕЮЩИЕ»
Оператор ГДЕ применяется к отдельным строкам таблицы или виртуальной таблицы запроса. Он фильтрует записи до группировки данных, то есть отсеивает те строки, которые не соответствуют условию. Например:
ВЫБРАТЬ
Товар.Наименование,
Товар.Цена
ИЗ
Справочник.Товары КАК Товар
ГДЕ
Товар.Цена > 1000
Здесь условие Цена > 1000 проверяется для каждой строки справочника Товары индивидуально. Если цена товара меньше или равна 1000, строка не попадёт в результат.
Оператор ИМЕЮЩИЕ, напротив, работает после группировки. Он фильтрует уже сгруппированные данные по агрегированным значениям (например, суммам, количествам, средним). Синтаксически он всегда идёт после блока СГРУППИРОВАТЬ ПО:
ВЫБРАТЬ
Клиент.Наименование,
СУММА(Документ.СуммаДокумента) КАК ОбщаяСумма
ИЗ
Документ.РеализацияТоваровУслуг КАК Документ
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Клиенты КАК Клиент
ПО Документ.Клиент = Клиент.Ссылка
СГРУППИРОВАТЬ ПО
Клиент.Наименование
ИМЕЮЩИЕ
СУММА(Документ.СуммаДокумента) > 50000
В этом примере сначала формируются группы по клиентам, рассчитывается сумма их заказов, и только затем отсеиваются клиенты с общей суммой менее 50 000. Использование «ГДЕ» вместо «ИМЕЮЩИЕ» здесь приведёт к ошибке, так как агрегированное поле «ОбщаяСумма» ещё не существует на этапе фильтрации строк.
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).
Как ускорить такие запросы:
- Перенесите часть фильтрации в
ГДЕ: Отсекайте ненужные строки до группировки. Например, если вам нужны клиенты с суммой заказов > 50 000, сначала отфильтруйте заказы за нужный период вГДЕ. - Используйте подзапросы: Иногда разбиение большого запроса на несколько маленьких работает быстрее.
- Применяйте индексы: Убедитесь, что поля, по которым идёт группировка или фильтрация, проиндексированы в СУБД.
- Ограничивайте период: Если возможно, добавьте условие по дате в
ГДЕ, чтобы сократить объём обрабатываемых данных.
Пример оптимизированного запроса:
ВЫБРАТЬ
Клиент.Наименование,
СУММА(Документ.СуммаДокумента) КАК Итог
ИЗ
Документ.РеализацияТоваровУслуг КАК Документ
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Клиенты КАК Клиент
ПО Документ.Клиент = Клиент.Ссылка
ГДЕ
Документ.Дата >= &НачалоПериода
И Документ.Дата <= &КонецПериода
И Документ.СуммаДокумента > 1000 // Отсекаем мелкие заказы заранее
СГРУППИРОВАТЬ ПО
Клиент.Наименование
ИМЕЮЩИЕ
СУММА(Документ.СуммаДокумента) > 50000
Есть ли условие по дате в ГДЕ?|Отфильтрованы ли заведомо ненужные строки?|Проиндексированы ли поля для группировки?|Можно ли разбить запрос на подзапросы?|Проверен ли план выполнения через ОбъяснитьЗапрос()?
-->
6. Сложные случаи: вложенные агрегаты и многоуровневая группировка
Иногда требуется фильтрация по агрегатам агрегатов. Например: «найти регионы, где средняя сумма заказа клиентов выше средней по всем регионам». Здесь придётся использовать подзапросы или временные таблицы.
Пример с подзапросом:
ВЫБРАТЬ
Регион.Наименование,
СРЕДНЕЕ(КлиентСумма.Итог) КАК СредняяПоРегиону
ИЗ
Справочник.Регионы КАК Регион
ЛЕВОЕ СОЕДИНЕНИЕ (
ВЫБРАТЬ
Клиент.Регион КАК Регион,
СУММА(Документ.СуммаДокумента) КАК Итог
ИЗ
Документ.РеализацияТоваровУслуг КАК Документ
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Клиенты КАК Клиент
ПО Документ.Клиент = Клиент.Ссылка
ГДЕ
Документ.Дата МЕЖДУ &НачалоГода И &КонецГода
СГРУППИРОВАТЬ ПО
Клиент.Регион
ИМЕЮЩИЕ
СУММА(Документ.СуммаДокумента) > 0
) КАК КлиентСумма
ПО Регион.Ссылка = КлиентСумма.Регион
ГДЕ
СРЕДНЕЕ(КлиентСумма.Итог) > (
ВЫБРАТЬ
СРЕДНЕЕ(СуммаПоРегиону.Итог)
ИЗ
(
ВЫБРАТЬ
СУММА(Документ.СуммаДокумента) КАК Итог
ИЗ
Документ.РеализацияТоваровУслуг КАК Документ
ГДЕ
Документ.Дата МЕЖДУ &НачалоГода И &КонецГода
) КАК СуммаПоРегиону
)
Такой запрос сложен для восприятия, но он демонстрирует, как можно комбинировать ГДЕ и ИМЕЮЩИЕ на разных уровнях вложенности. Для упрощения рекомендуется:
- 🧩 Разбивать запрос на части с использованием временных таблиц (
ВТ). - 📝 Документировать логику каждого блока комментариями.
- ⚡ Тестировать производительность на реальных данных.
⚠️ Внимание: Вложенные агрегаты могут приводить к неожиданным результатам, если не учитывать NULL-значения. Например,СРЕДНЕЕигнорируетNULL, аСУММА— нет. Всегда проверяйте промежуточные результаты.
7. Альтернативы: когда можно обойтись без «ИМЕЮЩИЕ»
В некоторых случаях ИМЕЮЩИЕ можно заменить другими конструкциями:
- 🔄 Подзапросы в
ИЗ: Если нужно отфильтровать группы по агрегату, иногда проще сначала получить агрегированные данные во временной таблице, а затем присоединить её. - 📌 Функция
ВЫРАЗИТЬ: В новых версиях 1С (8.3.15+) можно использоватьВЫРАЗИТЬдля создания вычисляемых полей с агрегатами. - 🗃️ Виртуальные таблицы: Например,
РегистрНакопления.ОстаткиИОборотыуже содержат агрегированные данные, что избавляет от необходимости группировки.
Пример с временной таблицей:
// Сначала получаем агрегированные данные
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
Клиент.Ссылка КАК Клиент,
СУММА(Документ.СуммаДокумента) КАК Итог
ИЗ
Документ.РеализацияТоваровУслуг КАК Документ
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Клиенты КАК Клиент
ПО Документ.Клиент = Клиент.Ссылка
ГДЕ
Документ.Дата МЕЖДУ &НачалоМесяца И &КонецМесяца
СГРУППИРОВАТЬ ПО
Клиент.Ссылка";
Результат = Запрос.Выполнить();
ВТ_Клиенты = Результат.Выгрузить();
// Затем фильтруем по агрегату
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
Клиенты.Итог КАК СуммаЗаказов
ИЗ
&ВТ_Клиенты КАК Клиенты
ГДЕ
Клиенты.Итог > 50000";
Запрос.УстановитьПараметр("ВТ_Клиенты", ВТ_Клиенты);
Результат = Запрос.Выполнить();
Такой подход может быть эффективнее, особенно если агрегированные данные нужны в нескольких местах кода.
Если запрос с ИМЕЮЩИЕ работает медленно, попробуйте заменить его на подзапрос с временной таблицей. Это часто ускоряет выполнение за счёт уменьшения объёма данных на каждом этапе.
FAQ: Ответы на частые вопросы
Можно ли использовать ИМЕЮЩИЕ без СГРУППИРОВАТЬ ПО?
Нет, это бессмысленно. Оператор ИМЕЮЩИЕ предназначен для фильтрации групп, а без группировки групп просто нет. В лучшем случае условие будет проигнорировано, в худшем — запрос упадёт с ошибкой.
Почему мой запрос с ИМЕЮЩИЕ возвращает пустой результат, хотя данные есть?
Вероятные причины:
- Условие в
ИМЕЮЩИЕслишком жёсткое (например,СУММА > 1 000 000, хотя максимальная сумма в данных — 500 000). - Ошибка в логике группировки: возможно, вы сгруппировали данные не по тому полю.
- Фильтрация в
ГДЕотсекла все строки, которые могли бы образовать группы.
Проверьте промежуточные результаты без условия ИМЕЮЩИЕ, чтобы понять, какие группы формируются.
Как узнать, что лучше использовать — ГДЕ или ИМЕЮЩИЕ?
Ответьте на два вопроса:
- Нужно ли вам фильтровать отдельные строки (до группировки)? → Используйте
ГДЕ. - Нужно ли фильтровать группы строк по агрегированным значениям (суммам, количествам и т.д.)? → Используйте
ИМЕЮЩИЕ.
Если сомневаетесь, попробуйте написать запрос обоими способами и сравните результаты.
Можно ли в одном запросе использовать и ГДЕ, и ИМЕЮЩИЕ?
Да, это стандартная практика. Например:
ВЫБРАТЬ
Клиент.Наименование,
СУММА(Документ.СуммаДокумента) КАК Итог
ИЗ
Документ.РеализацияТоваровУслуг КАК Документ
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Клиенты КАК Клиент
ПО Документ.Клиент = Клиент.Ссылка
ГДЕ
Документ.Дата > &НачалоГода // Фильтр строк
СГРУППИРОВАТЬ ПО
Клиент.Наименование
ИМЕЮЩИЕ
СУММА(Документ.СуммаДокумента) > 10000 // Фильтр групп
Здесь сначала отфильтровываются документы за текущий год (ГДЕ), а затем среди клиентов отбираются те, чья сумма заказов превышает 10 000 (ИМЕЮЩИЕ).
Есть ли разница в производительности между ГДЕ и ИМЕЮЩИЕ?
Да, и она существенная:
ГДЕобычно работает быстрее, так как сокращает объём данных до группировки.ИМЕЮЩИЕвынужден обрабатывать все группы, что может быть ресурсоёмко для больших наборов данных.
Если возможно, переносите часть условий из ИМЕЮЩИЕ в ГДЕ. Например, вместо:
ИМЕЮЩИЕ СУММА(Сумма) > 1000 И КОЛИЧЕСТВО(*) > 3
Лучше написать:
ГДЕ Сумма > 100 // Отсекаем мелкие строки заранее
...
ИМЕЮЩИЕ СУММА(Сумма) > 1000 И КОЛИЧЕСТВО(*) > 3