Почему не работает запрос к только что созданному документу в 1С и как правильно получить его ссылку

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

В процессе разработки в среде 1С:Предприятие часто возникает задача программного создания документов и их последующей привязки к другим объектам базы данных в качестве основания. Начинающие разработчики нередко сталкиваются с ситуацией, когда только что записанный в базу данных документ не удается найти с помощью последующего запроса, а отладчик выдает неочевидные ошибки вроде "Ошибка чтения значения". Разберем подробно причины такого поведения системы, проанализируем ошибки в коде и рассмотрим правильные архитектурные решения.

Главная архитектурная ошибка: избыточный запрос к базе данных

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

Такой подход является грубейшим антипаттерном программирования в 1С по следующим причинам:

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

Выясним причину, почему запрос не нужен. Как только мы вызываем метод Записать() у объекта источника, платформа 1С автоматически инициализирует его ссылку в базе данных. Эта ссылка сразу становится доступной в свойстве объекта Ссылка. Мы можем мгновенно перенести её в документ-приемник без каких-либо обращений к базе данных.

Посмотрим на пример правильного и лаконичного кода, полностью исключающего создание запроса:


// Создаем новый документ расчетов в памяти
ДокОснование = Документы.ДокументРасчетовСКонтрагентом.СоздатьДокумент();
ДокОснование.Дата = ДатаДок;
ДокОснование.Организация = Справочники.Организации.НайтиПоКоду("000000001");
ДокОснование.Контрагент = Справочники.Контрагенты.НайтиПоКоду(Код);

// Записываем документ в базу данных, Ссылка формируется автоматически
ДокОснование.Записать(); 

// Напрямую заполняем документ-приемник полученной ссылкой
ДокументСФ.ДокументОснование = ДокОснование.Ссылка;
ДокументСФ.Записать();

Продвинутый сценарий: получение ссылки до фактической записи

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

Разберем по шагам, как получить ссылку нового объекта заранее:


// 1. Создаем объект документа в оперативной памяти
ДокОснование = Документы.ДокументРасчетовСКонтрагентом.СоздатьДокумент();

// 2. Генерируем новую уникальную ссылку для этого типа документа
НоваяСсылка = Документы.ДокументРасчетовСКонтрагентом.ПолучитьСсылку(Новый УникальныйИдентификатор);

// 3. Устанавливаем сгенерированную ссылку нашему объекту в памяти
ДокОснование.УстановитьСсылкуНового(НоваяСсылка);

// 4. Теперь мы можем использовать переменную НоваяСсылка где угодно
ДокументСФ.ДокументОснование = НоваяСсылка;

// 5. Заполняем остальные реквизиты и записываем документ
ДокОснование.Дата = ДатаДок;
ДокОснование.Контрагент = СправочникКонтрагент;
ДокОснование.Записать();

ДокументСФ.Записать();

При использовании метода УстановитьСсылкуНового() в момент вызова Записать() система создаст запись в СУБД именно с тем уникальным идентификатором (GUID), который мы определили заранее. Это гарантирует целостность связей между документами.

Почему исходный запрос работал некорректно? Разбор синтаксических ошибок

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

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


ГДЕ
    ДокументРасчетовСКонтрагентом.Дата = Дата
    И ДокументРасчетовСКонтрагентом.Контрагент = Контрагент

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

  1. Отсутствие амперсанда (&) перед именами параметров. В языке запросов 1С все внешние параметры, передаваемые через метод УстановитьПараметр(), в тексте запроса обязательно должны предваряться символом &. Без него компилятор запросов воспринимает слова Дата и Контрагент как обращение к полям таблиц, а не к внешним переменным, что приводит к непредсказуемому результату или синтаксической ошибке.
  2. Сравнение дат без учета времени. Тип данных Дата в 1С всегда содержит время с точностью до секунды. Если в базе данных документ записан со временем 12:00:00, а в параметр запроса передается дата со временем 00:00:00, простое равенство = вернет ложь.

