Ошибка «Переменная не определена»: как правильно передать значение свойства в параметр запроса 1С

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

В процессе разработки на платформе 1С:Предприятие начинающие программисты часто сталкиваются с ситуацией, когда код, который логически кажется верным (или работает в Консоли запросов), выдает ошибку при исполнении. Одной из самых распространенных является ошибка «Переменная не определена». Рассмотрим эту проблему подробно на примере работы со свойствами контрагентов (механизм дополнительных свойств в старых конфигурациях, таких как УТ 10.3, УПП 1.3 или самописных решениях).

Суть проблемы

Разберем ситуацию: нам необходимо отобрать контрагентов, у которых установлено определенное свойство (например, статус «VIP»). В Консоли запросов мы просто выбираем нужное значение параметра мышкой из списка. Однако при переносе текста запроса в модуль, мы пишем следующий код:


Запрос.Текст = "... ГДЕ ЗначенияСвойствОбъектов.Свойство = &Свойство";
Запрос.УстановитьПараметр("Свойство", VIP); // Ошибка здесь

Система выдает ошибку: «Переменная не определена (VIP)». Почему это происходит? Дело в том, что для компилятора VIP — это просто набор символов. Программа не знает, что вы подразумеваете конкретный элемент плана видов характеристик или справочника. Переменная VIP нигде не объявлена и ей не присвоено значение (ссылка на объект базы данных).

Решение 1: Получение ссылки на свойство

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

Рассмотрим несколько способов получения этой ссылки, от простых (но ненадежных) к правильным.

Способ А: Поиск по Наименованию или Коду (Не рекомендуется, но работает)

В обсуждении было предложено использовать методы поиска. Это самый быстрый способ заставить код работать, но он несет риски: если пользователь переименует свойство или при обмене данными изменится код, ваша обработка перестанет работать.


// Поиск по наименованию
СвойствоVIP = ПланыВидовХарактеристик.СвойстваОбъектов.НайтиПоНаименованию("VIP");

// ИЛИ поиск по коду (как сделал автор темы)
СвойствоVIP = ПланыВидовХарактеристик.СвойстваОбъектов.НайтиПоКоду("06828");

Запрос.УстановитьПараметр("Свойство", СвойствоVIP);

Способ Б: Предопределенные элементы (Рекомендуется)

Если свойство «VIP» является критически важным для логики работы программы, правильнее всего создать его как Предопределенный элемент в конфигураторе. Тогда к нему можно обращаться напрямую через имя, и никакой поиск не потребуется.


// Обращение к предопределенному элементу
Запрос.УстановитьПараметр("Свойство", ПланыВидовХарактеристик.СвойстваОбъектов.VIP);

Решение 2: Оптимизация архитектуры (Избавляемся от запроса в цикле)

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

Важно: Никогда не выполняйте запросы внутри цикла! Это многократно увеличивает нагрузку на базу данных (Проблема N+1 запросов).

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

Посмотрим на правильный и оптимизированный пример кода:


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

Теперь в результате запроса у нас будет колонка ЭтоVIP, и нам не нужно делать дополнительные проверки в цикле.

Решение 3: Правильная работа с датами

Еще один важный момент, затронутый в обсуждении, — расчет сроков оплаты. Автор использовал прибавление секунд:

ПредельнаяДатаОплаты = ДатаОплаты + 15552000; // 180 дней

Это плохая практика, так как:

  1. Код становится нечитаемым (что это за число?).
  2. Не учитываются високосные года и разная длина месяцев.

В 1С есть встроенные механизмы для работы с датами. Рассмотрим более корректные варианты:

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


// Добавляем ровно 6 месяцев
ПредельнаяДатаОплаты = ДобавитьМесяц(ДатаОплаты, 6); 

Вариант 2. Использование констант времени
Если нужно добавить именно дни, лучше использовать явное умножение, чтобы код был понятен.


СуткиВСекундах = 86400; // 60 * 60 * 24
ПредельнаяДатаОплаты = ДатаОплаты + (180 * СуткиВСекундах);

Итог и рекомендации

Чтобы избежать подобных ошибок в будущем, следуйте простым правилам:

  1. Все переменные, которые вы передаете в Запрос.УстановитьПараметр, должны быть определены в коде до момента вызова метода.
  2. Для поиска ссылок на объекты метаданных (свойства, статусы) старайтесь использовать предопределенные элементы. Если это невозможно — используйте НайтиПоНаименованию, но будьте готовы к тому, что имя могут изменить.
  3. Избегайте выполнения запросов внутри циклов. Старайтесь получать все данные одним большим запросом, используя соединения таблиц.
  4. При работе с переменными выборки не переопределяйте переменную результата.

Пример правильного перебора выборки:


РезультатЗапроса = Запрос.Выполнить();
// Создаем новую переменную для выборки, а не перезаписываем РезультатЗапроса
Выборка = РезультатЗапроса.Выбрать(); 

Пока Выборка.Следующий() Цикл
    // Обработка данных
КонецЦикла;
← На главную