Подсчёт строк и значений в запросе на языке 1С: от КОЛИЧЕСТВО до группировки и соединений

· 10 мин

Содержание

Посчитать количество строк в запросе на языке 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 читается однозначнее.

Оптимизация запросов с подсчётом

КОЛИЧЕСТВО сама по себе дешёвая. Тормозит обычно не она, а то, по чему она считает. Несколько проверенных приёмов:

  1. Фильтруйте до группировки. Условие ГДЕ по индексированному полю (периоду, организации) резко сокращает объём данных ещё до подсчёта.
  2. Отбор по периоду задавайте в параметрах виртуальной таблицы, а не в ГДЕ после выборки из регистра. Платформа применит его раньше.
  3. Не злоупотребляйте РАЗЛИЧНЫЕ. Подсчёт уникальных значений дороже обычного — он требует сортировки или хеширования. Нужен только счёт строк — пишите КОЛИЧЕСТВО(*).
  4. Соединяйте через временные таблицы. Соединение с подзапросом индекс не использует; промежуточный результат в ПОМЕСТИТЬ соединяется по индексу.
  5. Индексируйте временные таблицы по полям соединения — ИНДЕКСИРОВАТЬ ПО в запросе, который наполняет таблицу. На больших объёмах это кратно ускоряет следующий шаг.
ВЫБРАТЬ
    Продажи.Контрагент КАК Контрагент,
    КОЛИЧЕСТВО(*) КАК ЧислоПродаж
ПОМЕСТИТЬ ПродажиПоКлиентам
ИЗ
    Документ.РеализацияТоваровУслуг КАК Продажи
ГДЕ
    Продажи.Проведен
    И Продажи.Дата МЕЖДУ &НачалоПериода И &КонецПериода
СГРУППИРОВАТЬ ПО
    Продажи.Контрагент
ИНДЕКСИРОВАТЬ ПО
    Контрагент

Здесь отбор проведённых документов за период стоит до группировки, а результат проиндексирован по контрагенту — такую временную таблицу удобно соединять дальше.

Частые ошибки при подсчёте количества

Разбор строится вокруг расхождения «ожидал одно — получил другое». Проверьте себя по списку:

  • 🧮 Счётчик задвоился. Почти всегда причина — соединение с табличной частью или с таблицей, где у строки несколько пар. Лечится переходом на КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Ссылка).
  • 🕳 У строк без пары вместо нуля — единица. Это КОЛИЧЕСТВО(*) на левом соединении. Считайте поле присоединённой таблицы, чтобы NULL не считались.
  • 🚫 Ошибка «поле не входит в группировку». В выборке есть обычное поле, которого нет в СГРУППИРОВАТЬ ПО. Либо добавьте его в группировку, либо заверните в агрегат.
  • 🔀 ГДЕ по счётчику не работает . Условие на результат КОЛИЧЕСТВО ставится в ИМЕЮЩИЕ, а не в ГДЕ.
  • 📦 Посчитали строки, а нужно было товар. КОЛИЧЕСТВО считает записи, количество товара в них — это СУММА поля «Количество».
Проверьте себя
Нужно число документов по контрагенту, а запрос соединён с их табличной частью. Что вернёт КОЛИЧЕСТВО(*)?
Как отобрать контрагентов, у которых больше 5 документов?
Чем КОЛИЧЕСТВО(Поле) отличается от КОЛИЧЕСТВО(*)?

Частые вопросы

Как в запросе посчитать количество уникальных значений?
Используйте форму КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Поле) — она вернёт число разных непустых значений. Например, КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Номенклатура) покажет, сколько разных товаров встретилось в выборке, независимо от числа строк.
Почему КОЛИЧЕСТВО возвращает ноль, хотя записи есть?
Если считаете КОЛИЧЕСТВО(Поле), а это поле у всех строк равно NULL (частый случай после левого соединения), функция их не учитывает и вернёт 0. Для подсчёта самих строк берите КОЛИЧЕСТВО(*).
Можно ли посчитать количество без группировки?
Да, если в выборке только агрегатные функции и нет обычных полей. Тогда весь запрос сворачивается в одну строку. Группировка нужна только когда рядом с КОЛИЧЕСТВО стоят неагрегатные поля — их все придётся перечислить в СГРУППИРОВАТЬ ПО.
Как посчитать строки в табличной части документа?
Обратитесь к табличной части как к таблице: Документ.ЗаказПокупателя.Товары, сгруппируйте по Ссылка и возьмите КОЛИЧЕСТВО(*). Это даст число строк по каждому документу.
В чём разница между КОЛИЧЕСТВО и СУММА?
КОЛИЧЕСТВО считает, сколько строк (или сколько заполненных/уникальных значений). СУММА складывает числовые значения поля. Чтобы узнать, сколько единиц товара отгружено, нужна СУММА(Количество), а не КОЛИЧЕСТВО.