Количество строк в 1С: таблица значений, запрос, выборка

· 9 мин

Содержание

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

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

Метод Количество() для таблицы значений и табличных частей

У объекта ТаблицаЗначений есть метод Количество(). Он не перебирает строки, а возвращает уже известный размер коллекции:

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

Тот же метод есть у большинства коллекций платформы. Названия и смысл совпадают:

КоллекцияКак получить размер
Таблица значенийТЗ.Количество()
Табличная часть документаОбъект.Товары.Количество()
МассивМассив.Количество()
Список значенийСписок.Количество()
СоответствиеСоответствие.Количество()
СтруктураСтруктура.Количество()

Для табличной части обращаемся к ней по имени реквизита объекта. Например, строки в табличной части Товары документа реализации:

Всего = Документ.Товары.Количество();

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

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

Не путайте Количество() с методом Итог(). Первый возвращает число строк, второй — сумму значений по колонке:

ЧислоСтрок = ТаблицаЗначений.Количество();   // сколько строк
СуммаПоКолонке = ТаблицаЗначений.Итог("Сумма"); // сумма значений в колонке «Сумма»

Сколько уникальных строк в таблице значений

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

Копия = ТаблицаЗначений.Скопировать();
Копия.Свернуть("Контрагент");
ЧислоКонтрагентов = Копия.Количество();

Свернуть() меняет саму таблицу, а не возвращает новую. Поэтому сначала делаем копию через Скопировать(), иначе исходные данные будут потеряны.

Подсчёт записей базы агрегатной функцией в запросе

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

ВЫБРАТЬ
	КОЛИЧЕСТВО(*) КАК ВсегоСтрок
ИЗ
	Справочник.Номенклатура

Такой запрос возвращает одну строку с одним числом — его и читаем:

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

Псевдоним агрегата не называйте словом «Количество» — оно совпадает с именем метода выборки, и чтение поля станет неоднозначным. Поэтому в примере используется ВсегоСтрок.

Разница между КОЛИЧЕСТВО(*) и КОЛИЧЕСТВО(Поле) принципиальна:

  • КОЛИЧЕСТВО(*) 🔢 — считает все строки, попавшие в выборку.
  • КОЛИЧЕСТВО(Поле) 🚫 — считает только строки, где это поле не NULL. Пустые значения пропускаются.

Если нужно число уникальных значений, добавляют ключевое слово РАЗЛИЧНЫЕ:

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

Это посчитает, со сколькими разными контрагентами были документы, а не сколько документов всего. Учтите: РАЗЛИЧНЫЕ тоже пропускает NULL — пустые значения в число уникальных не попадают.

Количество и сумма за один проход

Если рядом с числом строк нужна ещё и сумма по колонке, не делайте два запроса. Несколько агрегатов считаются в одном:

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

Один обход таблицы базой — и оба числа готовы. Это дешевле, чем два отдельных запроса с одинаковым отбором.

Подсчёт через выборку и менеджер объектов

Иногда запрос уже написан под другую задачу и возвращает полноценный набор строк, а число нужно «попутно». Здесь есть два способа.

Выгрузить результат в таблицу значений. Метод Выгрузить() отдаёт обычную таблицу значений, у которой снова работает знакомый Количество():

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

Спросить размер у выборки. У объекта ВыборкаИзРезультатаЗапроса есть метод Количество(). При линейном обходе (прямом, без группировок) он вернёт общее число записей:

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

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

Отдельно про обход через менеджер объекта — метод Выбрать() у менеджера справочника или документа:

Выборка = Справочники.Номенклатура.Выбрать();

Такая выборка предназначена для перебора элементов по одному и готового числа «сколько всего» не даёт. Считать через неё размер — значит перебирать все записи вручную. Для подсчёта это лишняя работа: тот же результат даёт запрос с КОЛИЧЕСТВО(*) за один обход на стороне СУБД.

Подсчёт строк с отбором и условием

Чаще нужно не «сколько всего», а «сколько подходящих под условие». Подход снова зависит от источника.

В базе условие ставят секцией ГДЕ — СУБД посчитает уже отфильтрованные строки:

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

Параметр &Организация передаётся через Запрос.УстановитьПараметр("Организация", Организация) — так подстановка значения проходит безопасно и запрос кэшируется.

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

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

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

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

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

Если же данные в базе, то же условие лучше задать в ГДЕ запроса — тогда и перебирать ничего не придётся.

Подсчёт по группам

Когда нужно число строк не одним числом, а в разрезе — по складам, организациям, статусам, — добавляют секцию СГРУППИРОВАТЬ ПО. На каждую группу придётся своя строка результата со своим количеством:

ВЫБРАТЬ
	Склад КАК Склад,
	КОЛИЧЕСТВО(*) КАК КоличествоСтрок
ИЗ
	РегистрНакопления.ТоварыНаСкладах
СГРУППИРОВАТЬ ПО
	Склад

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

Отбор по самому количеству

Отдельная задача — найти не все группы, а только те, где строк больше или меньше определённого числа. Например, контрагенты, по которым больше десяти документов. Секция ГДЕ здесь не подходит: она фильтрует строки до подсчёта, а условие нужно наложить на результат агрегата. Для этого есть секция ИМЕЮЩИЕ:

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

Разница между секциями короткая: ГДЕ отбирает исходные строки, ИМЕЮЩИЕ — уже посчитанные группы. Их можно использовать вместе: сначала ГДЕ отсеивает ненужные документы, потом группировка считает оставшиеся, а ИМЕЮЩИЕ оставляет только группы с нужным количеством.

Где лежат данные, которые нужно посчитать?

Какой способ быстрее

Разница в скорости — не в самом подсчёте, а в том, сколько данных при этом перемещается.

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

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

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

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

Цикл со счётчиком (Счётчик = Счётчик + 1 на каждой строке) оправдан лишь тогда, когда вы всё равно обходите коллекцию по другой причине и подсчёт идёт заодно. Ради одного только числа он не нужен ни для коллекции в памяти, ни тем более для базы.

Пустые значения, NULL и пустой результат

Два момента регулярно дают неожиданный результат.

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

ВЫБРАТЬ
	ЕСТЬNULL(СУММА(Остатки.КоличествоОстаток), 0) КАК Остаток
ИЗ
	РегистрНакопления.ТоварыНаСкладах.Остатки КАК Остатки

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

Иначе ведёт себя запрос без агрегата: по пустой таблице он не вернёт ни одной строки. Проверить это заранее помогает метод результата Пустой():

Результат = Запрос.Выполнить();
Если Результат.Пустой() Тогда
	Возврат 0;
КонецЕсли;

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

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

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