Вычисляемые поля СКД — поля схемы компоновки, которых нет в запросе набора данных. Их значение по выражению считает процессор компоновки, когда запрос уже вернул записи. Так в отчет добавляют прибыль, среднюю цену, процент, долю в итоге, место в рейтинге или текстовый статус, не переписывая запрос. Ниже — добавление поля в конфигураторе и формулой, выбор между СКД и запросом, 10 рабочих выражений, ограничения и код.
Типовые отчеты построены на том же механизме. В УТ 11.4 у отчета ВыручкаИСебестоимостьПродаж вычисляемые поля дают вес и объем проданного (Количество * НоменклатураВес), полную себестоимость из пяти слагаемых и пересчет количества в единицы для отчетов по параметру &ЕдиницыКоличества. В отчете «Задолженность клиентов по срокам» так считаются доля просроченного долга и доля клиента в общей задолженности.
Вычисляемое поле в СКД 1С 8.3 добавляют в конструкторе схемы компоновки:
ОсновнаяСхемаКомпоновкиДанных. Внешний отчет — «Файл → Открыть» и кнопка «Открыть схему компоновки данных».&, переносы строк допустимы.Сумма(ВаловаяПрибыль) или своя формула.Без конфигуратора, в отчетах на БСП, добавляют формулу: «Настройки...» → «Вид: Расширенный» → закладка «Поля и сортировки» → «Добавить» → «Добавить формулу» (так в УТ 11.4). Формула — пользовательское поле варианта: схема не меняется, конфигурация остается на поддержке, но функции общих модулей в ней запрещены. Итог простой арифметики БСП строит сама: для Выручка / Количество выйдет Сумма(Выручка) / Сумма(Количество).
Запрос выполняет СУБД, вычисляемое поле — сервер 1С, по записи за раз и уже после чтения данных. Отсюда выбор:
| Задача | Где считать | Почему |
|---|---|---|
| Отбор по показателю на большом регистре | Запрос: ГДЕ, ИМЕЮЩИЕ | Отбор по вычисляемому полю СКД применяет к уже прочитанным записям |
| Процент, средняя цена в группировках | СКД: ресурс | Отношение сумм пересчитывается на любом уровне |
| Доля в итоге, место, сравнение с соседней строкой | СКД: ВычислитьВыражение | В запросе — соединение таблицы с собой |
| Значение зависит от параметра отчета | СКД: &Параметр | Запрос не меняется |
| Нужна функция встроенного языка | СКД: функция общего модуля | В языке запросов ее нет |
| Нужны данные других таблиц | Запрос: соединение | Функция с запросом внутри — запрос на каждую запись |
| Значение нужно в условии или параметре виртуальной таблицы | Только запрос | Вычисляемое поле появляется после запроса |
Пример со стороны запроса — товары с рентабельностью ниже порога по регистру УТ 11.4. ИМЕЮЩИЕ отсекает строки в СУБД, но процент посчитан для строки запроса: в группировках СКД его не сложить, итог все равно дает ресурс. Запрос можно проверить в консоли запросов.
// Запрос набора данных, УТ 11.4: товары с рентабельностью ниже порога.
// Условие ИМЕЮЩИЕ выполняет СУБД - в отчет приходят только нужные строки.
// Параметр ПорогРентабельности (Число) появится на закладке «Параметры».
ВЫБРАТЬ
Продажи.АналитикаУчетаНоменклатуры.Номенклатура КАК Номенклатура,
СУММА(Продажи.КоличествоОборот) КАК Количество,
СУММА(Продажи.СуммаВыручкиОборот) КАК Выручка,
СУММА(Продажи.СтоимостьОборот) КАК Себестоимость,
ВЫБОР
КОГДА СУММА(Продажи.СуммаВыручкиОборот) = 0
ТОГДА 0
ИНАЧЕ ВЫРАЗИТЬ((СУММА(Продажи.СуммаВыручкиОборот) - СУММА(Продажи.СтоимостьОборот)) * 100
/ СУММА(Продажи.СуммаВыручкиОборот) КАК ЧИСЛО(15, 1))
КОНЕЦ КАК Рентабельность
ИЗ
РегистрНакопления.ВыручкаИСебестоимостьПродаж.Обороты(&НачалоПериода, &КонецПериода, , ) КАК Продажи
СГРУППИРОВАТЬ ПО
Продажи.АналитикаУчетаНоменклатуры.Номенклатура
ИМЕЮЩИЕ
СУММА(Продажи.СуммаВыручкиОборот) > 0
И (СУММА(Продажи.СуммаВыручкиОборот) - СУММА(Продажи.СтоимостьОборот)) * 100
< &ПорогРентабельности * СУММА(Продажи.СуммаВыручкиОборот)
Поля набора: Период, Номенклатура, Количество, Выручка, Себестоимость; в отчете по долгам — СрокОплаты. Параметры ВТысячах (Булево) и ДатаОтчета (Дата) заводятся на закладке «Параметры». «Ресурс» — выражение для закладки «Ресурсы» с тем же путем.
| № | Задача | Путь к данным | Выражение | Ресурс |
|---|---|---|---|---|
| 1 | Валовая прибыль | ВаловаяПрибыль | Выручка - ЕстьNULL(Себестоимость, 0) | Сумма(ВаловаяПрибыль) |
| 2 | Средняя цена | СредняяЦена | ВЫБОР КОГДА Количество = 0 ТОГДА 0 ИНАЧЕ Выручка / Количество КОНЕЦ | ВЫБОР КОГДА Сумма(Количество) = 0 ТОГДА 0 ИНАЧЕ Сумма(Выручка) / Сумма(Количество) КОНЕЦ |
| 3 | Рентабельность, % | Рентабельность | ВЫБОР КОГДА Выручка = 0 ТОГДА 0 ИНАЧЕ (Выручка - ЕстьNULL(Себестоимость, 0)) * 100 / Выручка КОНЕЦ | ВЫБОР КОГДА Сумма(Выручка) = 0 ТОГДА 0 ИНАЧЕ (Сумма(Выручка) - ЕстьNULL(Сумма(Себестоимость), 0)) * 100 / Сумма(Выручка) КОНЕЦ |
| 4 | Выручка в тысячах по флажку | ВыручкаОтчета | ВЫБОР КОГДА &ВТысячах ТОГДА Выручка / 1000 ИНАЧЕ Выручка КОНЕЦ | Сумма(ВыручкаОтчета) |
| 5 | Месяц для группировки | МесяцПродажи | НачалоПериода(Период, "Месяц") | не нужен; в «Оформлении» формат ДФ='MMMM yyyy' |
| 6 | Дней просрочки | ДнейПросрочки | ВЫБОР КОГДА ЗначениеЗаполнено(СрокОплаты) И СрокОплаты < &ДатаОтчета ТОГДА РазностьДат(СрокОплаты, &ДатаОтчета, "День") ИНАЧЕ 0 КОНЕЦ | Максимум(ДнейПросрочки) |
| 7 | Статус оплаты текстом | СтатусОплаты | ВЫБОР КОГДА НЕ ЗначениеЗаполнено(СрокОплаты) ТОГДА "Без срока" КОГДА РазностьДат(СрокОплаты, &ДатаОтчета, "День") > 30 ТОГДА "Больше 30 дней" КОГДА СрокОплаты < &ДатаОтчета ТОГДА "Просрочено" ИНАЧЕ "В срок" КОНЕЦ | не нужен, поле для группировки |
| 8 | Место по выручке в группировке | Место | 0 | ВычислитьВыражение("НомерПоПорядкуВГруппировке()", , , , , "Сумма(Выручка) Убыв") |
| 9 | Доля в выручке отчета, % | ДоляВыручки | 0 | ВЫБОР КОГДА ВычислитьВыражение("Сумма(Выручка)", "ОбщийИтог") = 0 ТОГДА 0 ИНАЧЕ Сумма(Выручка) * 100 / ВычислитьВыражение("Сумма(Выручка)", "ОбщийИтог") КОНЕЦ |
| 10 | Сумма прописью | ВыручкаПрописью | ВычисляемыеПоляОтчетов.СуммаПрописью(Выручка) | ВычисляемыеПоляОтчетов.СуммаПрописью(Сумма(Выручка)) |
"Месяц", "День". В языке запросов эти слова пишут без кавычек.РазностьДат(Дата1, Дата2, "День") возвращает Дата2 минус Дата1, поэтому срок оплаты стоит первым.0: в детальной строке нуль выводится пустой ячейкой, значение группы считает ресурс. Так устроены поля НомерПоПорядку и ДоляДолга в типовых отчетах УТ 11.4."ОбщийИтог" — итог всего отчета; шестой — сортировка, в порядке которой идет нумерация.Чего нет в языке выражений, выносят в экспортную функцию неглобального серверного общего модуля и вызывают с именем модуля. Типовой КомпоновкаДанныхСервер в УТ 11.4 отмечен флажками «Сервер», «Клиент (обычное приложение)» и «Внешнее соединение» — так функция доступна и при формировании в толстом клиенте. Подойдет и модуль менеджера: в отчете «Изменение ABC/XYZ - распределения клиентов» выражение поля — Отчеты.ИзменениеABCXYZРаспределенияКлиентов.ПолучитьПредставлениеКлассификации(ТекущийABC, ТекущийXYZ).
// Общий модуль ВычисляемыеПоляОтчетов.
// Флажки: Сервер, Клиент (обычное приложение), Внешнее соединение. Глобальный - нет.
// Вызов из выражения вычисляемого поля или ресурса:
// ВычисляемыеПоляОтчетов.СуммаПрописью(Выручка)
// ВычисляемыеПоляОтчетов.ТекстОднойСтрокой(Комментарий, 80)
// Функции вызываются для каждой записи отчета, поэтому запросов внутри нет.
// Сумма прописью в рублях. NULL и не число дают пустую строку.
Функция СуммаПрописью(Сумма) Экспорт
Если ТипЗнч(Сумма) <> Тип("Число") Тогда
Возврат "";
КонецЕсли;
Возврат ЧислоПрописью(Сумма, "Л=ru_RU; ДП=Ложь",
"рубль, рубля, рублей, м, копейка, копейки, копеек, ж, 2");
КонецФункции
// Текст в одну строку без лишних пробелов, обрезанный до МаксимумСимволов:
// многострочный комментарий не раздувает высоту строк отчета.
Функция ТекстОднойСтрокой(Текст, МаксимумСимволов) Экспорт
Если ТипЗнч(Текст) <> Тип("Строка") Тогда
Возврат "";
КонецЕсли;
Результат = СтрЗаменить(Текст, Символы.ВК, "");
Результат = СтрЗаменить(Результат, Символы.ПС, " ");
Результат = СтрЗаменить(Результат, Символы.Таб, " ");
Пока СтрНайти(Результат, " ") > 0 Цикл
Результат = СтрЗаменить(Результат, " ", " ");
КонецЦикла;
Результат = СокрЛП(Результат);
Если МаксимумСимволов > 3 И СтрДлина(Результат) > МаксимумСимволов Тогда
Результат = Лев(Результат, МаксимумСимволов - 3) + "...";
КонецЕсли;
Возврат Результат;
КонецФункции
Процессор.Инициализировать(Макет, , , Истина) — это ВозможностьИспользованияВнешнихФункций, по умолчанию Ложь. Так сделано в типовой ВедомостьПоДенежнымСредствам. Подробнее — в статье про выражения СКД.Когда отчет собирается кодом, поля добавляют в объект схемы до Компоновщик.Инициализировать: список доступных полей строится при инициализации. Повторный вызов функции перезапишет поле, а не создаст дубль.
// Модуль внешней обработки или общий модуль с флажком «Сервер».
// Схема - объект СхемаКомпоновкиДанных: копия из ПолучитьМакет (конфигурация
// не меняется) или схема, собранная кодом. Вызывать до инициализации
// компоновщика настроек: доступные поля строятся в момент Инициализировать.
// Добавляет вычисляемое поле или перезаписывает поле с тем же путем.
//
// Параметры:
// Схема - СхемаКомпоновкиДанных
// ПутьКДанным - Строка - имя поля в настройках, ресурсах и коде.
// Выражение - Строка - выражение для детальной записи.
// ЗаголовокПоля - Строка - заголовок колонки; пустой - платформа строит его из пути.
// ВыражениеИтога - Строка - выражение ресурса; пустое - итогов по полю нет.
// ЧислоЗнаков - Число, Неопределено - для числового поля: тип и формат.
//
// Возвращаемое значение:
// ВычисляемоеПолеСхемыКомпоновкиДанных
//
Функция УстановитьВычисляемоеПоле(Схема, ПутьКДанным, Выражение, ЗаголовокПоля = "",
ВыражениеИтога = "", ЧислоЗнаков = Неопределено) Экспорт
Поле = Неопределено;
Для Каждого Элемент Из Схема.ВычисляемыеПоля Цикл
Если ВРег(Элемент.ПутьКДанным) = ВРег(ПутьКДанным) Тогда
Поле = Элемент;
Прервать;
КонецЕсли;
КонецЦикла;
Если Поле = Неопределено Тогда
Поле = Схема.ВычисляемыеПоля.Добавить();
Поле.ПутьКДанным = ПутьКДанным;
КонецЕсли;
Поле.Выражение = Выражение;
Если Не ПустаяСтрока(ЗаголовокПоля) Тогда
Поле.Заголовок = ЗаголовокПоля;
КонецЕсли;
Если ЧислоЗнаков <> Неопределено Тогда
// Тип - для отбора и сравнения, формат - для вывода.
Поле.ТипЗначения = Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, ЧислоЗнаков));
Поле.Оформление.УстановитьЗначениеПараметра("Формат", "ЧДЦ=" + Строка(ЧислоЗнаков));
КонецЕсли;
// Ресурс с тем же путем считает поле в группировках и общем итоге.
Ресурс = Неопределено;
Для Каждого Элемент Из Схема.ПоляИтога Цикл
Если ВРег(Элемент.ПутьКДанным) = ВРег(ПутьКДанным) Тогда
Ресурс = Элемент;
Прервать;
КонецЕсли;
КонецЦикла;
Если ПустаяСтрока(ВыражениеИтога) Тогда
Если Ресурс <> Неопределено Тогда
Схема.ПоляИтога.Удалить(Ресурс);
КонецЕсли;
Иначе
Если Ресурс = Неопределено Тогда
Ресурс = Схема.ПоляИтога.Добавить();
Ресурс.ПутьКДанным = ПутьКДанным;
КонецЕсли;
Ресурс.Выражение = ВыражениеИтога;
КонецЕсли;
Возврат Поле;
КонецФункции
// Примеры 1, 3, 8, 9 и 10 из таблицы. Набор данных возвращает поля
// Номенклатура, Количество, Выручка, Себестоимость.
Процедура ДобавитьПоказателиПродаж(Схема) Экспорт
УстановитьВычисляемоеПоле(Схема, "ВаловаяПрибыль",
"Выручка - ЕстьNULL(Себестоимость, 0)",
"Валовая прибыль", "Сумма(ВаловаяПрибыль)", 2);
УстановитьВычисляемоеПоле(Схема, "Рентабельность",
"ВЫБОР КОГДА Выручка = 0 ТОГДА 0
|ИНАЧЕ (Выручка - ЕстьNULL(Себестоимость, 0)) * 100 / Выручка КОНЕЦ",
"Рентабельность, %",
"ВЫБОР КОГДА Сумма(Выручка) = 0 ТОГДА 0
|ИНАЧЕ (Сумма(Выручка) - ЕстьNULL(Сумма(Себестоимость), 0)) * 100 / Сумма(Выручка) КОНЕЦ", 1);
// Выражение поля 0: в детальной строке пусто, значение группы дает ресурс.
УстановитьВычисляемоеПоле(Схема, "Место", "0", "Место по выручке",
"ВычислитьВыражение(""НомерПоПорядкуВГруппировке()"", , , , , ""Сумма(Выручка) Убыв"")", 0);
УстановитьВычисляемоеПоле(Схема, "ДоляВыручки", "0", "Доля в выручке, %",
"ВЫБОР КОГДА ВычислитьВыражение(""Сумма(Выручка)"", ""ОбщийИтог"") = 0 ТОГДА 0
|ИНАЧЕ Сумма(Выручка) * 100 / ВычислитьВыражение(""Сумма(Выручка)"", ""ОбщийИтог"") КОНЕЦ", 1);
// Функция общего модуля: тип результата задаем явно.
Прописью = УстановитьВычисляемоеПоле(Схема, "ВыручкаПрописью",
"ВычисляемыеПоляОтчетов.СуммаПрописью(Выручка)", "Выручка прописью",
"ВычисляемыеПоляОтчетов.СуммаПрописью(Сумма(Выручка))");
Прописью.ТипЗначения = Новый ОписаниеТипов("Строка");
КонецПроцедуры
Дальше схему передают компоновщику как обычно — см. СКД программно. Как посчитана колонка типового отчета, покажет таблица выражений схемы — откройте ее в отладчике:
// Выражения вычисляемых полей и ресурсов схемы - в таблицу значений.
// Пример вызова на сервере:
// Таблица = ВыраженияСхемы(
// Отчеты.ЗадолженностьКлиентовПоСрокам.ПолучитьМакет("ОсновнаяСхемаКомпоновкиДанных"));
Функция ВыраженияСхемы(Схема) Экспорт
Таблица = Новый ТаблицаЗначений;
Таблица.Колонки.Добавить("Поле", Новый ОписаниеТипов("Строка"));
Таблица.Колонки.Добавить("Заголовок", Новый ОписаниеТипов("Строка"));
Таблица.Колонки.Добавить("Выражение", Новый ОписаниеТипов("Строка"));
Таблица.Колонки.Добавить("Ресурс", Новый ОписаниеТипов("Строка"));
Таблица.Колонки.Добавить("Запреты", Новый ОписаниеТипов("Строка"));
Для Каждого Поле Из Схема.ВычисляемыеПоля Цикл
НоваяСтрока = Таблица.Добавить();
НоваяСтрока.Поле = Поле.ПутьКДанным;
НоваяСтрока.Заголовок = Поле.Заголовок;
НоваяСтрока.Выражение = Поле.Выражение;
НоваяСтрока.Запреты = ТекстЗапретов(Поле.ОграничениеИспользования);
КонецЦикла;
// Ресурс вычисляемого поля - в его строку, ресурс поля набора - отдельной строкой.
// У одного поля бывает несколько ресурсов для разных группировок.
Для Каждого Ресурс Из Схема.ПоляИтога Цикл
НоваяСтрока = Таблица.Найти(Ресурс.ПутьКДанным, "Поле");
Если НоваяСтрока = Неопределено Тогда
НоваяСтрока = Таблица.Добавить();
НоваяСтрока.Поле = Ресурс.ПутьКДанным;
КонецЕсли;
НоваяСтрока.Ресурс = НоваяСтрока.Ресурс
+ ?(ПустаяСтрока(НоваяСтрока.Ресурс), "", Символы.ПС) + Ресурс.Выражение;
КонецЦикла;
Возврат Таблица;
КонецФункции
Функция ТекстЗапретов(Ограничение)
Запреты = Новый Массив;
Если Ограничение.Поле Тогда
Запреты.Добавить("вывод");
КонецЕсли;
Если Ограничение.Условие Тогда
Запреты.Добавить("отбор");
КонецЕсли;
Если Ограничение.Группировка Тогда
Запреты.Добавить("группировка");
КонецЕсли;
Если Ограничение.Порядок Тогда
Запреты.Добавить("сортировка");
КонецЕсли;
Возврат СтрСоединить(Запреты, ", ");
КонецФункции
Сумма(СебестоимостьИтого).Функция находит в настройках отборы по вычисляемым полям и формулам, которые СКД выполнит после запроса. Настройки берите из ПолучитьНастройки(): там уже учтены быстрые отборы пользователя.
// Модуль объекта отчета.
// Возвращает заголовки вычисляемых полей и формул, по которым в настройках
// включен отбор. Такие условия не попадают в текст запроса: СКД читает
// записи набора целиком и отбрасывает лишние уже после запроса.
//
// Параметры:
// Схема - СхемаКомпоновкиДанных
// Настройки - НастройкиКомпоновкиДанных - результат КомпоновщикНастроек.ПолучитьНастройки().
//
// Возвращаемое значение:
// Массив из Строка
//
Функция ОтборыПослеЗапроса(Схема, Настройки) Экспорт
// Путь в верхнем регистре -> заголовок для сообщения.
Вычисляемые = Новый Соответствие;
Для Каждого Поле Из Схема.ВычисляемыеПоля Цикл
Вычисляемые.Вставить(ВРег(Поле.ПутьКДанным),
?(ПустаяСтрока(Поле.Заголовок), Поле.ПутьКДанным, Поле.Заголовок));
КонецЦикла;
Для Каждого Формула Из Настройки.ПользовательскиеПоля.Элементы Цикл
Вычисляемые.Вставить(ВРег(Формула.ПутьКДанным),
?(ПустаяСтрока(Формула.Заголовок), Формула.ПутьКДанным, Формула.Заголовок));
КонецЦикла;
Результат = Новый Массив;
НайтиОтборыПоВычисляемым(Настройки.Отбор.Элементы, Вычисляемые, Результат);
Возврат Результат;
КонецФункции
// Обходит элементы отбора вместе с группами И, ИЛИ, НЕ.
Процедура НайтиОтборыПоВычисляемым(ЭлементыОтбора, Вычисляемые, Результат)
Для Каждого Элемент Из ЭлементыОтбора Цикл
Если Не Элемент.Использование Тогда
Продолжить;
КонецЕсли;
Если ТипЗнч(Элемент) = Тип("ГруппаЭлементовОтбораКомпоновкиДанных") Тогда
НайтиОтборыПоВычисляемым(Элемент.Элементы, Вычисляемые, Результат);
Продолжить;
КонецЕсли;
Если ТипЗнч(Элемент.ЛевоеЗначение) <> Тип("ПолеКомпоновкиДанных") Тогда
Продолжить;
КонецЕсли;
// Путь целиком, а для реквизита через точку - имя самого поля.
Путь = ВРег(Строка(Элемент.ЛевоеЗначение));
ИмяПоля = Вычисляемые[Путь];
Если ИмяПоля = Неопределено Тогда
ИмяПоля = Вычисляемые[СтрРазделить(Путь, ".")[0]];
КонецЕсли;
Если ИмяПоля <> Неопределено И Результат.Найти(ИмяПоля) = Неопределено Тогда
Результат.Добавить(ИмяПоля);
КонецЕсли;
КонецЦикла;
КонецПроцедуры
// Предупреждение при формировании. Стандартная обработка не отключается.
Процедура ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка)
Поля = ОтборыПослеЗапроса(ЭтотОбъект.СхемаКомпоновкиДанных,
ЭтотОбъект.КомпоновщикНастроек.ПолучитьНастройки());
Если Поля.Количество() > 0 Тогда
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = "Отбор по полям " + СтрСоединить(Поля, ", ")
+ " выполняется после чтения данных: на большом периоде отчет будет долгим.";
Сообщение.Сообщить();
КонецЕсли;
КонецПроцедуры
ВЫБОР КОГДА ... = 0.Продажи.СуммаВыручкиОборот), а не путь поля набора (Выручка): компоновщик поле не найдет.Сумма(Рентабельность) вместо отношения сумм.Инициализировать не передан четвертый параметр.НачалоПериода(Период, Месяц): слово Месяц читается как имя поля.Нет: запрос выполняется раньше, чем считаются вычисляемые поля. Нужно значение в условии — перенесите выражение в запрос.
Вычисляемое поле дает значение детальной записи, ресурс — то, как поле итожится в группировках и общем итоге. У одного пути бывают оба: так устроены цена и процент.
Перенести условие в запрос: ГДЕ для записей, ИМЕЮЩИЕ для сумм. Если такой отбор пользователю не нужен, запретите его флажком «Условие».
Детальные записи — вычисляемым полем НомерПоПорядкуВГруппировке(), строки группировки по убыванию выручки — ресурсом из примера 8.
Формулой в варианте — да, схема при этом не меняется. Функцию общего модуля формулой не вызвать: это уже правка схемы.
Одно-два поля добавляются за минуты. Если нужно переделать типовой отчет на поддержке, перенести тяжелые отборы в запрос или собрать отчет с долями, рейтингами и своими функциями, закажите доработку отчетов и обработок 1С или разместите задачу: оценка бесплатная, ответ в течение рабочего дня.
Станьте частью сообщества!
Войдите или зарегистрируйтесь, и вы сможете участвовать в обсуждениях.