При разработке отчетов или обработок на платформе 1С:Предприятие 8 программисты часто сталкиваются с необходимостью получить статистику по документам: сколько заказов создал менеджер и какое количество товарных позиций в них содержится. Типичная ошибка новичков заключается в попытке обратиться к вложенной таблице (табличной части) напрямую через основной список полей запроса с использованием агрегатных функций, что приводит к синтаксическим ошибкам.
В этой статье мы подробно разберем, почему стандартный подход КОЛИЧЕСТВО(ЗаказКлиента.Товары.НомерСтроки) не работает, применим отладчик запроса 1С 8.3 и рассмотрим несколько эффективных способов решения этой задачи.
Рассмотрим ситуацию: разработчик пытается написать запрос к шапке документа и внутри функции КОЛИЧЕСТВО() указать поле из табличной части. В языке запросов 1С табличная часть для объекта (например, Документ.ЗаказКлиента) является вложенной таблицей. Вы не можете просто «просуммировать» или «посчитать» строки вложенной таблицы в контексте одной строки основной таблицы без предварительного развертывания этих данных.
Синтаксис вида КОЛИЧЕСТВО(ЗаказКлиента.Товары.(НомерСтроки)) является ошибочным для секции ВЫБРАТЬ, если вы используете обычную группировку. Чтобы получить результат, нам нужно изменить структуру самого запроса.
Это самый простой и производительный способ, если нам нужно получить данные в разрезе менеджеров по всем строкам их документов. Вместо того чтобы обращаться к таблице Документ.ЗаказКлиента, мы обратимся напрямую к таблице Документ.ЗаказКлиента.Товары.
Проанализируем пример кода:
ВЫБРАТЬ
ЗаказКлиентаТовары.Ссылка.Менеджер КАК Менеджер,
КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ЗаказКлиентаТовары.Ссылка) КАК ДокументовСоздано,
КОЛИЧЕСТВО(ЗаказКлиентаТовары.НомерСтроки) КАК КоличествоСтрок,
СУММА(ЗаказКлиентаТовары.Количество) КАК ОбщееКоличествоТовара
ИЗ
Документ.ЗаказКлиента.Товары КАК ЗаказКлиентаТовары
ГДЕ
ЗаказКлиентаТовары.Ссылка.Проведен = ИСТИНА
СГРУППИРОВАТЬ ПО
ЗаказКлиентаТовары.Ссылка.Менеджер
Давайте разберем, что происходит в этом запросе:
КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Ссылка). Параметр РАЗЛИЧНЫЕ критически важен: он гарантирует, что если в заказе 10 строк, документ будет посчитан только один раз.КОЛИЧЕСТВО(НомерСтроки) вернет нам общее количество строк (позиций) во всех заказах данного менеджера.СУММА(Количество) позволяет нам узнать не просто количество строк, а физический объем товара (например, штук или килограммов), указанный в колонке «Количество».Иногда бизнес-задача требует вывести всех менеджеров или все документы, даже если в них нет ни одной строки товаров (например, ошибочно созданные или пустые заказы). В этом случае первый способ не подойдет, так как пустые документы просто не попадут в выборку из таблицы Товары.
Рассмотрим вариант с использованием ЛЕВОЕ СОЕДИНЕНИЕ:
ВЫБРАТЬ
ЗаказКлиента.Менеджер КАК Менеджер,
КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ЗаказКлиента.Ссылка) КАК ДокументовВсего,
КОЛИЧЕСТВО(ЗаказКлиентаТовары.НомерСтроки) КАК ПозицийВЗаказах
ИЗ
Документ.ЗаказКлиента КАК ЗаказКлиента
ЛЕВОЕ СОЕДИНЕНИЕ Документ.ЗаказКлиента.Товары КАК ЗаказКлиентаТовары
ПО ЗаказКлиента.Ссылка = ЗаказКлиентаТовары.Ссылка
ГДЕ
ЗаказКлиента.Дата МЕЖДУ &НачалоПериода И &КонецПериода
СГРУППИРОВАТЬ ПО
ЗаказКлиента.Менеджер
Важный нюанс: при левом соединении, если у документа нет строк в ТЧ, поле ЗаказКлиентаТовары.НомерСтроки примет значение NULL. Агрегатная функция КОЛИЧЕСТВО() игнорирует значения NULL, поэтому для пустого документа счетчик строк покажет 0, что является абсолютно корректным результатом.
Если база данных содержит сотни тысяч заказов, запросы к табличным частям «в лоб» могут работать медленно. Выясним причину: системе приходится сканировать огромные таблицы строк. Для оптимизации рекомендуется сначала отобрать ссылки на нужные документы во временную таблицу.
Проанализируем шаги оптимизации:
ВТ_Заказы, куда отбираем ссылки на документы по нужному периоду и статусу (например, только проведенные).
// Шаг 1: Отбор нужных документов
ВЫБРАТЬ
ЗаказКлиента.Ссылка КАК Ссылка,
ЗаказКлиента.Менеджер КАК Менеджер
ПОМЕСТИТЬ ВТ_Заказы
ИЗ
Документ.ЗаказКлиента КАК ЗаказКлиента
ГДЕ
ЗаказКлиента.Проведен = ИСТИНА
И ЗаказКлиента.Дата МЕЖДУ &НачалоПериода И &КонецПериода
;
// Шаг 2: Подсчет данных
ВЫБРАТЬ
ВТ_Заказы.Менеджер КАК Менеджер,
КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ВТ_Заказы.Ссылка) КАК Документов,
КОЛИЧЕСТВО(ЗаказКлиентаТовары.НомерСтроки) КАК СтрокТЧ
ИЗ
ВТ_Заказы КАК ВТ_Заказы
ЛЕВОЕ СОЕДИНЕНИЕ Документ.ЗаказКлиента.Товары КАК ЗаказКлиентаТовары
ПО ВТ_Заказы.Ссылка = ЗаказКлиентаТовары.Ссылка
СГРУППИРОВАТЬ ПО
ВТ_Заказы.Менеджер
В процессе разработки, когда выполняется анализ продаж, валовой прибыли и среднего чека по номенклатуре, важно уточнять у заказчика, что именно нужно считать. Рассмотрим два разных сценария:
КОЛИЧЕСТВО(НомерСтроки).Для подсчета уникальных позиций используйте конструкцию:
КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ЗаказКлиентаТовары.Номенклатура) КАК УникальныхТоваров
Проанализируем еще несколько моментов, которые помогут сделать ваши запросы профессиональными:
1. Фильтрация проведенных документов. Всегда проверяйте состояние документа. Данные в помеченных на удаление или просто записанных документах обычно не должны попадать в управленческую отчетность. Используйте условие Ссылка.Проведен.
2. Использование итогов. Если вы строите дерево данных, полезно использовать секцию ИТОГИ. Например:
ВЫБРАТЬ ... СГРУППИРОВАТЬ ПО ...
ИТОГИ
КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Ссылка),
СУММА(КоличествоСтрок)
ПО
ОБЩИЕ,
Менеджер
Это позволит получить итоговые суммы по всей компании в дополнение к данным по каждому менеджеру.
3. Обращение к регистрам. Помните, что в типовых конфигурациях (таких как УТ 11, ERP 2.5 или КА) данные о продажах и заказах пишутся в регистры накопления. Запрос к виртуальной таблице Обороты соответствующего регистра (например, ВыручкаИСебестоимостьПродаж) будет работать быстрее, чем прямой запрос к документам, так как данные там уже частично агрегированы.
Таким образом, мы рассмотрели основные подходы к получению данных из табличных частей. Наиболее универсальным и правильным для решения задачи автора является Способ №1 или Способ №2 с обязательным использованием функции КОЛИЧЕСТВО(РАЗЛИЧНЫЕ ...) для документов.