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

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

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

Разбор основной ошибки: Конкатенация против Параметров

Рассмотрим типичную ошибку, с которой столкнулся автор темы. Попытка вставить значение переменной напрямую в текст запроса через оператор "+" (конкатенацию) — это путь к неработающему коду и уязвимостям. Посмотрим на этот пример:

Текст = "... ГДЕ Ресурс1 = """ + ДатаДокумента + """ ";

Почему это не работает:

  1. Несоответствие типов: Когда вы прибавляете дату к строке, 1С неявно преобразует дату в строку формата пользователя (например, "02.03.2021 12:47:50"). Для СУБД (SQL) этот текст не является датой. Чтобы запрос понял, что это дата, она должна быть представлена в строго определенном формате 'ГГГГММДДЧЧММСС' или через функцию ДАТАВРЕМЯ().
  2. Сложность синтаксиса: Попытки угадать количество кавычек делают код нечитаемым и провоцируют синтаксические ошибки.

Правильное решение: Всегда используйте параметры запроса, обозначаемые символом &. Проанализируем верный подход:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	ДвиженияУФДляСписания.Период КАК ДатаУФ
    |ИЗ
    |	РегистрСведений.ДвиженияУФДляСписания КАК ДвиженияУФДляСписания
    |ГДЕ
    |	ДвиженияУФДляСписания.Ресурс1 = &ДатаДокумента";

Запрос.УстановитьПараметр("ДатаДокумента", Объект.Дата);
РезультатЗапроса = Запрос.Выполнить();

Использование метода УстановитьПараметр гарантирует, что платформа сама передаст значение в нужном для базы данных формате, сохранив тип Дата.

Проблема сопоставления типов: Ссылка против Строки

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

Важный нюанс: Ссылка — это уникальный идентификатор (GUID), а строка — это просто набор символов. Они никогда не будут равны. Если Ресурс3 в вашем регистре имеет тип Строка, то передача в параметр Объект.Организация (которая является ссылкой) приведет к пустому результату запроса.

Выясним, как поступить, если тип данных в регистре изменить нельзя:

  1. Если в регистре хранится строка (наименование), передавайте в параметр именно наименование: Запрос.УстановитьПараметр("Организация", Объект.Организация.Наименование);.
  2. Лучшая практика: Измените тип ресурса в метаданных на СправочникСсылка.Организации. Это обеспечит ссылочную целостность (если организацию переименуют, связь не разорвется).

Посмотрим, как выглядит ошибка "двойного поиска", упомянутая в обсуждении:

Запрос.УстановитьПараметр("ВыбРесурс3", Справочники.Организации.НайтиПоНаименованию(Организация));

Если переменная Организация уже содержит ссылку (например, получена из Объект.Организация), то повторный поиск по наименованию бессмыслен. Вы просто заставляете систему выполнять лишнюю работу, которая к тому же может вернуть не тот элемент, если в базе есть дубликаты с одинаковыми именами.

Как правильно обходить выборку запроса

После выполнения запроса мы получаем объект РезультатЗапроса. Чтобы прочитать данные, необходимо создать выборку. Проанализируем, как правильно работать с курсором выборки.

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

Сценарий 1: Если ожидается несколько записей


Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
    // Здесь мы обрабатываем каждую строку
    Сообщить("Найдена дата: " + Выборка.ДатаУФ);
КонецЦикла;

Сценарий 2: Если нужно проверить только наличие записи или получить одно значение


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

Важное замечание по коду из темы: Автор пытался присваивать значения переменным внутри цикла Пока Выборка.Следующий(), но при этом условия проверки выносил за пределы цикла. В таком случае в переменных останутся данные только от последней записи выборки. Если ваша задача — проверить наличие конкретной записи, лучше перенести логику внутрь цикла или использовать метод РезультатЗапроса.Пустой().

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

Если ваша цель — просто узнать, есть ли в регистре запись с такими параметрами, не нужно выбирать все поля и крутить цикл. Достаточно проверить результат на пустоту. Разберем наиболее производительный вариант кода:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ ПЕРВЫЕ 1
    |	1
    |ИЗ
    |	РегистрСведений.ДвиженияУФДляСписания КАК ДвиженияУФДляСписания
    |ГДЕ
    |	ДвиженияУФДляСписания.Ресурс1 = &Дата
    |	И ДвиженияУФДляСписания.Ресурс2 = &Номер
    |	И ДвиженияУФДляСписания.Ресурс3 = &Организация";

Запрос.УстановитьПараметр("Дата", Объект.Дата);
Запрос.УстановитьПараметр("Номер", Объект.Номер);
Запрос.УстановитьПараметр("Организация", Объект.Организация);

Если НЕ Запрос.Выполнить().Пустой() Тогда
    Объект.ДокументГотовДляРедактирования = Истина;
КонецЕсли;

Почему этот код лучше:

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

Для стабильной работы ваших выборок всегда придерживайтесь следующих правил:

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