Подсчёт количества строк в таблицах и выборках 1С

· 9 мин

Содержание

Количество строк в табличной части, в результате запроса или в массиве в 1С 8.3 возвращает метод Количество() — например, Документ.Товары.Количество(). Для коллекций это самый быстрый способ: число хранится готовым и вычислять его не нужно. Если нужно сосчитать строки с условием (только с заполненной суммой, только по одному товару) — идёт цикл со счётчиком, а для данных прямо из базы без выгрузки в память — запрос с агрегатной функцией КОЛИЧЕСТВО(*).

Ниже — все рабочие способы: когда брать Количество(), когда цикл, когда запрос, как считать строки во временной таблице и как узнать число выделенных строк на форме. Весь код — для платформы 8.3, встроенный язык.

Свойство Количество() у табличной части и коллекций

Количество() есть почти у всех коллекций встроенного языка: табличной части документа, таблицы значений, массива, списка значений, соответствия, структуры, дерева значений. Метод возвращает число элементов (строк) и работает мгновенно — платформа держит счётчик внутри.

// Строк в табличной части документа
КолвоСтрок = Документ.Товары.Количество();

// Строк в таблице значений
КолвоСтрок = ТаблицаЗначений.Количество();

// Элементов в массиве
КолвоЭлементов = Массив.Количество();

Чтобы вывести результат пользователю, не склеивайте число со строкой напрямую — используйте СтрШаблон() или Формат(), иначе получите ошибку преобразования типов:

Сообщить(СтрШаблон("В документе строк: %1", Документ.Товары.Количество()));

Проверить, что коллекция вообще не пуста, можно и без подсчёта: Если ТаблицаЗначений.Количество() = 0 Тогда. Для результата запроса для этого есть отдельный метод Пустой() — он не выгружает данные.

Отдельно про дерево значений. Дерево.Строки.Количество() вернёт число строк только верхнего уровня. Чтобы сосчитать все узлы вместе с вложенными, нужен рекурсивный обход — об этом ниже, в разделе про цикл.

Подсчёт строк в цикле: условие и ручная нумерация

Когда нужно сосчитать не все строки, а только подходящие под условие, Количество() не поможет — здесь работает цикл со счётчиком. Классический пример: сколько строк с положительной суммой.

Счётчик = 0;
Для Каждого Строка Из Документ.Товары Цикл
    Если Строка.Сумма > 0 Тогда
        Счётчик = Счётчик + 1;
    КонецЕсли;
КонецЦикла;

Второй частый сценарий — ручная нумерация строк, когда в колонку нужно проставить порядковые номера 1, 2, 3… (например, для печатной формы):

Номер = 0;
Для Каждого Строка Из ТаблицаЗначений Цикл
    Номер = Номер + 1;
    Строка.НомерПП = Номер;
КонецЦикла;

Для табличной части документа отдельный реквизит под номер заводить не нужно — там уже есть системное поле НомерСтроки, платформа заполняет его сама при проведении.

Если нужен индекс конкретной строки (её позиция в коллекции), не ищите его счётчиком — есть готовый метод Индекс(), он возвращает позицию начиная с нуля:

ПозицияСтроки = ТаблицаЗначений.Индекс(НайденнаяСтрока); // 0, 1, 2...

Для таблицы значений есть способ сосчитать строки по условию без ручного цикла — метод НайтиСтроки(). Он принимает структуру с отбором и возвращает массив подходящих строк, у которого остаётся взять Количество():

Отбор = Новый Структура("Статус", "Оплачен");
КолвоОплаченных = ТаблицаЗначений.НайтиСтроки(Отбор).Количество();

Отбор по структуре работает только на точное равенство. Если условие сложнее (сумма больше нуля, дата в диапазоне) — остаётся цикл со счётчиком.

Не удаляйте строки внутри цикла Для Каждого — он собьётся. Если удаление нужно, идите в обратном порядке через Пока, уменьшая счётчик от Количество() - 1 до нуля.

Рекурсивный подсчёт всех узлов дерева значений тоже строится на цикле — функция перебирает Строки текущего уровня и вызывает сама себя для каждой вложенной коллекции:

Функция ВсегоУзловДерева(Строки)
    Итог = Строки.Количество();
    Для Каждого Строка Из Строки Цикл
        Итог = Итог + ВсегоУзловДерева(Строка.Строки);
    КонецЦикла;
    Возврат Итог;
