Как исправить ошибку "Преобразование значения к типу Число не может быть выполнено" при формировании печатной формы с объединенными запросами в 1С?

Программист 1С v8.3 (Управляемые формы)
← На главную

При разработке внешних печатных форм в 1С часто возникает необходимость объединять данные из различных табличных частей одного документа или из разных связанных таблиц. Для этого удобно использовать язык запросов 1С с оператором ОБЪЕДИНИТЬ ВСЕ (UNION ALL). Однако, некорректное использование этого оператора может привести к ошибкам преобразования типов данных, например, к сообщению "Преобразование значения к типу Число не может быть выполнено". В этом руководстве мы подробно рассмотрим причины возникновения этой ошибки и предложим эффективные способы ее устранения, а также рассмотрим альтернативные подходы.

Суть проблемы: объединение табличных частей документа "РеализацияТоваровУслуг"

Предположим, перед нами стоит задача сформировать печатную форму акта выполненных работ, которая должна содержать информацию как об оказанных услугах (из табличной части Услуги документа РеализацияТоваровУслуг), так и о товарах (например, запчастях или материалах, из табличной части Товары того же документа). При попытке объединить эти данные в одном запросе и вывести их в табличную область печатной формы, возникает упомянутая ошибка.

Изначальный код запроса, который может привести к ошибке, выглядит примерно так:


Запрос = новый Запрос;
Запрос.УстановитьПараметр("Ссылка",СсылкаНаОбъект);
Запрос.Текст = "ВЫБРАТЬ
    | РеализацияТоваровУслугТовары.НомерСтроки,
    | РеализацияТоваровУслугТовары.Номенклатура КАК ТипПротектора,
    | ВЫРАЗИТЬ(РеализацияТоваровУслугТовары.Номенклатура.НаименованиеПолное КАК Строка(100)) КАК Услуга,
    | РеализацияТоваровУслугТовары.Сумма
    |ИЗ
    | Документ.РеализацияТоваровУслуг.Услуги КАК РеализацияТоваровУслугТовары
    |ГДЕ
    | РеализацияТоваровУслугТовары.Ссылка = &Ссылка
    |ОБЪЕДИНИТЬ ВСЕ
    |ВЫБРАТЬ
    | РеализацияТоваровУслугТовары.НомерСтроки,
    | РеализацияТоваровУслугТовары.Номенклатура.Наименование КАК Марка,
    | ВЫРАЗИТЬ(РеализацияТоваровУслугТовары.Номенклатура.РазмерШины КАК Строка(100)) КАК Размер,
    | ВЫРАЗИТЬ(РеализацияТоваровУслугТовары.Номенклатура.ЗаводскойНомерИДатаИзготовления КАК Строка(100)) КАК ЗавНомер
    |ИЗ
    | Документ.РеализацияТоваровУслуг.Товары КАК РеализацияТоваровУслугТовары
    |ГДЕ
    | РеализацияТоваровУслугТовары.Ссылка = &Ссылка
    |УПОРЯДОЧИТЬ ПО
    | НомерСтроки";

Выборка = Запрос.Выполнить.Выбрать;
ТабДок.Вывести(ОбластьШапкаТаблицы);
ИтогоСумма = 0;
Пока Выборка.Следующий Цикл
    ЗаполнитьЗначенияСвойств(ОбластьДанные.Параметры,Выборка);
    ИтогоСумма = ИтогоСумма + Выборка.Сумма;
    ТабДок.Вывести(ОбластьДанные);
КонецЦикла;

В этом коде ошибка возникает при попытке сложить значения Выборка.Сумма. Причина в том, что в первой части запроса колонка Сумма является числом, а во второй части запроса в аналогичной позиции (четвертая колонка) находится ЗавНомер, который является строкой. Оператор ОБЪЕДИНИТЬ ВСЕ требует, чтобы количество колонок, их порядок и совместимость типов были одинаковыми для всех объединяемых частей. Типы колонок результирующей выборки определяются типами колонок в первом подзапросе. Если во втором подзапросе в аналогичной позиции будет колонка несовместимого типа, она будет приведена к типу из первого подзапроса (если это возможно) или вызовет ошибку при попытке дальнейшей обработки, как в нашем случае при сложении.

