Подсчёт количества строк в таблицах и выборках 1С
Содержание
Количество строк в табличной части, в результате запроса или в массиве в 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 Тогда. Для результата запроса есть специальный метод Результат.Пустой(), который не выгружает данные.