При работе с языком запросов 1С программисты часто сталкиваются со странным поведением платформы: если в запросе создать колонку с помощью константного выражения (литерала) и явно ограничить её тип через ВЫРАЗИТЬ, то после выгрузки результата в ТаблицуЗначений квалификаторы числа (разрядность и точность) оказываются «пустыми» (равными нулю). При этом для строковых колонок всё работает корректно. Рассмотрим подробнее, почему так происходит, и разберем способы решения этой задачи.
Проанализируем классический пример кода, который вызывает недоумение. Допустим, мы создаем две колонки: числовую и строковую, явно задавая их параметры:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ВЫРАЗИТЬ(1 КАК ЧИСЛО(10, 2)) КАК ПолеЧисло,
| ВЫРАЗИТЬ("""" КАК СТРОКА(10)) КАК ПолеСтрока";
Результат = Запрос.Выполнить().Выгрузить();
// Проверяем квалификаторы
Сообщить("Разрядность числа: " + Результат.Колонки.ПолеЧисло.ТипЗначения.КвалификаторыЧисла.Разрядность);
Сообщить("Длина строки: " + Результат.Колонки.ПолеСтрока.ТипЗначения.КвалификаторыСтроки.Длина);
После выполнения этого кода в консоли мы увидим неожиданный результат: для строки длина будет равна 10, а для числа разрядность окажется 0. Это означает, что платформа создала колонку с типом Число неограниченной длины, полностью проигнорировав указание ЧИСЛО(10, 2) в тексте запроса.
Разберем ситуацию на уровне взаимодействия платформы 1С и СУБД (например, MS SQL Server). Если посмотреть технологический журнал или трассировку SQL, мы увидим, что запрос транслируется абсолютно корректно. Платформа отправляет в сторону базы данных инструкцию CAST(1 AS NUMERIC(10, 2)). Это означает, что сама СУБД возвращает данные в правильном формате.
Проблема возникает на этапе десериализации результата и заполнения объекта ТаблицаЗначений. Платформа 1С применяет следующую логику:
Важный нюанс: если вы используете ВЫРАЗИТЬ для реального поля из таблицы базы данных (например, ВЫРАЗИТЬ(Документ.Сумма КАК ЧИСЛО(15, 2))), платформа зачастую сохраняет квалификаторы, так как у колонки есть «опора» в виде метаданных. В случае с литералами (константами) такая опора отсутствует.
Если вам критически важно иметь типизированную таблицу значений (например, для последующей передачи во временную таблицу другого запроса), наиболее надежным способом будет создание «правильной» структуры ТЗ вручную и копирование данных. Однако, чтобы не использовать медленные циклы Для Каждого, можно применить метод выгрузки колонок через массивы.
Рассмотрим алгоритм реализации:
// 1. Получаем результат запроса
РезультатЗапроса = Запрос.Выполнить();
ТЗ_Исходная = РезультатЗапроса.Выгрузить();
// 2. Создаем новую таблицу с нужными типами
ТЗ_Типизированная = Новый ТаблицаЗначений;
Квалификаторы = Новый КвалификаторыЧисла(10, 2);
ОписаниеТипаЧисло = Новый ОписаниеТипов("Число", Квалификаторы);
ТЗ_Типизированная.Колонки.Добавить("ПолеЧисло", ОписаниеТипаЧисло);
// Добавляем остальные колонки...
// 3. Быстро переносим данные без циклов
МассивДанных = ТЗ_Исходная.ВыгрузитьКолонку("ПолеЧисло");
ТЗ_Типизированная.ЗагрузитьКолонку(МассивДанных, "ПолеЧисло");
Этот метод работает значительно быстрее, чем построчное заполнение, особенно на больших объемах данных (более 100 000 строк).
Существует еще один способ, позволяющий избежать создания второй таблицы. Мы можем добавить в существующую таблицу новую колонку с нужным типом, перенести в неё данные и удалить старую. Проанализируем, как это сделать эффективно:
ТЗ новую колонку с временным именем и правильным ОписаниеТипов.ЗагрузитьКолонку(), передавая в него данные из ВыгрузитьКолонку() старой колонки.Это позволяет сохранить объект ТаблицаЗначений и избежать лишнего расхода памяти на создание полной копии данных.
Если ваша конечная цель — использовать результат этого запроса в другом запросе, то выгружать данные в ТаблицуЗначений на стороне встроенного языка вообще не стоит. Выясним, как это сделать внутри одного сеанса работы с базой данных.
Посмотрим на пример использования МенеджераВременныхТаблиц:
МенеджерВТ = Новый МенеджерВременныхТаблиц;
Запрос.МенеджерВременныхТаблиц = МенеджерВТ;
Запрос.Текст =
"ВЫБРАТЬ
| ВЫРАЗИТЬ(1 КАК ЧИСЛО(10, 2)) КАК ПолеЧисло
|ПОМЕСТИТЬ ВТ_Данные";
Запрос.Выполнить();
В этом случае внутри SQL-сервера будет создана временная таблица, где поле ПолеЧисло будет иметь строго заданный тип numeric(10, 2). При последующих обращениях к ВТ_Данные в рамках этого же менеджера типизация сохранится. Проблема потери квалификаторов возникает только в момент передачи данных из РезультатЗапроса в объект ТаблицаЗначений на стороне 1С:Предприятия.
Выясненная особенность платформы не является ошибкой в чистом виде, а представляет собой специфику реализации динамических типов данных. Для большинства повседневных задач (вывод в отчет, расчеты в коде) отсутствие квалификаторов у числа в ТЗ не играет роли, так как точность самих данных при выгрузке сохраняется (дробная часть не отсекается).
Однако, если вы планируете передавать эту таблицу обратно в запрос как параметр, обязательно используйте ручную типизацию колонок (Способ 1), иначе система может некорректно сопоставить типы данных, что приведет к ошибкам или существенному замедлению работы запроса из-за неявного приведения типов в SQL.