Решение 1: Коррекция запроса с ОБЪЕДИНИТЬ ВСЕ для обеспечения совместимости типов (рекомендуемый подход)

Наиболее эффективным и правильным решением является приведение колонок в каждом подзапросе оператора ОБЪЕДИНИТЬ ВСЕ к единой структуре и совместимым типам данных. Это означает, что для колонок, которые существуют только в одной из объединяемых частей, мы должны добавить "пустые" или фиктивные колонки соответствующего типа в другую часть.

  1. Обеспечим одинаковое количество колонок: Убедимся, что оба подзапроса возвращают одинаковое количество колонок.
  2. Согласуем наименования колонок: Названия колонок в результирующей выборке ОБЪЕДИНИТЬ ВСЕ берутся из первого подзапроса. Поэтому колонки, которые должны быть выведены в печатную форму, должны иметь соответствующие наименования в первом подзапросе.
  3. Согласуем типы данных: Если колонка Сумма (число) присутствует в первой части, но отсутствует во второй, во вторую часть нужно добавить фиктивную колонку с числовым значением (например, 0 КАК Сумма). Аналогично, если колонка ЗавНомер (строка) присутствует во второй части, но отсутствует в первой, в первую часть нужно добавить фиктивную колонку со строковым значением (например, "" КАК ЗавНомер).

Рассмотрим пример исправленного запроса, который корректно объединяет данные об услугах и товарах:


Запрос = Новый Запрос;
Запрос.УстановитьПараметр("Ссылка", СсылкаНаОбъект);
Запрос.Текст = 
"ВЫБРАТЬ
    | РеализацияТоваровУслугУслуги.НомерСтроки КАК НомерСтроки,
    | РеализацияТоваровУслугУслуги.Номенклатура КАК Номенклатура,
    | РеализацияТоваровУслугУслуги.Номенклатура.НаименованиеПолное КАК ОписаниеСтроки,
    | РеализацияТоваровУслугУслуги.Сумма КАК Сумма,
    | "" КАК Марка,
    | "" КАК Размер,
    | "" КАК ЗавНомер,
    | "" КАК ТипЛатки,
    | ИСТИНА КАК ЭтоУслуга // Флаг для идентификации типа строки
    |ИЗ
    | Документ.РеализацияТоваровУслуг.Услуги КАК РеализацияТоваровУслугУслуги
    |ГДЕ
    | РеализацияТоваровУслугУслуги.Ссылка = &Ссылка
    
    |ОБЪЕДИНИТЬ ВСЕ
    
    |ВЫБРАТЬ
    | РеализацияТоваровУслугТовары.НомерСтроки,
    | РеализацияТоваровУслугТовары.Номенклатура,
    | РеализацияТоваровУслугТовары.Номенклатура.Наименование КАК ОписаниеСтроки,
    | 0 КАК Сумма, // Добавляем нулевую сумму для товаров
    | РеализацияТоваровУслугТовары.Номенклатура.Наименование,
    | ВЫРАЗИТЬ(РеализацияТоваровУслугТовары.Номенклатура.РазмерШины КАК Строка(100)),
    | ВЫРАЗИТЬ(РеализацияТоваровУслугТовары.Номенклатура.ЗаводскойНомерИДатаИзготовления КАК Строка(100)),
    | "" КАК ТипЛатки,
    | ЛОЖЬ КАК ЭтоУслуга // Флаг для идентификации типа строки
    |ИЗ
    | Документ.РеализацияТоваровУслуг.Товары КАК РеализацияТоваровУслугТовары
    |ГДЕ
    | РеализацияТоваровУслугТовары.Ссылка = &Ссылка
    
    |ОБЪЕДИНИТЬ ВСЕ
    
    |ВЫБРАТЬ
    | РеализацияТоваровУслугЛатки.НомерСтроки,
    | РеализацияТоваровУслугЛатки.Номенклатура,
    | РеализацияТоваровУслугЛатки.Номенклатура.Наименование КАК ОписаниеСтроки,
    | 0 КАК Сумма, // Добавляем нулевую сумму для латок
    | "" КАК Марка,
    | "" КАК Размер,
    | "" КАК ЗавНомер,
    | РеализацияТоваровУслугЛатки.Номенклатура.Наименование,
    | ЛОЖЬ КАК ЭтоУслуга // Флаг для идентификации типа строки
    |ИЗ
    | Документ.РеализацияТоваровУслуг.Латки КАК РеализацияТоваровУслугЛатки
    |ГДЕ
    | РеализацияТоваровУслугЛатки.Ссылка = &Ссылка
    
    |УПОРЯДОЧИТЬ ПО
    | НомерСтроки";

