Как правильно обходить Таблицу Значений из СКД и избегать ошибок сравнения типов в 1С?

Программист 1С v8.3 (Управляемые формы) 1С:Управление торговлей Управленческий учет Торговля и дистрибуция
← На главную

При работе с платформой 1С:Предприятие 8, особенно когда мы имеем дело с данными, полученными из Системы Компоновки Данных (СКД), часто возникает задача обхода таблиц значений. В процессе этой работы программисты-новички (и не только) сталкиваются с рядом типовых ошибок, связанных как с синтаксисом доступа к данным, так и с проблемами сравнения различных типов значений. Давайте подробно рассмотрим эти сложности и выясним, как их корректно преодолевать, основываясь на практическом примере с форума.

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


Схема = Элементы.СписокСтандартныйПоискНоменклатура.ПолучитьИсполняемуюСхемуКомпоновкиДанных;
Настройки = Элементы.СписокСтандартныйПоискНоменклатура.ПолучитьИсполняемыеНастройкиКомпоновкиДанных;
КомпоновщикМакета = Новый КомпоновщикМакетаКомпоновкиДанных;
МакетКомпоновки = КомпоновщикМакета.Выполнить(Схема, Настройки, , ,Тип("ГенераторМакетаКомпоновкиДанныхДляКоллекцииЗначений"));
ПроцессорКомпоновки = Новый ПроцессорКомпоновкиДанных;
ПроцессорКомпоновки.Инициализировать(МакетКомпоновки);
ПроцессорВывода = Новый ПроцессорВыводаРезультатаКомпоновкиДанныхВКоллекциюЗначений;
Результат = ПроцессорВывода.Вывести(ПроцессорКомпоновки);

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

Понимание исходной проблемы: некорректный доступ к колонкам

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


Для Каждого ЭлементаСписка из Результат ЦИКЛ
    Добавляем = Объект.Корзина.Добавить; // Синтаксическая ошибка: нужно Добавить()
    Если Результат.Продажи > 1 Тогда // Ошибка: Результат - это вся таблица, а не строка
        Добавляем.Номенклатура = Результат.Колонки.Наименование; // Ошибка: Результат - это вся таблица
    КонецЕсли;
КонецЦикла;

В этом фрагменте кода есть сразу несколько критических недочетов, которые приведут к ошибке:

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

Правильный подход к доступу к данным внутри цикла выглядит так:


Для Каждого ЭлементаСписка из Результат ЦИКЛ
    // Теперь ЭлементаСписка - это текущая строка таблицы значений
    Добавляем = Объект.Корзина.Добавить(); // Корректный вызов метода
    Если ЭлементаСписка.Продажи > 1 Тогда // Правильный доступ к колонке текущей строки
        Добавляем.Номенклатура = ЭлементаСписка.Наименование; // Правильный доступ к колонке текущей строки
    КонецЕсли;
КонецЦикла;

После исправления синтаксиса доступа к колонкам, автор темы столкнулся с новой проблемой, описанной в сообщении 7.

Диагностика и устранение ошибки сравнения типов

Когда мы исправили доступ к колонкам, могла возникнуть следующая ошибка:

Операции сравнения на больше-меньше допустимы только для значений совпадающих примитивных типов (Булево, Число, Строка, Дата) {ВнешняяОбработка.ПомощникПереноса.Форма.ФормаПодбора.Форма(7153)}: Если ЭлементаСписка.Продажи > 1 Тогда

Эта ошибка указывает на то, что мы пытаемся сравнить значение колонки ЭлементаСписка.Продажи (которое, по всей видимости, не является числом или имеет неопределенный тип) с числом 1. Причины могут быть разными:

  1. Неопределенное значение: Если для поля Продажи в СКД не нашлось данных для какой-либо строки, то в соответствующей ячейке таблицы значений может оказаться Неопределено. Сравнение Неопределено > 1 вызовет ошибку.
  2. NULL значение из базы данных: Аналогично Неопределено, если СКД возвращает значения NULL из базы данных, они также могут вызвать ошибку при прямом сравнении.
  3. Другой тип данных: Иногда, особенно при сложных запросах СКД, поле, которое должно быть числом, по какой-то причине может быть интерпретировано как строка (например, пустая строка ""). Сравнение "" > 1 также приведет к ошибке.

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

Подробное решение проблемы: безопасный обход и сравнение

Теперь, когда мы понимаем как правильно обращаться к колонкам и какие проблемы могут возникнуть с типами данных, давайте рассмотрим полноценное и надежное решение. Мы будем использовать функции ЗначениеЗаполнено() и ТипЗнч() для проверки значений перед их использованием и сравнением.


