При разработке отчетов или обработок в 1С мы часто сталкиваемся с необходимостью динамического формирования запросов и передачи в них различных параметров. Одним из распространенных сценариев является использование оператора В для отбора данных по списку ссылок. Однако возникают вопросы, как правильно передавать параметры, если список ссылок может быть пустым, а также как избежать ошибок при сравнении дат или некорректном использовании полей ссылок в табличных частях.
В этой статье мы подробно рассмотрим различные подходы к решению этих задач, анализируя типичные ошибки и предлагая эффективные способы их исправления.
Один из основных вопросов, возникших у нас, касался передачи параметра в условие ГДЕ ... Ссылка В (&МасОтгруженоЗаказчику), когда массив МасОтгруженоЗаказчику оказывается пустым. Попытка передать в такой параметр просто пустую ссылку, например, Документы.ХХХ_НарядЗадание.ПустаяСсылка(), приведет к ошибке, так как оператор В ожидает не одиночное значение, а коллекцию (массив, список значений, таблицу значений).
Правильным подходом в такой ситуации является передача именно пустого объекта типа Массив. В этом случае, если массив пуст, условие В корректно вернет пустой результат, не вызывая ошибок выполнения запроса. Такое поведение соответствует логике: если нет элементов для сравнения, то и результат отбора будет пустым.
Рассмотрим, как это реализовать:
// Предположим, у нас есть переменная МасОтгруженоЗаказчику, которая может быть пустым массивом.
// Инициализация МасОтгруженоЗаказчику должна быть выполнена, например, как Новый Массив.
// Если в него ничего не было добавлено, он останется пустым.
Если МасОтгруженоЗаказчику.Количество() > 0 Тогда
ЗапросПоТоварам.УстановитьПараметр("МасОтгруженоЗаказчику", МасОтгруженоЗаказчику);
Иначе
// Если массив пуст, передаем пустой объект Массив
ЗапросПоТоварам.УстановитьПараметр("МасОтгруженоЗаказчику", Новый Массив);
КонецЕсли;
Этот способ является наиболее чистым и предсказуемым, так как пустой массив в условии В логически означает "нет элементов для отбора", что соответствует ожидаемому поведению. Именно такой подход рекомендуем использовать.
В качестве менее предпочтительного варианта можно передать массив, содержащий одну пустую ссылку. Это позволит запросу выполниться без ошибок, так как параметр будет установлен как коллекция, но требует дополнительного внимания и понимания потенциальных последствий.
Посмотрим на пример:
Если МасОтгруженоЗаказчику.Количество() > 0 Тогда
ЗапросПоТоварам.УстановитьПараметр("МасОтгруженоЗаказчику", МасОтгруженоЗаказчику);
Иначе
// Передаем массив, содержащий только пустую ссылку
ЗапросПоТоварам.УстановитьПараметр("МасОтгруженоЗаказчику", Новый Массив(Документы.ХХХ_НарядЗадание.ПустаяСсылка()));
КонецЕсли;
Важное замечание: Этот метод может привести к неожиданным результатам, если в базе данных существуют записи, у которых поле Ссылка (или другое поле, по которому идет отбор) действительно содержит пустую ссылку. В таком случае эти записи будут отобраны запросом, что, вероятно, не является желаемым поведением и может привести к некорректным данным в отчете. Поэтому, если вы не уверены в отсутствии таких "пустых" ссылок в ваших данных, этот вариант следует использовать с осторожностью. Всегда предпочтительнее передавать именно пустой массив (Новый Массив).
Для более сложной логики, когда наличие или отсутствие массива должно влиять на структуру всего запроса (например, исключать целые секции ОБЪЕДИНИТЬ ВСЕ или удалять часть условий ГДЕ), наиболее эффективным и гибким решением является динамическое изменение текста запроса. Этот подход позволяет строить текст запроса буквально "по кирпичикам" в зависимости от входных данных. Для быстрой сборки и переноса текста запроса в программный код подойдёт конвертер запросов 1С.
Рассмотрим сценарий, где у нас есть объединение двух подзапросов, и каждый из них зависит от своего массива ссылок. Если один из массивов пуст, мы можем исключить соответствующий подзапрос целиком из общего текста, чтобы он не выполнялся.
Пример реализации, где мы формируем текст запроса на основе наличия элементов в массивах:
// Шаг 1: Инициализируем и заполняем массивы ссылок.
// Эти массивы будут содержать ссылки на документы "ХХХ_НарядЗадание",
// которые соответствуют условиям "ОтгруженоЗаказчиком" или "ОтгруженоИсполнителем"
// из табличной части документа "ХХХ_ВнутреннееПеремещение".
// Массив для отгрузок заказчиком
МасОтгруженоЗаказчиком = Новый Массив;
ЗапросОтгруженоЗаказчиком = Новый Запрос;
ЗапросОтгруженоЗаказчиком.Текст = "
|ВЫБРАТЬ
| ХХХ_ВнутреннееПеремещениеХХХ_Основание_НарядЗадание.ДокументСсылка КАК ДокументСсылка
|ИЗ
| Документ.ХХХ_ВнутреннееПеремещение.ХХХ_Основание_НарядЗадание КАК ХХХ_ВнутреннееПеремещениеХХХ_Основание_НарядЗадание
|ГДЕ
| ХХХ_ВнутреннееПеремещениеХХХ_Основание_НарядЗадание.ОтгруженоЗаказчиком = ИСТИНА";
РезультатЗаказчиком = ЗапросОтгруженоЗаказчиком.Выполнить();
Если Не РезультатЗаказчиком.Пустой() Тогда
МасОтгруженоЗаказчиком = РезультатЗаказчиком.Выгрузить().ВыгрузитьКолонку("ДокументСсылка");
КонецЕсли;
// Массив для отгрузок исполнителем
МасОтгруженоИсполнителем = Новый Массив;
ЗапросОтгруженоИсполнителем = Новый Запрос;
ЗапросОтгруженоИсполнителем.Текст = "
|ВЫБРАТЬ
| ХХХ_ВнутреннееПеремещениеХХХ_Основание_НарядЗадание.ДокументСсылка КАК ДокументСсылка
|ИЗ
| Документ.ХХХ_ВнутреннееПеремещение.ХХХ_Основание_НарядЗадание КАК ХХХ_ВнутреннееПеремещениеХХХ_Основание_НарядЗадание
|ГДЕ
| ХХХ_ВнутреннееПеремещениеХХХ_Основание_НарядЗадание.ОтгруженоИсполнителем = ИСТИНА";
РезультатИсполнителем = ЗапросОтгруженоИсполнителем.Выполнить();
Если Не РезультатИсполнителем.Пустой() Тогда
МасОтгруженоИсполнителем = РезультатИсполнителем.Выгрузить().ВыгрузитьКолонку("ДокументСсылка");
КонецЕсли;
// Шаг 2: Динамически формируем основной текст запроса
ОсновнойЗапрос = Новый Запрос;
ОсновнойТекстЗапроса = "";
Если МасОтгруженоЗаказчиком.Количество() > 0 Тогда
ОсновнойТекстЗапроса = ОсновнойТекстЗапроса + "
|ВЫБРАТЬ
| ХХХ_НарядЗаданиеОтгруженоЗаказчиком.Ссылка КАК Ссылка,
| ХХХ_НарядЗаданиеОтгруженоЗаказчиком.Ссылка КАК НарядЗадание,
| ХХХ_НарядЗаданиеОтгруженоЗаказчиком.Ссылка.Менеджер КАК Менеджер,
| ХХХ_НарядЗаданиеОтгруженоЗаказчиком.Ссылка.ЗаказКлиента КАК ЗаказКлиента,
| ХХХ_НарядЗаданиеОтгруженоЗаказчиком.НомерСтроки КАК НомерСтроки,
| ХХХ_НарядЗаданиеОтгруженоЗаказчиком.Номенклатура КАК Номенклатура,
| ХХХ_НарядЗаданиеОтгруженоЗаказчиком.Ссылка.ВидРабот КАК ВидРабот,
| ХХХ_НарядЗаданиеОтгруженоЗаказчиком.Количество КАК Количество,
| ХХХ_НарядЗаданиеОтгруженоЗаказчиком.Номенклатура.ЕдиницаИзмерения.Наименование КАК ЕдиницаИзмеренияНаименование,
| ХХХ_НарядЗаданиеОтгруженоЗаказчиком.Номенклатура.ЕдиницаИзмерения.Наименование КАК ЕдиницаИзмеренияМестНаименование,
| ХХХ_НарядЗаданиеОтгруженоЗаказчиком.Номенклатура.ЕдиницаИзмерения.Код КАК ЕдиницаИзмеренияКодПоОКЕИ,
| ХХХ_НарядЗаданиеОтгруженоЗаказчиком.Количество КАК КоличествоМест,
| 0 КАК Цена,
| 1 КАК КоличествоВОдномМесте,
| 1 КАК ID,
| ХХХ_НарядЗаданиеОтгруженоЗаказчиком.Получатель КАК Получатель,
| ХХХ_НарядЗаданиеОтгруженоЗаказчиком.Комментарий КАК Комментарий,
| ХХХ_НарядЗаданиеОтгруженоЗаказчиком.Чертеж КАК Чертеж,
| ХХХ_НарядЗаданиеОтгруженоЗаказчиком.СотрудникСклада КАК СотрудникСклада,
| ХХХ_НарядЗаданиеОтгруженоЗаказчиком.ПолученоСкладом КАК ПолученоСкладом,
| ХХХ_НарядЗаданиеОтгруженоЗаказчиком.ДатаСклада КАК ДатаСклада
|ИЗ
| Документ.ХХХ_НарядЗадание.ОтгруженоЗаказчиком КАК ХХХ_НарядЗаданиеОтгруженоЗаказчиком
|ГДЕ
| ХХХ_НарядЗаданиеОтгруженоЗаказчиком.Ссылка В(&МасОтгруженоЗаказчиком)
| И ХХХ_НарядЗаданиеОтгруженоЗаказчиком.ДатаСклада = &ТекДата";
ОсновнойЗапрос.УстановитьПараметр("МасОтгруженоЗаказчиком", МасОтгруженоЗаказчиком);
КонецЕсли;
Если МасОтгруженоИсполнителем.Количество() > 0 Тогда
Если ОсновнойТекстЗапроса <> "" Тогда
ОсновнойТекстЗапроса = ОсновнойТекстЗапроса + "
|ОБЪЕДИНИТЬ ВСЕ
|";
КонецЕсли;
ОсновнойТекстЗапроса = ОсновнойТекстЗапроса + "
|ВЫБРАТЬ
| ХХХ_НарядЗаданиеОтгруженоИсполнителем.Ссылка,
| ХХХ_НарядЗаданиеОтгруженоИсполнителем.Ссылка,
| ХХХ_НарядЗаданиеОтгруженоИсполнителем.Ссылка.Менеджер,
| ХХХ_НарядЗаданиеОтгруженоИсполнителем.Ссылка.ЗаказКлиента,
| ХХХ_НарядЗаданиеОтгруженоИсполнителем.НомерСтроки,
| ХХХ_НарядЗаданиеОтгруженоИсполнителем.Номенклатура,
| ХХХ_НарядЗаданиеОтгруженоИсполнителем.Ссылка.ВидРабот,
| ХХХ_НарядЗаданиеОтгруженоИсполнителем.Количество,
| ХХХ_НарядЗаданиеОтгруженоИсполнителем.Номенклатура.ЕдиницаИзмерения.Наименование,
| ХХХ_НарядЗаданиеОтгруженоИсполнителем.Номенклатура.ЕдиницаИзмерения.Наименование,
| ХХХ_НарядЗаданиеОтгруженоИсполнителем.Номенклатура.ЕдиницаИзмерения.Код,
| ХХХ_НарядЗаданиеОтгруженоИсполнителем.Количество,
| 0,
| 1,
| 1,
| ХХХ_НарядЗаданиеОтгруженоИсполнителем.Получатель,
| ХХХ_НарядЗаданиеОтгруженоИсполнителем.Комментарий,
| ХХХ_НарядЗаданиеОтгруженоИсполнителем.Чертеж,
| ХХХ_НарядЗаданиеОтгруженоИсполнителем.СотрудникСклада,
| ХХХ_НарядЗаданиеОтгруженоИсполнителем.ПолученоСкладом,
| ХХХ_НарядЗаданиеОтгруженоИсполнителем.ДатаСклада
|ИЗ
| Документ.ХХХ_НарядЗадание.ОтгруженоИсполнителем КАК ХХХ_НарядЗаданиеОтгруженоИсполнителем
|ГДЕ
| ХХХ_НарядЗаданиеОтгруженоИсполнителем.Ссылка В(&МасОтгруженоИсполнителем)
| И ХХХ_НарядЗаданиеОтгруженоИсполнителем.ДатаСклада = &ТекДата";
ОсновнойЗапрос.УстановитьПараметр("МасОтгруженоИсполнителем", МасОтгруженоИсполнителем);
КонецЕсли;
// Шаг 3: Выполняем запрос, если текст не пустой
Если ОсновнойТекстЗапроса <> "" Тогда
ОсновнойЗапрос.Текст = ОсновнойТекстЗапроса;
ОсновнойЗапрос.УстановитьПараметр("ТекДата", ТекущаяДата()); // Параметр даты всегда устанавливаем
ВыборкаСтрокТовары = ОсновнойЗапрос.Выполнить().Выбрать();
КоличествоСтрок = ВыборкаСтрокТовары.Количество();
Пока ВыборкаСтрокТовары.Следующий() Цикл
// Здесь размещается код для обработки полученных данных
КонецЦикла;
Иначе
// Обработка случая, когда оба массива пусты и запрос не сформирован
КоличествоСтрок = 0;
// Можно вывести сообщение или выполнить другие действия
КонецЕсли;
Этот подход позволяет избежать выполнения ненужных частей запроса, что повышает производительность и делает код более гибким. Мы формируем текст запроса только из тех частей, для которых есть данные, а также устанавливаем параметры только для тех частей, которые были включены в запрос. Это наиболее предпочтительный метод для сложных, динамически изменяющихся запросов. Для проверки и отладки динамических запросов прямо из кода можно использовать консоль запросов с автоопределением параметров.
Еще одна распространенная проблема, с которой мы сталкиваемся, связана со сравнением дат в запросах. Если в базе данных поле, например, ДатаСклада, хранится с точностью до дня (то есть имеет тип "Дата" без компонента времени, фактически 01.01.2023 0:00:00), а в запросе мы сравниваем его с параметром &ТекДата, который устанавливается как ТекущаяДата() (эта функция всегда возвращает текущие дату и время), сравнение ДатаСклада = &ТекДата может не дать ожидаемого результата.
Причина в том, что ТекущаяДата(), возвращая, например, 01.01.2023 10:30:00, не будет равна полю ДатаСклада со значением 01.01.2023 0:00:00. Чтобы корректно сравнивать даты, игнорируя время, мы можем использовать встроенные функции языка запросов 1С:
НАЧАЛОДНЯ(&ПараметрДата): Эта функция обрезает компонент времени до начала дня (00:00:00). Мы можем использовать ее для сравнения поля даты с параметром.КОНЕЦДНЯ(&ПараметрДата): Эта функция устанавливает компонент времени на конец дня (23:59:59). Ее часто используют в паре с НАЧАЛОДНЯ() для создания диапазона дат, чтобы захватить все записи за определенный день.Посмотрим, как можно скорректировать условие ГДЕ в тексте запроса:
// Если поле ДатаСклада хранится без времени или нам не важно время
// Сравнение только по дню:
"ГДЕ
| ХХХ_НарядЗаданиеСкладЗакачика.ДатаСклада = НАЧАЛОДНЯ(&ТекДата)"
// Или для отбора за весь день, включая все время суток:
"ГДЕ
| ХХХ_НарядЗаданиеСкладЗакачика.ДатаСклада МЕЖДУ НАЧАЛОДНЯ(&ТекДата) И КОНЕЦДНЯ(&ТекДата)"
// В коде 1С мы устанавливаем параметр как обычно, с полным значением даты и времени:
ОсновнойЗапрос.УстановитьПараметр("ТекДата", ТекущаяДата());
Использование НАЧАЛОДНЯ() или диапазона МЕЖДУ НАЧАЛОДНЯ() И КОНЕЦДНЯ() позволяет гарантировать, что сравнение будет происходить корректно только по дате, независимо от компонента времени, содержащегося в параметре или поле базы данных. Это предотвратит потерю данных из-за неточного сравнения.
Наконец, давайте разберем еще один важный аспект при работе с запросами, касающийся ссылок в табличных частях документов. В языке запросов 1С при обращении к табличной части документа существуют два ключевых поля, которые могут вызывать путаницу и приводить к некорректным результатам отбора:
<code>ТабличнаяЧасть.Ссылка: Это поле возвращает ссылку на строку табличной части. Каждая строка табличной части в 1С имеет свой собственный уникальный идентификатор и, соответственно, свою уникальную ссылку, которая отличается от ссылки на сам документ.<code>ТабличнаяЧасть.ДокументСсылка: Это поле возвращает ссылку на документ-владелец, которому принадлежит данная строка табличной части. То есть, это ссылка на родительский документ.В нашем случае, когда мы формируем массивы ссылок на документы типа "ХХХ_НарядЗадание" (предполагая, что Документ.ХХХ_ВнутреннееПеремещение.ХХХ_Основание_НарядЗадание содержит ссылку на документ "ХХХ_НарядЗадание"), а не на отдельные строки табличных частей, необходимо использовать именно ДокументСсылка. Изначально в коде из сообщений использовалось ХХХ_ВнутреннееПеремещениеХХХ_Основание_НарядЗадание.Ссылка, что могло приводить к получению ссылок на строки табличной части документа "ХХХ_ВнутреннееПеремещение", а не на ссылки на сам документ "ХХХ_НарядЗадание", который являлся основанием.
Корректное использование поля для получения ссылки на документ-владелец:
// Для получения ссылок на документы-владельцы (например, документы "ХХХ_НарядЗадание",
// которые являются основанием для строк табличной части "ХХХ_ВнутреннееПеремещение")
ЗапросОтгруженоЗаказчиком.Текст = "
|ВЫБРАТЬ
| ХХХ_ВнутреннееПеремещениеХХХ_Основание_НарядЗадание.ДокументСсылка КАК ДокументСсылка
|ИЗ
| Документ.ХХХ_ВнутреннееПеремещение.ХХХ_Основание_НарядЗадание КАК ХХХ_ВнутреннееПеремещениеХХХ_Основание_НарядЗадание
|ГДЕ
| ХХХ_ВнутреннееПеремещениеХХХ_Основание_НарядЗадание.ОтгруженоЗаказчиком = ИСТИНА";
Рез = ЗапросОтгруженоЗаказчиком.Выполнить();
Если Не Рез.Пустой() Тогда
// Выгружаем колонку с ссылками на документы-владельцы
МасОтгруженоЗаказчиком = Рез.Выгрузить().ВыгрузитьКолонку("ДокументСсылка");
КонецЕсли;
Всегда внимательно проверяйте, какой тип ссылки вам необходим в конкретной задаче, чтобы избежать логических ошибок в отборе данных. Использование ДокументСсылка гарантирует, что мы работаем с самими документами, а не с их составными частями.
Мы рассмотрели несколько ключевых аспектов правильной работы с параметрами запросов в 1С. Передача пустого массива для оператора В является стандартным и надежным способом обработки пустых списков. Динамическое формирование текста запроса предлагает максимальную гибкость и производительность для сложных сценариев. Наконец, корректное сравнение дат и понимание различий между Ссылка и ДокументСсылка в табличных частях помогают избежать распространенных ошибок и обеспечивают точность ваших запросов.
Применяя эти подходы, мы сможем создавать более надежные, производительные и легко поддерживаемые решения в 1С, эффективно справляясь с динамическими условиями и особенностями работы с данными.