//... дальнейший код обработки выборки

В этом примере мы добавили фиктивные колонки (например, "" КАК Марка, 0 КАК Сумма) в те подзапросы, где соответствующие данные отсутствуют, чтобы все три части запроса возвращали одинаковый набор колонок с совместимыми типами. Это позволяет избежать ошибки преобразования. Также мы добавили колонку ЭтоУслуга для программного определения типа строки в цикле вывода.

Оптимизация и обработка данных после выполнения запроса

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


// ... (код до запроса) ...

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

ТабДок.Вывести(ОбластьШапкаТаблицы);

Для Каждого ТекСтрокаДетали Из Выборка Цикл
    // Используем флаг для определения, как заполнять область
    Если ТекСтрокаДетали.ЭтоУслуга Тогда
        ОбластьДанные.Параметры.Услуга = ТекСтрокаДетали.ОписаниеСтроки;
        ОбластьДанные.Параметры.Сумма = ТекСтрокаДетали.Сумма;
        ОбластьДанные.Параметры.ТипПротектора = ТекСтрокаДетали.Номенклатура.Представление; // или другое поле
        ОбластьДанные.Параметры.Марка = "";
        ОбластьДанные.Параметры.Размер = "";
        ОбластьДанные.Параметры.ЗавНомер = "";
        ОбластьДанные.Параметры.ТипЛатки = "";
    ИначеЕсли ТекСтрокаДетали.Номенклатура.Тип = Тип("СправочникСсылка.Номенклатура") Тогда // Если это товар
        ОбластьДанные.Параметры.Услуга = "";
        ОбластьДанные.Параметры.Сумма = ""; // Товары могут не иметь прямого 'суммы' в этой колонке
        ОбластьДанные.Параметры.ТипПротектора = "";
        ОбластьДанные.Параметры.Марка = ТекСтрокаДетали.Марка;
        ОбластьДанные.Параметры.Размер = ТекСтрокаДетали.Размер;
        ОбластьДанные.Параметры.ЗавНомер = ТекСтрокаДетали.ЗавНомер;
        ОбластьДанные.Параметры.ТипЛатки = "";
    Иначе // Возможно, это латка
        ОбластьДанные.Параметры.Услуга = "";
        ОбластьДанные.Параметры.Сумма = "";
        ОбластьДанные.Параметры.ТипПротектора = "";
        ОбластьДанные.Параметры.Марка = "";
        ОбластьДанные.Параметры.Размер = "";
        ОбластьДанные.Параметры.ЗавНомер = "";
        ОбластьДанные.Параметры.ТипЛатки = ТекСтрокаДетали.ТипЛатки;
    КонецЕсли;

    ТабДок.Вывести(ОбластьДанные);
КонецЦикла;

ОбластьПодвал.Параметры.ИтогоСумма = Выборка.Итог("Сумма"); // Используем метод Итог()
ТабДок.Вывести(ОбластьПодвал);

Обратите внимание, что в данном случае колонка ОписаниеСтроки может содержать как полное наименование услуги, так и наименование товара/латки. В макете печатной формы вам потребуется соответствующая логика для вывода информации в нужные поля.

Решение 2: Использование нескольких независимых запросов (менее оптимальный подход)

Альтернативным, хотя и менее оптимальным с точки зрения производительности, способом является выполнение нескольких отдельных запросов для каждой табличной части и последующая их обработка. Этот подход может быть проще для понимания новичкам, так как он позволяет избежать сложностей с ОБЪЕДИНИТЬ ВСЕ, но сопряжен с дополнительными обращениями к серверу базы данных.

Пример реализации с несколькими запросами:


// ... (код до запроса) ...

ТабДок.Вывести(ОбластьШапки);