КонецФункции

Подсчёт строк запросом: КОЛИЧЕСТВО(*)

Если данные лежат в базе, а не в памяти, не выгружайте их в таблицу значений ради подсчёта — посчитайте прямо запросом. Агрегатная функция КОЛИЧЕСТВО(*) считает число записей:

Запрос = Новый Запрос;
Запрос.Текст =
    "ВЫБРАТЬ
    |    КОЛИЧЕСТВО(*) КАК Строк
    |ИЗ
    |    Документ.РеализацияТоваровУслуг.Товары";
Выборка = Запрос.Выполнить().Выбрать();
Выборка.Следующий();
КолвоСтрок = Выборка.Строк;

Запрос с КОЛИЧЕСТВО(*) всегда возвращает одну строку результата, поэтому Пустой() тут проверять не нужно — достаточно одного Следующий().

Несколько полезных вариантов агрегата:

  • КОЛИЧЕСТВО(*) — все записи, включая повторы.
  • КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Номенклатура) — число уникальных значений в колонке.
  • Группировка — сколько строк по каждому товару сразу:
ВЫБРАТЬ
    Номенклатура КАК Номенклатура,
    КОЛИЧЕСТВО(*) КАК Строк
ИЗ
    Документ.РеализацияТоваровУслуг.Товары
СГРУППИРОВАТЬ ПО
    Номенклатура

Сосчитать записи по условию тоже делает сам запрос — условие ставится в ГДЕ, цикл не нужен:

ВЫБРАТЬ
    КОЛИЧЕСТВО(*) КАК Строк
ИЗ
    Документ.РеализацияТоваровУслуг.Товары
ГДЕ
    Сумма > 0

А если нужно оставить только группы, где строк больше заданного числа (например, товары, встречающиеся в документе больше одного раза), условие на агрегат ставится через ИМЕЮЩИЕ:

ВЫБРАТЬ
    Номенклатура КАК Номенклатура,
    КОЛИЧЕСТВО(*) КАК Строк
ИЗ
    Документ.РеализацияТоваровУслуг.Товары
СГРУППИРОВАТЬ ПО
    Номенклатура
ИМЕЮЩИЕ
    КОЛИЧЕСТВО(*) > 1

Если же результат запроса уже получен и строки нужны как число, не перебирайте выборку вручную — у результата есть Количество():

Результат = Запрос.Выполнить();
КолвоСтрок = Результат.Выгрузить().Количество();

Запрос считает на стороне сервера СУБД и не тянет все записи в память — для больших таблиц это единственный правильный способ. Цикл по десяткам тысяч объектов, выгруженных в таблицу значений, съест память и время.

Сколько строк во временной таблице

Временные таблицы живут в менеджере временных таблиц в рамках пакетного запроса. Посчитать в них строки можно тем же КОЛИЧЕСТВО(*) — отдельным запросом пакета, который обращается к ранее помещённой таблице по её имени.

Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
Запрос.Текст =
    "ВЫБРАТЬ
    |    Товар,
    |    Сумма
    |ПОМЕСТИТЬ ВремТовары
    |ИЗ
    |    &ТаблицаТоваров КАК Т
    |;
    |ВЫБРАТЬ
    |    КОЛИЧЕСТВО(*) КАК Строк
    |ИЗ
    |    ВремТовары";
Запрос.УстановитьПараметр("ТаблицаТоваров", ТаблицаТоваров);
Результаты = Запрос.ВыполнитьПакет();

ВыполнитьПакет() возвращает массив результатов по каждому запросу пакета. Второй результат — наш подсчёт:

ВыборкаПоСчёту = Результаты[1].Выбрать();
ВыборкаПоСчёту.Следующий();
КолвоСтрок = ВыборкаПоСчёту.Строк;

Если временная таблица уже помещена отдельным запросом (через общий МенеджерВременныхТаблиц), новый запрос с тем же менеджером видит её по имени — и подсчёт делается без повторной выгрузки данных.

Считать строки во временной таблице, выгружая её обратно в таблицу значений ради Количество(), смысла нет: это лишний рейс данных. Держите подсчёт внутри пакета запросов.

Количество строк в динамическом списке и на форме

