Подсчёт строк и значений в запросе на языке 1С: от КОЛИЧЕСТВО до группировки и соединений
Содержание
Посчитать количество строк в запросе на языке 1С помогает агрегатная функция КОЛИЧЕСТВО. Чтобы получить число записей целиком, пишут КОЛИЧЕСТВО(*); чтобы сосчитать только заполненные значения конкретного поля — КОЛИЧЕСТВО(Поле); чтобы узнать число уникальных значений — КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Поле).
На практике вопрос «сколько» почти никогда не стоит сам по себе. Нужно количество документов по каждому контрагенту, число позиций в заказе, количество уникальных номенклатур за период. Здесь к КОЛИЧЕСТВО добавляется группировка по измерениям, а при соединении нескольких таблиц счётчик легко начинает врать. Ниже — как считать правильно в каждом из этих случаев и чем они отличаются.
Весь разбор — на языке запросов платформы 8.3 (конструктор запросов, консоль запросов, объект Запрос во встроенном языке). Синтаксис одинаков для «Бухгалтерии», «ЗУП», «Управления торговлей» и любой другой конфигурации.
Три формы функции КОЛИЧЕСТВО и чем они различаются
КОЛИЧЕСТВО — агрегатная функция: она сворачивает набор строк в одно число. Форм записи три, и они дают разный результат на одних и тех же данных.
- 🔢
КОЛИЧЕСТВО(*)— число строк в выборке (или в группе). Считает все строки, даже если в полях естьNULL. - 📝
КОЛИЧЕСТВО(Поле)— число строк, где это поле не равноNULL. Пустые значения пропускаются. - 🎯
КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Поле)— число уникальных непустых значений поля.
Разница между первыми двумя неочевидна, пока в данных нет пустых значений. Как только появляется ЛЕВОЕ СОЕДИНЕНИЕ или реквизит, который заполняют не всегда, КОЛИЧЕСТВО(*) и КОЛИЧЕСТВО(Поле) расходятся.
Простейший запрос — сколько всего элементов в справочнике номенклатуры:
ВЫБРАТЬ
КОЛИЧЕСТВО(*) КАК ВсегоПозиций
ИЗ
Справочник.Номенклатура
Когда в выборке только агрегатные функции и ни одного обычного поля, группировка не нужна — платформа сама сворачивает всё в одну строку. Если выборка пустая, запрос всё равно вернёт одну строку со значением 0 (а не пустой результат).
Сравните три формы на поле, которое заполнено не у всех элементов, например «Производитель»:
| Выражение | Что считает | Результат на 100 позициях, где у 30 пустой производитель и 12 разных производителей |
|---|---|---|
КОЛИЧЕСТВО(*) | все строки | 100 |
КОЛИЧЕСТВО(Производитель) | строки с заполненным полем | 70 |
КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Производитель) | уникальные значения | 12 |
Если нужно просто «сколько записей» — всегда берите КОЛИЧЕСТВО(*). Это и быстрее, и не зависит от заполненности отдельных реквизитов.
Подсчёт по группам: количество в разрезе измерений
Отдельное число редко закрывает задачу. Чаще нужно количество в разрезе: по контрагентам, складам, менеджерам, месяцам. За это отвечает СГРУППИРОВАТЬ ПО.
Правило простое: все поля, которые не завёрнуты в агрегатную функцию, обязаны попасть в СГРУППИРОВАТЬ ПО. Иначе платформа выдаст ошибку вида «Поле не входит в группировку и не является агрегатной функцией».
Посчитаем число документов реализации по каждому контрагенту:
ВЫБРАТЬ
Реализация.Контрагент КАК Контрагент,
КОЛИЧЕСТВО(*) КАК ЧислоДокументов
ИЗ
Документ.РеализацияТоваровУслуг КАК Реализация
СГРУППИРОВАТЬ ПО
Реализация.Контрагент
УПОРЯДОЧИТЬ ПО
ЧислоДокументов УБЫВ
В результате — по строке на контрагента и счётчик его документов. УПОРЯДОЧИТЬ ПО по алиасу агрегата ставит сверху самых активных.
Если группировок несколько, перечислите все неагрегатные поля через запятую — например контрагент и договор, чтобы получить количество документов по каждой паре.
Отбор по значению счётчика: ИМЕЮЩИЕ вместо ГДЕ
Частая развилка — нужно оставить только тех, у кого документов больше, скажем, пяти. ГДЕ здесь не сработает: условие ГДЕ применяется до группировки, к отдельным строкам, а счётчика на этом этапе ещё нет.
Фильтр по результату агрегатной функции задаётся через ИМЕЮЩИЕ:
ВЫБРАТЬ
Реализация.Контрагент КАК Контрагент,
КОЛИЧЕСТВО(*) КАК ЧислоДокументов
ИЗ
Документ.РеализацияТоваровУслуг КАК Реализация
СГРУППИРОВАТЬ ПО
Реализация.Контрагент
ИМЕЮЩИЕ
КОЛИЧЕСТВО(*) > 5
ГДЕ отбирает строки до подсчёта, ИМЕЮЩИЕ — группы после подсчёта. Это одна из самых частых причин, по которой запрос «молча» возвращает не то: условие поставили не туда.
Два условия можно сочетать: ГДЕ сузит исходные данные (например, только проведённые документы за период), а ИМЕЮЩИЕ отсечёт мелкие группы уже по счётчику.
Количество строк в табличной части документа
Отдельный частый вопрос — сколько позиций в заказе или строк в табличной части. Табличная часть в запросе адресуется через точку: Документ.ЗаказПокупателя.Товары.
ВЫБРАТЬ
Товары.Ссылка КАК Заказ,
КОЛИЧЕСТВО(*) КАК ЧислоСтрок,
КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Товары.Номенклатура) КАК УникальныхПозиций
ИЗ
Документ.ЗаказПокупателя.Товары КАК Товары
СГРУППИРОВАТЬ ПО
Товары.Ссылка
Здесь видно, зачем нужна форма с РАЗЛИЧНЫЕ: ЧислоСтрок покажет, сколько всего строк в табличной части, а УникальныхПозиций — сколько разных номенклатур (одна и та же позиция могла попасть в заказ несколькими строками).
Не путайте количество строк (сколько записей в табличной части) и количество товара (поле «Количество» в этих строках). Для суммы товара нужна функция СУММА(Товары.Количество), а не КОЛИЧЕСТВО.
Подсчёт через временные таблицы
Когда счёт идёт в несколько этапов, на помощь приходят временные таблицы — результат промежуточного запроса, помещённый в менеджер временных таблиц через ПОМЕСТИТЬ.
Типичный сценарий: сначала собрать по каждому контрагенту сумму продаж, а потом посчитать, сколько контрагентов перешагнули некий порог. В один проход это сделать неудобно — агрегат от агрегата в одном запросе не записать. Два шага через временную таблицу решают задачу чисто:
ВЫБРАТЬ
Реализация.Контрагент КАК Контрагент,
СУММА(Реализация.Сумма) КАК Выручка
ПОМЕСТИТЬ ВыручкаПоКонтрагентам
ИЗ
Документ.РеализацияТоваровУслуг КАК Реализация
СГРУППИРОВАТЬ ПО
Реализация.Контрагент
;
ВЫБРАТЬ
КОЛИЧЕСТВО(*) КАК КрупныхКлиентов
ИЗ
ВыручкаПоКонтрагентам КАК Т
ГДЕ
Т.Выручка > 1000000
Первый запрос считает выручку по каждому и складывает её во временную таблицу. Второй считает строки этой таблицы с нужным условием. Точка с запятой разделяет пакет запросов.
Второй повод для временной таблицы — когда одно и то же промежуточное множество используется несколько раз или участвует в соединениях. Соединять две временные таблицы почти всегда быстрее и предсказуемее, чем городить вложенные подзапросы.
Как соединения искажают счётчик
Самая коварная зона. Стоит добавить к запросу СОЕДИНЕНИЕ, и КОЛИЧЕСТВО начинает показывать не то, что ожидаешь. Причина — размножение строк.
При соединении одной строки основной таблицы с несколькими строками присоединяемой строка основной таблицы «размножается». Если потом посчитать КОЛИЧЕСТВО(*), получится не число исходных документов, а число их пар с подчинёнными строками.
Разберём на примере. Нужно по каждому контрагенту узнать количество документов реализации. Если соединить реализацию с её табличной частью, счётчик задвоится — у документа с тремя строками товара появятся три записи:
- ❌
КОЛИЧЕСТВО(*)после соединения с табличной частью → посчитает строки товаров, а не документы. - ✅
КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Реализация.Ссылка)→ вернёт настоящее число документов, потому что уберёт повторы ссылки.
Второй эффект соединений — NULL. При ЛЕВОМ СОЕДИНЕНИИ строки левой таблицы, которым не нашлось пары справа, сохраняются, а поля правой таблицы в них равны NULL. Отсюда разница:
КОЛИЧЕСТВО(*)посчитает в том числе строки без пары.КОЛИЧЕСТВО(ПраваяТаблица.Поле)эти строки пропустит, потому что поле в нихNULL.
Пример: список всех контрагентов с числом их заказов, включая тех, у кого заказов нет вовсе.
ВЫБРАТЬ
Контрагенты.Ссылка КАК Контрагент,
КОЛИЧЕСТВО(Заказы.Ссылка) КАК ЧислоЗаказов
ИЗ
Справочник.Контрагенты КАК Контрагенты
ЛЕВОЕ СОЕДИНЕНИЕ Документ.ЗаказПокупателя КАК Заказы
ПО Заказы.Контрагент = Контрагенты.Ссылка
СГРУППИРОВАТЬ ПО
Контрагенты.Ссылка
Здесь намеренно взято КОЛИЧЕСТВО(Заказы.Ссылка), а не КОЛИЧЕСТВО(*). У контрагента без заказов Заказы.Ссылка равна NULL, функция её не считает — и в результате честный 0. С КОЛИЧЕСТВО(*) у такого контрагента получилась бы 1 (одна строка-«пустышка» от левого соединения), что было бы ошибкой.
Правило на соединениях: считаете документы — берите КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Ссылка); хотите учесть отсутствие пары как ноль — берите КОЛИЧЕСТВО(Поле присоединённой таблицы).
Количество по регистрам: виртуальные таблицы вместо ручного счёта
Если данные лежат в регистре накопления или сведений, обычно не нужно вручную группировать записи регистра. У регистров есть виртуальные таблицы — Остатки, Обороты, ОстаткиИОбороты, СрезПоследних, — которые уже свёрнуты платформой.
Например, чтобы узнать число номенклатурных позиций, по которым есть остаток на складе, достаточно посчитать строки виртуальной таблицы остатков:
ВЫБРАТЬ
КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Остатки.Номенклатура) КАК ПозицийСОстатком
ИЗ
РегистрНакопления.ТоварыНаСкладах.Остатки КАК Остатки
Виртуальная таблица уже отобрала актуальные остатки, поэтому запрос короткий и быстрый. Ручная группировка записей регистра движения по датам ради того же результата — лишняя работа и для разработчика, и для сервера.
Подсчёт по условию в одной строке
Иногда в одном запросе нужно посчитать не всё подряд, а несколько разных счётчиков с разными условиями — например по каждому контрагенту сразу число проведённых и число непроведённых документов. Плодить отдельные запросы или соединения не обязательно. Приём — обернуть условие в ВЫБОР и просуммировать единицы:
ВЫБРАТЬ
Реализация.Контрагент КАК Контрагент,
СУММА(ВЫБОР КОГДА Реализация.Проведен ТОГДА 1 ИНАЧЕ 0 КОНЕЦ) КАК Проведенных,
СУММА(ВЫБОР КОГДА НЕ Реализация.Проведен ТОГДА 1 ИНАЧЕ 0 КОНЕЦ) КАК Черновиков
ИЗ
Документ.РеализацияТоваровУслуг КАК Реализация
СГРУППИРОВАТЬ ПО
Реализация.Контрагент
Логика такая: ВЫБОР возвращает 1 для строк, подходящих под условие, и 0 для остальных, а СУММА складывает единицы — получается условный счётчик. Так в одну группировку помещается сколько угодно условных количеств, и база читается один раз. Этот приём выручает в отчётах, где рядом нужны несколько срезов по одному набору данных.
КОЛИЧЕСТВО(ВЫБОР КОГДА Условие ТОГДА 1 КОНЕЦ) тоже сработает: без ветки ИНАЧЕ невыполненное условие даёт NULL, а КОЛИЧЕСТВО его пропускает. Результат тот же, но вариант с СУММА и явным ИНАЧЕ 0 читается однозначнее.
Оптимизация запросов с подсчётом
КОЛИЧЕСТВО сама по себе дешёвая. Тормозит обычно не она, а то, по чему она считает. Несколько проверенных приёмов:
- Фильтруйте до группировки. Условие
ГДЕпо индексированному полю (периоду, организации) резко сокращает объём данных ещё до подсчёта. - Отбор по периоду задавайте в параметрах виртуальной таблицы, а не в
ГДЕпосле выборки из регистра. Платформа применит его раньше. - Не злоупотребляйте
РАЗЛИЧНЫЕ. Подсчёт уникальных значений дороже обычного — он требует сортировки или хеширования. Нужен только счёт строк — пишитеКОЛИЧЕСТВО(*). - Соединяйте через временные таблицы. Соединение с подзапросом индекс не использует; промежуточный результат в
ПОМЕСТИТЬсоединяется по индексу. - Индексируйте временные таблицы по полям соединения —
ИНДЕКСИРОВАТЬ ПОв запросе, который наполняет таблицу. На больших объёмах это кратно ускоряет следующий шаг.
ВЫБРАТЬ
Продажи.Контрагент КАК Контрагент,
КОЛИЧЕСТВО(*) КАК ЧислоПродаж
ПОМЕСТИТЬ ПродажиПоКлиентам
ИЗ
Документ.РеализацияТоваровУслуг КАК Продажи
ГДЕ
Продажи.Проведен
И Продажи.Дата МЕЖДУ &НачалоПериода И &КонецПериода
СГРУППИРОВАТЬ ПО
Продажи.Контрагент
ИНДЕКСИРОВАТЬ ПО
Контрагент
Здесь отбор проведённых документов за период стоит до группировки, а результат проиндексирован по контрагенту — такую временную таблицу удобно соединять дальше.
Частые ошибки при подсчёте количества
Разбор строится вокруг расхождения «ожидал одно — получил другое». Проверьте себя по списку:
- 🧮 Счётчик задвоился. Почти всегда причина — соединение с табличной частью или с таблицей, где у строки несколько пар. Лечится переходом на
КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Ссылка). - 🕳 У строк без пары вместо нуля — единица. Это
КОЛИЧЕСТВО(*)на левом соединении. Считайте поле присоединённой таблицы, чтобыNULLне считались. - 🚫 Ошибка «поле не входит в группировку». В выборке есть обычное поле, которого нет в
СГРУППИРОВАТЬ ПО. Либо добавьте его в группировку, либо заверните в агрегат. - 🔀
ГДЕпо счётчику не работает . Условие на результатКОЛИЧЕСТВОставится вИМЕЮЩИЕ, а не вГДЕ. - 📦 Посчитали строки, а нужно было товар.
КОЛИЧЕСТВОсчитает записи, количество товара в них — этоСУММАполя «Количество».
Частые вопросы
Как в запросе посчитать количество уникальных значений?
КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Поле) — она вернёт число разных непустых значений. Например, КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Номенклатура) покажет, сколько разных товаров встретилось в выборке, независимо от числа строк.Почему КОЛИЧЕСТВО возвращает ноль, хотя записи есть?
КОЛИЧЕСТВО(Поле), а это поле у всех строк равно NULL (частый случай после левого соединения), функция их не учитывает и вернёт 0. Для подсчёта самих строк берите КОЛИЧЕСТВО(*).Можно ли посчитать количество без группировки?
КОЛИЧЕСТВО стоят неагрегатные поля — их все придётся перечислить в СГРУППИРОВАТЬ ПО.Как посчитать строки в табличной части документа?
Документ.ЗаказПокупателя.Товары, сгруппируйте по Ссылка и возьмите КОЛИЧЕСТВО(*). Это даст число строк по каждому документу.В чём разница между КОЛИЧЕСТВО и СУММА?
КОЛИЧЕСТВО считает, сколько строк (или сколько заполненных/уникальных значений). СУММА складывает числовые значения поля. Чтобы узнать, сколько единиц товара отгружено, нужна СУММА(Количество), а не КОЛИЧЕСТВО.