// Запрос для услуг
Запрос = Новый Запрос;
Запрос.УстановитьПараметр("Ссылка", СсылкаНаОбъект);
Запрос.Текст = "ВЫБРАТЬ
    | РеализацияТоваровУслугУслуги.НомерСтроки КАК НомерСтроки,
    | РеализацияТоваровУслугУслуги.Номенклатура КАК ТипПротектора,
    | РеализацияТоваровУслугУслуги.Номенклатура.НаименованиеПолное КАК Услуга,
    | РеализацияТоваровУслугУслуги.Сумма
    |ИЗ
    | Документ.РеализацияТоваровУслуг.Услуги КАК РеализацияТоваровУслугУслуги
    |ГДЕ
    | РеализацияТоваровУслугУслуги.Ссылка = &Ссылка
    |УПОРЯДОЧИТЬ ПО
    | НомерСтроки";
ВыборкаУслуг = Запрос.Выполнить().Выбрать();

// Запрос для товаров
Запрос1 = Новый Запрос;
Запрос1.УстановитьПараметр("Ссылка", СсылкаНаОбъект);
Запрос1.Текст = "ВЫБРАТЬ
    | РеализацияТоваровУслугТовары.НомерСтроки КАК НомерСтроки,
    | РеализацияТоваровУслугТовары.Номенклатура КАК Марка,
    | РеализацияТоваровУслугТовары.Номенклатура.РазмерШины КАК Размер,
    | РеализацияТоваровУслугТовары.Номенклатура.ЗаводскойНомерИДатаИзготовления КАК ЗавНомер
    |ИЗ
    | Документ.РеализацияТоваровУслуг.Товары КАК РеализацияТоваровУслугТовары
    |ГДЕ
    | РеализацияТоваровУслугТовары.Ссылка = &Ссылка
    |УПОРЯДОЧИТЬ ПО
    | НомерСтроки";
ВыборкаТоваров = Запрос1.Выполнить().Выбрать();

// Запрос для латок (если есть отдельная табличная часть)
Запрос2 = Новый Запрос;
Запрос2.УстановитьПараметр("Ссылка", СсылкаНаОбъект);
Запрос2.Текст = "ВЫБРАТЬ
    | РеализацияТоваровУслугЛатки.НомерСтроки КАК НомерСтроки,
    | РеализацияТоваровУслугЛатки.Номенклатура КАК ТипЛатки
    |ИЗ
    | Документ.РеализацияТоваровУслуг.Латки КАК РеализацияТоваровУслугЛатки
    |ГДЕ
    | РеализацияТоваровУслугЛатки.Ссылка = &Ссылка
    |УПОРЯДОЧИТЬ ПО
    | НомерСтроки";
ВыборкаЛаток = Запрос2.Выполнить().Выбрать();

ТабДок.Вывести(ОбластьШапкаТаблицы);

ИтогоСумма = 0;

// Предполагаем, что вывод должен быть по строкам документа, объединяя данные по НомерСтроки
// Это более сложная логика, требующая связывания выборок или предварительной выгрузки в ТаблицыЗначений
// и их объединения в коде. 
// Если просто итерировать каждую выборку отдельно, получим сначала все услуги, потом все товары, потом все латки.
// Пример, который привел пользователь, не совсем корректен для вывода в одну строку,
// т.к. он использует вложенные циклы, что приведет к дублированию строк основной табличной части:
// Пока ВыборкаУслуг.Следующий Цикл
//     ЗаполнитьЗначенияСвойств(ОбластьДанные.Параметры,ВыборкаУслуг);
//     ИтогоСумма = ИтогоСумма + ВыборкаУслуг.Сумма;
//     Пока ВыборкаТоваров.Следующий Цикл // ЭТО ОШИБКА! Вложенные циклы не для этого!
//         ЗаполнитьЗначенияСвойств(ОбластьДанные.Параметры,ВыборкаТоваров);
//     КонецЦикла;
//     ТабДок.Вывести(ОбластьДанные);
// КонецЦикла;

// Правильная обработка нескольких выборок потребует более сложной логики,
// например, выгрузки всех выборок в ТаблицыЗначений и последующего объединения по НомерСтроки
// или вывода каждой выборки в отдельный блок макета, если это позволяет дизайн печатной формы.

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