На управляемой форме встречаются два разных вопроса: сколько строк пользователь выделил и сколько строк в списке всего.

Число выделенных строк берётся из коллекции ВыделенныеСтроки элемента формы:

&НаКлиенте
Процедура ПосчитатьВыделенные(Команда)
    Колво = Элементы.Список.ВыделенныеСтроки.Количество();
    Сообщить(СтрШаблон("Выделено строк: %1", Колво));
КонецПроцедуры

Для таблицы значений или табличной части, выведенной на форму, ВыделенныеСтроки содержит сами строки; для динамического списка — идентификаторы записей. Количество в обоих случаях считается одинаково.

А вот общее число записей в динамическом списке просто так не достанешь: список подгружает данные порциями по мере прокрутки и целиком в памяти не держит. Считать его перебором нельзя. Правильный путь — отдельный запрос КОЛИЧЕСТВО(*) по той же таблице с тем же отбором, что стоит на списке:

&НаСервере
Функция ВсегоВСписке()
    Запрос = Новый Запрос("ВЫБРАТЬ КОЛИЧЕСТВО(*) КАК Строк ИЗ Справочник.Номенклатура");
    Выборка = Запрос.Выполнить().Выбрать();
    Выборка.Следующий();
    Возврат Выборка.Строк;
КонецФункции

Если на списке стоит пользовательский отбор, его нужно повторить и в запросе подсчёта — иначе число не сойдётся с тем, что видит пользователь на экране.

Для обычной таблицы на форме (не динамического списка) всё проще: обращайтесь к реквизиту формы как к таблице значений и берите Количество() напрямую.

Отдельный случай — отмеченные флажком строки. Выделение мышью и пометка флажком в колонке-признаке это разные вещи: выделенные строки лежат в ВыделенныеСтроки, а отмеченные флажком считаются перебором по значению колонки-флажка:

Отмечено = 0;
Для Каждого Строка Из Объект.Товары Цикл
    Если Строка.Пометка Тогда
        Отмечено = Отмечено + 1;
    КонецЕсли;
КонецЦикла;

Строки в табличном документе

Запрос «посчитать строки в 1С» нередко относится не к таблице значений, а к табличному документу — печатной форме или выгрузке в ячейки. Здесь число строк и колонок возвращают свойства ВысотаТаблицы и ШиринаТаблицы:

КолвоСтрок = ТабДокумент.ВысотаТаблицы;
КолвоКолонок = ТабДокумент.ШиринаТаблицы;

ВысотаТаблицы возвращает номер последней заполненной строки — то есть фактическое число строк в документе. Это удобно, когда печатная форма собирается в цикле и нужно узнать, докуда она доросла, или добавить итоговую строку ровно под последней.

Обращаться к области по номеру строки можно через ТабДокумент.Область(НомерСтроки, НомерКолонки). Перебор от первой до ВысотаТаблицы даёт проход по всем заполненным строкам документа.

Какой способ подсчёта выбрать

Коротко: данные уже в памяти — Количество(); нужен отбор по условию — цикл; данные в базе — запрос. Выбор зависит от того, где лежат строки и что именно нужно сосчитать.

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

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

Несколько мест, где подсчёт даёт неверный результат или падает с ошибкой.

  • 🔢 Склейка числа со строкой. "Строк: " + Таблица.Количество() вызывает ошибку преобразования. Оборачивайте число в Формат() или собирайте текст через СтрШаблон().
  • 🧮 Количество() вызвали как свойство без скобок. В 8.3 это метод — скобки обязательны: Таблица.Количество(), а не Таблица.Количество.
  • 🗑 Удаление строк внутри цикла Для Каждого. После удаления коллекция сдвигается, часть строк цикл пропускает. Удаляйте обратным проходом через Пока.
  • 🌳 Дерево.Строки.Количество() считает только верхний уровень. Вложенные узлы в это число не входят — для полного подсчёта нужна рекурсия.
  • 🐘 Выгрузка всей таблицы базы ради подсчёта. Выгрузить().Количество() на большой таблице тянет все записи в память. Считайте КОЛИЧЕСТВО(*) прямо в запросе.
  • 👁 Перебор динамического списка на форме. Список подгружается порциями, полного набора строк в памяти нет — считать его в цикле бессмысленно, только отдельным запросом.

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

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