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