При работе с запросами в 1С часто возникает задача посчитать количество строк, удовлетворяющих определенным условиям, и вернуть это число из функции для дальнейшего использования. На первый взгляд, задача простая, но есть несколько нюансов, которые могут привести к ошибкам. Давайте разберем, как правильно это сделать, и какие подводные камни могут встретиться на пути.
Проанализируем исходную ситуацию. Разработчик написал функцию с запросом, который использует агрегатную функцию КОЛИЧЕСТВО() для подсчета строк в табличной части документа. Вот этот код:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ЗаказКлиентаТовары.Ссылка КАК Ссылка,
| КОЛИЧЕСТВО(ЗаказКлиентаТовары.НомерСтроки) КАК Количество
|ИЗ
| Документ.ЗаказКлиента.Товары КАК ЗаказКлиентаТовары
|ГДЕ
| ЗаказКлиентаТовары.Ссылка = &Ссылка
|
|СГРУППИРОВАТЬ ПО
| ЗаказКлиентаТовары.Ссылка";
Запрос.УстановитьПараметр("Ссылка", СсылкаНаДокумент);
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
Возврат ?????????????
Проблема заключалась в том, как из объекта Выборка получить итоговое число. Попытка использовать Выборка.Количество или Выборка.Количество() не давала ожидаемого результата, а в дальнейшем приводила к ошибке сравнения типов. Разберемся по шагам, как решить эту задачу правильно.
Ключевой момент, который нужно понимать: запрос с агрегатными функциями (такими как КОЛИЧЕСТВО(), СУММА()) без группировки или с группировкой по одному значению (как в примере) всегда возвращает одну строку. В этой единственной строке и содержится вычисленный результат.
Объект ВыборкаИзРезультатаЗапроса — это, по сути, курсор, который нужно сначала установить на эту строку, чтобы получить из нее данные. Для этого используется метод Следующий().
Шаг 1: Позиционирование на результате
Перед тем как обращаться к полям выборки, необходимо вызвать метод Выборка.Следующий(). Если результат запроса не пустой, метод вернет Истина и переместит внутренний указатель на первую (и в нашем случае единственную) строку.
Шаг 2: Получение значения
После успешного позиционирования можно обращаться к полям результата по их именам. В нашем запросе мы назвали колонку с количеством строк "Количество" (КАК Количество). Значит, для получения значения нужно использовать конструкцию Выборка.Количество.
Соединим эти шаги в код:
// ... код запроса ...
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
Если Выборка.Следующий() Тогда
// Мы успешно встали на строку с результатом
Возврат Выборка.Количество;
КонецЕсли;
Этот код уже почти рабочий. Но он содержит скрытую проблему, которая и вызвала у автора ошибку "Операции сравнения на больше-меньше допустимы только для значений совпадающих примитивных типов".
Выясним причину ошибки. Что произойдет, если запрос не вернет ни одной строки? Например, если у документа пустая табличная часть "Товары". В этом случае метод Выборка.Следующий() вернет Ложь, условие Если не выполнится, и функция завершит работу без явного оператора Возврат. В 1С это означает, что функция вернет значение Неопределено.
Далее, в вызывающем коде, при попытке сравнить этот результат с числом (например, Если МояФункция() > 10 Тогда ...) платформа справедливо выдаст ошибку, так как нельзя сравнить Неопределено и Число.
Чтобы избежать этого, функция всегда должна возвращать значение ожидаемого типа, в нашем случае — число.
Решение 1: Использование `Иначе`
Самый простой способ — добавить ветку Иначе и в ней возвращать 0.
Если Выборка.Следующий() Тогда
Возврат Выборка.Количество;
Иначе
Возврат 0;
КонецЕсли;
Это полностью рабочая и корректная конструкция.
Решение 2: Инициализация переменной (рекомендуемый стиль)
Более универсальный и читаемый подход, который избавляет от лишних вложенных условий и множественных точек выхода из функции, — это инициализация переменной для результата.
Функция ПолучитьКоличествоСтрокТоваров(СсылкаНаДокумент)
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| КОЛИЧЕСТВО(ТЧ.НомерСтроки) КАК Количество
|ИЗ
| Документ.ЗаказКлиента.Товары КАК ТЧ
|ГДЕ
| ТЧ.Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", СсылкаНаДокумент);
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
// 1. Инициализируем переменную значением по умолчанию
КоличествоСтрок = 0;
// 2. Пытаемся получить реальное значение из запроса
Если Выборка.Следующий() Тогда
КоличествоСтрок = Выборка.Количество;
КонецЕсли;
// 3. Возвращаем переменную. Она всегда будет иметь тип Число.
Возврат КоличествоСтрок;
КонецФункции
Такая структура функции считается хорошей практикой программирования: она имеет одну точку выхода и всегда возвращает предсказуемый результат. Обратите внимание, что мы также немного упростили запрос, убрав из него лишнее поле Ссылка и группировку, так как для получения одного итогового числа они не нужны.
Рассмотрим и другие методы, которые могут быть полезны в разных ситуациях.
Можно выгрузить результат запроса в таблицу значений и затем получить нужную ячейку. Это удобно, но может быть менее производительно, если результат запроса большой (в нашем случае он всегда состоит из одной строки, так что разница будет невелика).
// ... код запроса ...
РезультатЗапроса = Запрос.Выполнить();
Если РезультатЗапроса.Пустой() Тогда
Возврат 0;
Иначе
ТаблицаРезультата = РезультатЗапроса.Выгрузить();
// Обращаемся к первой строке (индекс 0) и колонке "Количество"
Возврат ТаблицаРезультата[0].Количество;
КонецЕсли;
Важный момент: если вам нужно просто посчитать все строки в табличной части конкретного документа и нет никаких дополнительных условий отбора (которые требуют использования ГДЕ), то выполнять запрос к базе данных — это избыточное действие.
Гораздо эффективнее получить объект документа и обратиться к его табличной части напрямую.
// Этот способ намного быстрее, если нет сложных условий
ОбъектДокумента = СсылкаНаДокумент.ПолучитьОбъект();
КоличествоСтрок = ОбъектДокумента.Товары.Количество();
Возврат КоличествоСтрок;
Этот метод не нагружает СУБД, так как все данные документа считываются в оперативную память одним обращением при вызове ПолучитьОбъект(). Используйте его, если условия задачи это позволяют.