Пока ВыборкаТоваров.Следующий Цикл
    ЗаполнитьЗначенияСвойств(ОбластьДанные.Параметры, ВыборкаТоваров); // Заполняем параметры из товаров
    // Очищаем или заполняем пустые значения для колонок, специфичных для услуг/латок
    ОбластьДанные.Параметры.Услуга = "";
    ОбластьДанные.Параметры.ТипПротектора = "";
    ОбластьДанные.Параметры.Сумма = 0; // Для суммирования, если товары не имеют 'Сумма'
    ОбластьДанные.Параметры.ТипЛатки = "";
    ТабДок.Вывести(ОбластьДанные);
КонецЦикла;

Пока ВыборкаЛаток.Следующий Цикл
    ЗаполнитьЗначенияСвойств(ОбластьДанные.Параметры, ВыборкаЛаток); // Заполняем параметры из латок
    // Очищаем или заполняем пустые значения для колонок, специфичных для услуг/товаров
    ОбластьДанные.Параметры.Услуга = "";
    ОбластьДанные.Параметры.ТипПротектора = "";
    ОбластьДанные.Параметры.Сумма = 0; // Для суммирования, если латки не имеют 'Сумма'
    ОбластьДанные.Параметры.Марка = "";
    ОбластьДанные.Параметры.Размер = "";
    ОбластьДанные.Параметры.ЗавНомер = "";
    ТабДок.Вывести(ОбластьДанные);
КонецЦикла;

ОбластьПодвал.Параметры.ИтогоСумма = ИтогоСумма;
ТабДок.Вывести(ОбластьПодвал);

Недостатки этого подхода:

  1. Производительность: Выполняется несколько запросов к базе данных, что значительно медленнее, чем один сложный запрос, особенно на больших объемах данных.
  2. Сложность сопоставления строк: Если требуется, чтобы услуги, товары и латки, относящиеся к одной логической позиции документа, выводились в одной строке печатной формы, этот подход становится очень громоздким и требует сложной логики сопоставления строк выборок в коде 1С.
  3. Суммирование: Итоговая сумма должна быть рассчитана путем агрегации всех необходимых выборок, а не только одной из них.

Общие рекомендации и лучшие практики при работе с запросами для печатных форм

  1. Используйте параметры запросов: Всегда передавайте переменные в запрос через параметры (например, &Ссылка), а не конкатенацией строк. Это предотвращает SQL-инъекции и улучшает производительность, позволяя СУБД кэшировать планы выполнения запросов.
  2. Корректное формирование текста запроса: При построении текста запроса из нескольких частей используйте одну переменную для накопления текста, чтобы избежать перезаписи.
    
    ТекстЗапроса = "ВЫБРАТЬ ...";
    ТекстЗапроса = ТекстЗапроса + "|ОБЪЕДИНИТЬ ВСЕ ...";
    Запрос.Текст = ТекстЗапроса;
    
  3. Явное приведение типов: Если есть сомнения в типе данных, используйте функцию ВЫРАЗИТЬ(Поле КАК Тип) в запросе. Например, ВЫРАЗИТЬ(РеализацияТоваровУслугТовары.Сумма КАК Число(15,2)).
  4. Использование ЗаполнитьЗначенияСвойств(): Метод ЗаполнитьЗначенияСвойств(Область.Параметры, Выборка) автоматически заполняет параметры области макета значениями из выборки, если имена параметров совпадают с именами полей выборки. Это удобно, но требует соответствия имен.
  5. Отладка запросов: Если запрос работает некорректно, всегда используйте консоль запросов для отладки. Выполните запрос по частям, проверьте типы и значения возвращаемых колонок.
  6. Соответствие макета и запроса: Убедитесь, что в макете печатной формы предусмотрены параметры для всех колонок, которые возвращает запрос, и что они используются корректно (например, [ТипПротектора], [Услуга], [Сумма]).

Таким образом, для эффективной и безошибочной работы с печатными формами, требующими объединения данных, мы рекомендуем использовать единый запрос с оператором ОБЪЕДИНИТЬ ВСЕ, тщательно следя за согласованностью количества колонок, их наименований и типов данных во всех подзапросах. Применение фиктивных колонок и явное приведение типов помогут избежать распространенных ошибок и значительно упростят процесс разработки.

← На главную