Как преобразовать строковое дополнительное свойство в число в запросе или СКД 1С?

Программист 1С v8.3 (Обычные формы) IT и автоматизация бизнеса
← На главную

При построении отчетов в 1С часто возникает необходимость выполнить математические расчеты над дополнительными реквизитами и свойствами объектов. Классическая задача: получить длину изделия в сантиметрах и разделить её на 100, чтобы перевести в метры. Однако попытка применить оператор ВЫРАЗИТЬ или выполнить деление в запросе либо в вычисляемых полях СКД нередко приводит к пустому значению или ошибке синтаксиса. Разберем причины такого поведения платформы и детально рассмотрим рабочие способы решения этой проблемы.

Выясним причину: почему оператор ВЫРАЗИТЬ не превращает строку в число

Для понимания проблемы проанализируем, как устроен механизм хранения дополнительных свойств в типовых конфигурациях (например, в УПП). В регистре сведений ЗначенияСвойствОбъектов поле Значение имеет составной тип данных. В этот тип могут входить как простые типы (число, строка, дата, булево), так и ссылки на справочники, в частности СправочникСсылка.ЗначенияСвойствОбъектов.

Часто при первоначальном заполнении базы пользователи создают свойство не с типом Число, а выбирают тип Значение свойства объекта. В этом случае в регистре хранится ссылка, а само число записано лишь в поле Наименование элемента справочника в виде текстовой строки (например, «290»).

При попытке выполнить конструкцию ВЫРАЗИТЬ(ЗначенияСвойствОбъектов.Значение КАК ЧИСЛО(15, 2)) запрос возвращает пустое значение (NULL). Разберем, почему это происходит:

  1. Оператор ВЫРАЗИТЬ в языке запросов 1С не является функцией парсинга или конвертации типов. Он не умеет преобразовывать строку «290» в число 290.
  2. Единственное назначение ВЫРАЗИТЬ — ограничение длины строк неограниченной длины и уточнение типа в полях составного типа данных. Однако приведение к типу срабатывает только тогда, когда внутри составного поля уже физически лежит значение нужного типа. Если там записана ссылка или строка, приведение к числу даст NULL.
  3. Язык выражений системы компоновки данных (СКД) также не имеет встроенной функции, аналогичной функции встроенного языка Число(), поэтому применение арифметических операций к строковому полю вызывает ошибку.

Рассмотрим четыре проверенных способа решения данной задачи в зависимости от доступности конфигуратора и архитектурных требований.

Способ 1. Использование экспортной функции в вычисляемых полях СКД

Если отчет строится на базе СКД и у нас есть возможность вносить изменения в конфигурацию, наиболее простым и надежным решением является вызов функции встроенного языка. В вычисляемых полях СКД разрешено использовать вызовы функций общих модулей.

Разберем по шагам, как это реализовать:

  1. В общем модуле конфигурации (у которого установлены флаги Сервер и Вызов сервера) создаем экспортную функцию для безопасного преобразования строки в число:

Функция СтрокаВЧисло(ЗначениеСтрока) Экспорт
    Попытка
        Возврат Число(СокрЛП(ЗначениеСтрока));
    Исключение
        Возврат 0;
    КонецПопытки;
КонецФункции
  1. В запросе схемы компоновки данных получаем наименование свойства как обычную строку: ЗначенияСвойствОбъектов.Значение.Наименование КАК ДлинаСтрокой.
  2. Переходим на закладку Вычисляемые поля в СКД. Добавляем новое поле ДлинаВМетрах и указываем для него выражение:

ИмяОбщегоМодуля.СтрокаВЧисло(ДлинаСтрокой) / 100

Этот способ позволяет избежать сложных конструкций в запросе и гарантирует корректную обработку некорректно введенных строковых данных благодаря блоку перехвата исключений.

Способ 2. Разбор строки по разрядам непосредственно в запросе

Когда изменение конфигурации невозможно (например, отчет разрабатывается как внешний без права добавления общих модулей, или расчет требуется строго внутри 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) и динамическое определение позиции символа от правого края. Однако такой код становится громоздким и создает дополнительную нагрузку на СУБД.

Способ 3. Программная постобработка и внешний набор данных

Если отчет формируется программно, наиболее практично разделить получение данных и математическую обработку:

  1. Выполняем запрос к базе и выгружаем результат в ТаблицаЗначений.
  2. Добавляем в таблицу типизированную числовую колонку.
  3. Проходим в цикле по строкам, заполняя числовое поле с помощью стандартной функции Число():

ТаблицаРезультат = Запрос.Выполнить().Выгрузить();
ТаблицаРезультат.Колонки.Добавить("ДлинаВМетрах", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 2)));

Для Каждого Стр Из ТаблицаРезультат Цикл
    Попытка
        Стр.ДлинаВМетрах = Число(Стр.ЗначениеСтрока) / 100;
    Исключение
        Стр.ДлинаВМетрах = 0;
    КонецПопытки;
КонецЦикла;
  1. Передаем подготовленную таблицу в СКД через набор данных типа Объект.

Способ 4. Архитектурное исправление: нормализация данных

Хранение чисел в строковых наименованиях справочников — распространенная ошибка проектирования структуры базы. Самым правильным и долгосрочным решением является исправление исходных данных:

  1. В плане видов характеристик (в УПП это СвойстваОбъектов) для свойства «Длина» устанавливаем тип значения Число с нужной разрядностью (например, 10 знаков, без дробной части). Либо заводим новое корректное свойство.
  2. Пишем одноразовую обработку, которая переносит значения из справочника в регистр сведений уже в числовом виде:

НаборЗаписей = РегистрыСведений.ЗначенияСвойствОбъектов.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Свойство.Установить(СтароеСвойство);
НаборЗаписей.Прочитать();

Для Каждого Запись Из НаборЗаписей Цикл
    Если ЗначениеЗаполнено(Запись.Значение) Тогда
        СтроковоеЗначение = Запись.Значение.Наименование;
        
        // Создаем запись для нового числового свойства
        МенеджерЗаписи = РегистрыСведений.ЗначенияСвойствОбъектов.СоздатьМенеджерЗаписи();
        МенеджерЗаписи.Объект = Запись.Объект;
        МенеджерЗаписи.Свойство = НовоеЧисловоеСвойство;
        Попытка
            МенеджерЗаписи.Значение = Число(СтроковоеЗначение);
            МенеджерЗаписи.Записать(Истина);
        Исключение
            // Логирование ошибок некорректных строк
        КонецПопытки;
    КонецЕсли;
КонецЦикла;
  1. После того как свойство физически хранится в типе Число, запрос будет работать без костылей:

ВЫБРАТЬ
    ЗначенияСвойствОбъектов.Объект КАК Номенклатура,
    (ВЫРАЗИТЬ(ЗначенияСвойствОбъектов.Значение КАК ЧИСЛО(10, 2))) / 100 КАК ДлинаВМетрах
ИЗ
    РегистрСведений.ЗначенияСвойствОбъектов КАК ЗначенияСвойствОбъектов
ГДЕ
    ЗначенияСвойствОбъектов.Свойство = &НовоеЧисловоеСвойство

Такой подход полностью устраняет необходимость в обходных путях, исключает ошибки деления в отчетах и повышает общую производительность системы.

← На главную