Правильный вариант условий в запросе должен выглядеть следующим образом:


ГДЕ
    НАЧАЛОПЕРИОДА(ДокументРасчетовСКонтрагентом.Дата, ДЕНЬ) = НАЧАЛОПЕРИОДА(&Дата, ДЕНЬ)
    И ДокументРасчетовСКонтрагентом.Контрагент = &Контрагент

Использование функции НАЧАЛОПЕРИОДА позволяет привести обе даты к началу дня (к времени 00:00:00), тем самым нивелируя разницу в часах, минутах и секундах записи документа.

Разгадка "Ошибки чтения значения" в табло отладчика

Часто разработчики пугаются сообщения "Ошибка чтения значения" или "Переменная не определена" при анализе выборки запроса в отладчике. Выясним причину этого явления.

После выполнения конструкции РезультатЗапроса = Запрос.Выполнить().Выбрать(); мы получаем объект типа ВыборкаИзРезультатаЗапроса. Важно понимать, что в этот момент выборка находится в "холодном" состоянии — указатель еще не позиционирован ни на одной строке результата.

Попытка обратиться к полям выборки в табло (например, написать РезультатЗапроса.Ссылка) до первого вызова метода Следующий() всегда будет выдавать ошибку чтения. Платформа еще не знает, из какой именно строки считывать данные.

Правильный алгоритм отладки выборки выглядит так:

  1. Установите точку останова внутри цикла Пока РезультатЗапроса.Следующий() Цикл.
  2. Только после того, как выполнение кода остановится внутри цикла, открывайте табло отладки.
  3. В этот момент метод Следующий() уже позиционировал указатель на первую запись, и вы сможете без ошибок прочитать значения полей: РезультатЗапроса.Ссылка, РезультатЗапроса.Организация и т.д.

Если отладчик проскакивает цикл полностью, это означает лишь одно — запрос вернул пустой результат. Проверьте правильность передачи параметров и наличие соответствующих записей в базе данных.

Опасность дублирования данных (Учетный риск)

При реализации автоматического создания документов в обработках загрузки (например, из файлов Excel, XML или сторонних API) кроется серьезный учетный риск. Если просто записывать новый документ при каждом запуске алгоритма, в информационной базе неизбежно возникнет лавинообразное появление дублей.

При повторном проведении или повторном импорте одной и той же операции у вас будут создаваться новые и новые документы расчетов. Это приведет к "расползанию" аналитики взаиморасчетов на счетах учета (60, 62), неверному зачету авансов и хаосу в Книге покупок и Книге продаж.

Золотое правило разработки импорта в 1С: Перед тем как создавать новый объект, всегда выполняйте проверку на его существование в базе данных. Создавать объект нужно только тогда, когда поиск не дал результатов.

Рассмотрим законченный шаблон кода, который решает эту проблему корректно и безопасно:


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

ЗапросПоиска.УстановитьПараметр("Дата", ДатаДок);
ЗапросПоиска.УстановитьПараметр("Контрагент", НайденаяСсылкаКонтрагента);
ЗапросПоиска.УстановитьПараметр("Организация", СсылкаОрганизации);

ВыборкаПоиска = ЗапросПоиска.Выполнить().Выбрать();

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

// Теперь безопасно привязываем ссылку к документу-приемнику
ДокументСФ.ДокументОснование = СсылкаНаДокумент;
Если ЗначениеЗаполнено(СсылкаНаДоговор) Тогда
    ДокументСФ.ДоговорКонтрагента = СсылкаНаДоговор;
КонецЕсли;
ДокументСФ.Записать();

Обратите внимание на исправление скрытого бага самоприсваивания, который часто встречается у новичков: ДокументСФ.ДоговорКонтрагента = ДокументСФ.ДоговорКонтрагента;. Данное выражение бессмысленно. В приведенном выше шаблоне мы заменили его на корректное присвоение из внешней переменной СсылкаНаДоговор, что гарантирует правильное заполнение аналитики учета НДС.

← На главную