При построении отчетов в 1С часто возникает необходимость выполнить математические расчеты над дополнительными реквизитами и свойствами объектов. Классическая задача: получить длину изделия в сантиметрах и разделить её на 100, чтобы перевести в метры. Однако попытка применить оператор ВЫРАЗИТЬ или выполнить деление в запросе либо в вычисляемых полях СКД нередко приводит к пустому значению или ошибке синтаксиса. Разберем причины такого поведения платформы и детально рассмотрим рабочие способы решения этой проблемы.
Для понимания проблемы проанализируем, как устроен механизм хранения дополнительных свойств в типовых конфигурациях (например, в УПП). В регистре сведений ЗначенияСвойствОбъектов поле Значение имеет составной тип данных. В этот тип могут входить как простые типы (число, строка, дата, булево), так и ссылки на справочники, в частности СправочникСсылка.ЗначенияСвойствОбъектов.
Часто при первоначальном заполнении базы пользователи создают свойство не с типом Число, а выбирают тип Значение свойства объекта. В этом случае в регистре хранится ссылка, а само число записано лишь в поле Наименование элемента справочника в виде текстовой строки (например, «290»).
При попытке выполнить конструкцию ВЫРАЗИТЬ(ЗначенияСвойствОбъектов.Значение КАК ЧИСЛО(15, 2)) запрос возвращает пустое значение (NULL). Разберем, почему это происходит:
ВЫРАЗИТЬ в языке запросов 1С не является функцией парсинга или конвертации типов. Он не умеет преобразовывать строку «290» в число 290.ВЫРАЗИТЬ — ограничение длины строк неограниченной длины и уточнение типа в полях составного типа данных. Однако приведение к типу срабатывает только тогда, когда внутри составного поля уже физически лежит значение нужного типа. Если там записана ссылка или строка, приведение к числу даст NULL.Число(), поэтому применение арифметических операций к строковому полю вызывает ошибку.Рассмотрим четыре проверенных способа решения данной задачи в зависимости от доступности конфигуратора и архитектурных требований.
Если отчет строится на базе СКД и у нас есть возможность вносить изменения в конфигурацию, наиболее простым и надежным решением является вызов функции встроенного языка. В вычисляемых полях СКД разрешено использовать вызовы функций общих модулей.
Разберем по шагам, как это реализовать:
Функция СтрокаВЧисло(ЗначениеСтрока) Экспорт
Попытка
Возврат Число(СокрЛП(ЗначениеСтрока));
Исключение
Возврат 0;
КонецПопытки;
КонецФункции
ЗначенияСвойствОбъектов.Значение.Наименование КАК ДлинаСтрокой.ДлинаВМетрах и указываем для него выражение:
ИмяОбщегоМодуля.СтрокаВЧисло(ДлинаСтрокой) / 100
Этот способ позволяет избежать сложных конструкций в запросе и гарантирует корректную обработку некорректно введенных строковых данных благодаря блоку перехвата исключений.
Когда изменение конфигурации невозможно (например, отчет разрабатывается как внешний без права добавления общих модулей, или расчет требуется строго внутри SQL-запроса), строку можно преобразовать в число средствами самого языка запросов. Для этого реализуется посимвольный разбор строки с вычислением веса каждого разряда через конструкцию ВЫБОР ... КОГДА.
Посмотрим на пример запроса для фиксированной длины числа до трех знаков (от 0 до 999):
ВЫБРАТЬ
ИсходныеДанные.Ссылка КАК Номенклатура,
(
// Разряд сотен
(ВЫБОР ПОДСТРОКА(ИсходныеДанные.ДлинаСтрока, 1, 1)
КОГДА "1" ТОГДА 1
КОГДА "2" ТОГДА 2
КОГДА "3" ТОГДА 3
КОГДА "4" ТОГДА 4
КОГДА "5" ТОГДА 5
КОГДА "6" ТОГДА 6
КОГДА "7" ТОГДА 7
КОГДА "8" ТОГДА 8
КОГДА "9" ТОГДА 9
ИНАЧЕ 0
КОНЕЦ) * 100
+
// Разряд десятков
(ВЫБОР ПОДСТРОКА(ИсходныеДанные.ДлинаСтрока, 2, 1)
КОГДА "1" ТОГДА 1
КОГДА "2" ТОГДА 2
КОГДА "3" ТОГДА 3
КОГДА "4" ТОГДА 4
КОГДА "5" ТОГДА 5
КОГДА "6" ТОГДА 6
КОГДА "7" ТОГДА 7
КОГДА "8" ТОГДА 8
КОГДА "9" ТОГДА 9
ИНАЧЕ 0
КОНЕЦ) * 10
+
// Разряд единиц
(ВЫБОР ПОДСТРОКА(ИсходныеДанные.ДлинаСтрока, 3, 1)
КОГДА "1" ТОГДА 1
КОГДА "2" ТОГДА 2
КОГДА "3" ТОГДА 3
КОГДА "4" ТОГДА 4
КОГДА "5" ТОГДА 5
КОГДА "6" ТОГДА 6
КОГДА "7" ТОГДА 7
КОГДА "8" ТОГДА 8
КОГДА "9" ТОГДА 9
ИНАЧЕ 0
КОНЕЦ)
) / 100 КАК ДлинаВМетрах
ИЗ
(ВЫБРАТЬ
Номенклатура.Ссылка КАК Ссылка,
// Дополняем строку слева нулями до 3 символов, если длина может быть меньше
ПОДСТРОКА(
ВЫБОР ПОДСТРОКА(ЗначенияСвойствОбъектов.Значение.Наименование, 3, 1)
КОГДА "" ТОГДА
ВЫБОР ПОДСТРОКА(ЗначенияСвойствОбъектов.Значение.Наименование, 2, 1)
КОГДА "" ТОГДА "00" + ЗначенияСвойствОбъектов.Значение.Наименование
ИНАЧЕ "0" + ЗначенияСвойствОбъектов.Значение.Наименование
КОНЕЦ
ИНАЧЕ ЗначенияСвойствОбъектов.Значение.Наименование
КОНЕЦ, 1, 3
) КАК ДлинаСтрока
ИЗ
Справочник.Номенклатура КАК Номенклатура
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЗначенияСвойствОбъектов КАК ЗначенияСвойствОбъектов
ПО Номенклатура.Ссылка = ЗначенияСвойствОбъектов.Объект
И (ЗначенияСвойствОбъектов.Свойство = &СвойствоДлина)
) КАК ИсходныеДанные
Для произвольной длины строки разбор усложняется: применяется соединение с таблицей чисел (от 0 до 9) и динамическое определение позиции символа от правого края. Однако такой код становится громоздким и создает дополнительную нагрузку на СУБД.
Если отчет формируется программно, наиболее практично разделить получение данных и математическую обработку:
ТаблицаЗначений.Число():
ТаблицаРезультат = Запрос.Выполнить().Выгрузить();
ТаблицаРезультат.Колонки.Добавить("ДлинаВМетрах", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2)));
Для Каждого Стр Из ТаблицаРезультат Цикл
Попытка
Стр.ДлинаВМетрах = Число(Стр.ЗначениеСтрока) / 100;
Исключение
Стр.ДлинаВМетрах = 0;
КонецПопытки;
КонецЦикла;
Хранение чисел в строковых наименованиях справочников — распространенная ошибка проектирования структуры базы. Самым правильным и долгосрочным решением является исправление исходных данных:
СвойстваОбъектов) для свойства «Длина» устанавливаем тип значения Число с нужной разрядностью (например, 10 знаков, без дробной части). Либо заводим новое корректное свойство.
НаборЗаписей = РегистрыСведений.ЗначенияСвойствОбъектов.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Свойство.Установить(СтароеСвойство);
НаборЗаписей.Прочитать();
Для Каждого Запись Из НаборЗаписей Цикл
Если ЗначениеЗаполнено(Запись.Значение) Тогда
СтроковоеЗначение = Запись.Значение.Наименование;
// Создаем запись для нового числового свойства
МенеджерЗаписи = РегистрыСведений.ЗначенияСвойствОбъектов.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Объект = Запись.Объект;
МенеджерЗаписи.Свойство = НовоеЧисловоеСвойство;
Попытка
МенеджерЗаписи.Значение = Число(СтроковоеЗначение);
МенеджерЗаписи.Записать(Истина);
Исключение
// Логирование ошибок некорректных строк
КонецПопытки;
КонецЕсли;
КонецЦикла;
ВЫБРАТЬ
ЗначенияСвойствОбъектов.Объект КАК Номенклатура,
(ВЫРАЗИТЬ(ЗначенияСвойствОбъектов.Значение КАК ЧИСЛО(10, 2))) / 100 КАК ДлинаВМетрах
ИЗ
РегистрСведений.ЗначенияСвойствОбъектов КАК ЗначенияСвойствОбъектов
ГДЕ
ЗначенияСвойствОбъектов.Свойство = &НовоеЧисловоеСвойство
Такой подход полностью устраняет необходимость в обходных путях, исключает ошибки деления в отчетах и повышает общую производительность системы.