Для Каждого ЭлементаСписка из Результат ЦИКЛ
    // Шаг 1: Проверяем, заполнено ли значение в колонке "Продажи" и является ли оно числом
    Если ЗначениеЗаполнено(ЭлементаСписка.Продажи) И ТипЗнч(ЭлементаСписка.Продажи) = Тип("Число") Тогда
        // Шаг 2: Проверяем, заполнено ли значение в колонке "ВНаличииОстатокСкладаОтгрузки" и является ли оно числом
        Если ЗначениеЗаполнено(ЭлементаСписка.ВНаличииОстатокСкладаОтгрузки) И ТипЗнч(ЭлементаСписка.ВНаличииОстатокСкладаОтгрузки) = Тип("Число") Тогда
            // Шаг 3: Выполняем логику сравнения, когда оба значения гарантированно являются числами
            Если ЭлементаСписка.ВНаличииОстатокСкладаОтгрузки >= ЭлементаСписка.Продажи Тогда
                Добавляем = Объект.Корзина.Добавить(); // Добавляем новую строку в коллекцию "Корзина"
                
                // Шаг 4: Проверяем, заполнено ли Наименование перед использованием
                Если ЗначениеЗаполнено(ЭлементаСписка.Наименование) Тогда
                    // Ищем номенклатуру по наименованию. Преобразуем наименование в строку на всякий случай.
                    // Важно: НайтиПоНаименованию может быть неточным, если есть несколько номенклатур с одинаковым названием.
                    Добавляем.Номенклатура = Справочники.Номенклатура.НайтиПоНаименованию(Строка(ЭлементаСписка.Наименование));
                Иначе
                    // Обработка случая, когда наименование не заполнено, например, устанавливаем пустую ссылку
                    Добавляем.Номенклатура = Справочники.Номенклатура.ПустаяСсылка();
                КонецЕсли;
                
                Добавляем.Количество = ЭлементаСписка.Продажи;
                Добавляем.КоличествоУпаковок = ЭлементаСписка.Продажи;
            КонецЕсли;
        КонецЕсли;
    КонецЕсли;
КонецЦикла;

Разберем этот код по шагам:

  1. Для Каждого ЭлементаСписка из Результат ЦИКЛ: Мы начинаем итерацию по каждой строке таблицы значений Результат. Переменная ЭлементаСписка на каждой итерации будет представлять собой текущую строку.
  2. Если ЗначениеЗаполнено(ЭлементаСписка.Продажи) И ТипЗнч(ЭлементаСписка.Продажи) = Тип("Число") Тогда: Это критически важная проверка. Мы убеждаемся в двух вещах:
    • ЗначениеЗаполнено(ЭлементаСписка.Продажи): гарантирует, что значение в колонке Продажи не является Неопределено, NULL, пустой строкой, или нулем (для числовых типов).
    • ТипЗнч(ЭлементаСписка.Продажи) = Тип("Число"): явно проверяем, что тип значения является числовым. Это дает дополнительную гарантию от ошибок сравнения, если ЗначениеЗаполнено() для каких-то нечисловых типов возвращает Истину.
    Аналогичная проверка выполняется и для колонки ВНаличииОстатокСкладаОтгрузки.
  3. Если ЭлементаСписка.ВНаличииОстатокСкладаОтгрузки >= ЭлементаСписка.Продажи Тогда: Только после успешных проверок на заполненность и тип мы выполняем операцию сравнения. Теперь мы можем быть уверены, что сравниваем два числа.
  4. Добавляем = Объект.Корзина.Добавить();: Мы корректно добавляем новую строку в табличную часть или коллекцию значений Корзина объекта Объект и получаем ссылку на эту новую строку в переменную Добавляем.
  5. Добавляем.Номенклатура = Справочники.Номенклатура.НайтиПоНаименованию(Строка(ЭлементаСписка.Наименование));:
    • Мы используем функцию Строка() для явного преобразования значения из колонки Наименование в строку. Это полезно, если наименование по какой-то причине пришло не в строковом формате.
    • Метод Справочники.Номенклатура.НайтиПоНаименованию() ищет элемент справочника по полному наименованию. Важно учитывать, что этот метод может быть не всегда точным, если в справочнике есть несколько элементов с одинаковым наименованием или если наименование отличается регистром. Для более надежного поиска рекомендуется использовать поиск по коду или по уникальному идентификатору (GUID), если они доступны. Также мы добавили проверку ЗначениеЗаполнено(ЭлементаСписка.Наименование), чтобы избежать передачи пустого значения в метод поиска, что может привести к непредсказуемому поведению или ошибкам.
  6. Добавляем.Количество = ЭлементаСписка.Продажи; и Добавляем.КоличествоУпаковок = ЭлементаСписка.Продажи;: Мы присваиваем значения числовых колонок из таблицы СКД в соответствующие поля новой строки корзины.

Дополнительные рекомендации при работе с Таблицами Значений из СКД

Для обеспечения максимальной стабильности и надежности кода при работе с таблицами значений, полученными из СКД, рассмотрим дополнительные рекомендации:

  1. Всегда проверяйте тип и заполненность: Мы уже убедились, что это критически важно. Особенно тщательно проверяйте данные, которые будут использоваться в математических операциях, сравнениях или при поиске по ссылкам.
  2. Явное приведение типов: Если вы ожидаете число, но есть вероятность получить строковое представление числа, используйте Число(Значение). Если ожидаете строку, используйте Строка(Значение). Помните, что Число(Неопределено) вернет 0, а Число("") также вернет 0, что может быть не всегда желательным. В таких случаях сначала используйте ЗначениеЗаполнено() или ТипЗнч().
  3. Обработка неопределенных или NULL значений: В СКД поля, по которым нет данных, могут возвращать Неопределено или NULL (в зависимости от источника данных и настроек). Всегда предусматривайте логику обработки таких значений, чтобы ваш код не завершался ошибкой.
  4. Использование безопасных методов поиска: Метод НайтиПоНаименованию() является удобным, но не всегда надежным. Если возможно, используйте поиск по уникальным идентификаторам (например, НайтиПоКоду() или НайтиПоУникальномуИдентификатору()), чтобы избежать неоднозначности.
  5. Отладка и просмотр данных: При возникновении ошибок активно используйте отладчик 1С. Просматривайте значения переменных (ЭлементаСписка.Продажи, ТипЗнч(ЭлементаСписка.Продажи)) на каждой итерации цикла. Это поможет точно определить, какое значение или какой тип вызывает проблему.

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

← На главную