Как в запросе 1С вывести контрагента без Email, если у него оформлен определенный вид договора

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

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

При решении этой задачи разработчики часто сталкиваются с тем, что контрагенты без почты «исчезают» из результатов запроса или строки в документе начинают дублироваться. В этой статье мы подробно проанализируем причины такого поведения СУБД, разберем типичные ошибки и рассмотрим несколько вариантов правильного решения задачи.

Анализ исходной проблемы и типичных ошибок в запросах

Проанализируем ситуацию на примере исходного запроса, с которым столкнулся автор темы. Попытки наложить условие «выводить контрагента, если заполнен Email ИЛИ если вид договора равен "Услуга"» часто приводят к неработающему коду. Выясним причину, разобрав основные ошибки.

Ошибка №1: Несовместимость типов в конструкции ВЫБОР

В попытках вернуть признак валидности адреса разработчики часто пишут следующую конструкцию:


ВЫБОР 
    КОГДА ВыбДокумент.ДоговорКонтрагента.ВидВзаиморасчетов = &Договор И КонтрагентыКонтактнаяИнформация.АдресЭП = "" 
        ТОГДА ИСТИНА 
    ИНАЧЕ КонтрагентыКонтактнаяИнформация.АдресЭП 
КОНЕЦ КАК АдресЭП

Это грубая ошибка языка запросов 1С. В рамках одной колонки запроса СУБД не может возвращать значения разных типов. В ветке ТОГДА возвращается Булево (ИСТИНА), а в ветке ИНАЧЕ — Строка (АдресЭП). Такой запрос либо завершится аварийно с ошибкой несовместимости типов, либо приведет к непредсказуемому поведению при попытке СУБД неявно преобразовать типы данных.

Ошибка №2: Некорректная обработка значений NULL при левом соединении

Когда мы соединяем документ со справочником контактной информации с помощью ЛЕВОГО СОЕДИНЕНИЯ, СУБД пытается найти запись с адресом для каждого контрагента. Если у контрагента вообще нет ни одной записи электронной почты в базе, то в полях присоединяемой таблицы (включая АдресЭП) вернется специальное пустое значение — NULL.

В языке SQL и запросах 1С любое прямое сравнение с NULL (например, АдресЭП = "" или НЕ АдресЭП = "") возвращает результат ЛОЖЬ (точнее, "Неизвестно"). В результате контрагенты, у которых физически отсутствует запись Email в базе данных, полностью отсекаются условиями фильтрации в секции ГДЕ.

Ошибка №3: Дублирование строк (картезианское произведение)

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

ПО ВыбДокумент.Контрагент = КонтрагентыКонтактнаяИнформация.Ссылка

то СУБД сопоставит документу все имеющиеся у контрагента виды контактной информации. В результате одна строка документа превратится в три или четыре строки в выборке (по одной строке на телефон, Email и адрес). Чтобы этого избежать, условие фильтрации по типу контактной информации нужно накладывать непосредственно в секции связи.

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

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

---

Способ 1. Исправление классического запроса (Legacy-подход)

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

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

  1. Связываем основную таблицу документов с табличной частью контактной информации через ЛЕВОЕ СОЕДИНЕНИЕ.
  2. В секцию ПО (параметры связи) обязательно добавляем жесткий фильтр по типу контактной информации: АдресЭлектроннойПочты. Это защитит запрос от дублирования строк.
  3. Для корректной обработки отсутствующих адресов (значений NULL) оборачиваем поле в функцию ЕСТЬNULL(..., "").
  4. В секции ГДЕ прописываем комбинированное условие: нам подходят либо контрагенты с заполненным Email, либо любые контрагенты (даже без Email), если у них выбран договор с нужным видом взаиморасчетов.

Посмотрим на пример реализации такого запроса:


Функция ПоискКонтрагента(ВидДокумента)
    
    Запрос = Новый Запрос;
    Запрос.УстановитьПараметр("НачалоПериода", НачалоДня(Объект.ДатаНач));
    Запрос.УстановитьПараметр("КонецПериода", КонецДня(Объект.ДатаКон));
    Запрос.УстановитьПараметр("ДоговорУслуга", Объект.ДоговорУслуга); // Передаем нужный вид договора в параметр
    
    Запрос.Текст = 
    "ВЫБРАТЬ
    |	РассылкаПоЭлектроннойПочтеТЧСчета.Контрагент КАК Контрагент
    |ПОМЕСТИТЬ ВТ_ИсключаемыеКонтрагенты
    |ИЗ
    |	Документ.РассылкаПоЭлектроннойПочте.ТЧСчета КАК РассылкаПоЭлектроннойПочтеТЧСчета
    |ГДЕ
    |	РассылкаПоЭлектроннойПочтеТЧСчета.Отправлять = ИСТИНА
    |	И НЕ РассылкаПоЭлектроннойПочтеТЧСчета.Ссылка.ПометкаУдаления
    |	И РассылкаПоЭлектроннойПочтеТЧСчета.Ссылка.Дата МЕЖДУ &НачалоПериода И &КонецПериода
    |;
    |
    |////////////////////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ
    |	ВыбДокумент.Ссылка КАК Ссылка,
    |	ВыбДокумент.Контрагент КАК Контрагент,
    |	ЕСТЬNULL(КонтрагентыКонтактнаяИнформация.АдресЭП, """") КАК АдресЭП
    |ИЗ
    |	Документ." + ВидДокумента + " КАК ВыбДокумент
    |	
    |	// Левое соединение строго по типу контактной информации ""АдресЭлектроннойПочты""
    |	ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Контрагенты.КонтактнаяИнформация КАК КонтрагентыКонтактнаяИнформация
    |		ПО ВыбДокумент.Контрагент = КонтрагентыКонтактнаяИнформация.Ссылка
    |		И КонтрагентыКонтактнаяИнформация.Тип = ЗНАЧЕНИЕ(Перечисление.ТипыКонтактнойИнформации.АдресЭлектроннойПочты)
    |ГДЕ
    |	ВыбДокумент.Дата МЕЖДУ &НачалоПериода И &КонецПериода
    |	И НЕ ВыбДокумент.ПометкаУдаления
    |	
    |	// Исключаем контрагентов, которые уже обработаны (находятся во временной таблице)
    |	И НЕ ВыбДокумент.Контрагент В
    |				(ВЫБРАТЬ
    |					ВТ_ИсключаемыеКонтрагенты.Контрагент
    |				ИЗ
    |					ВТ_ИсключаемыеКонтрагенты КАК ВТ_ИсключаемыеКонтрагенты)
    |	
    |	// Ключевое условие: либо Email заполнен, либо договор относится к виду ""Услуга""
    |	И (ЕСТЬNULL(КонтрагентыКонтактнаяИнформация.АдресЭП, """") <> """"
    |		ИЛИ ВыбДокумент.ДоговорКонтрагента.ВидВзаиморасчетов = &ДоговорУслуга)";
    
    Возврат Запрос.Выполнить().Выбрать();
    
КонецФункции

Проанализируем работу условия в секции ГДЕ. Если у контрагента заполнен Email, то выражение ЕСТЬNULL(КонтрагентыКонтактнаяИнформация.АдресЭП, "") <> "" возвращает ИСТИНА, и строка попадает в результат запроса независимо от договора. Если Email не заполнен (значение в базе отсутствует или равно пустой строке), первая часть условия возвращает ЛОЖЬ, но СУБД проверяет вторую часть условия через оператор ИЛИ. Если договор документа соответствует виду договора из параметра &ДоговорУслуга, строка все равно попадет в результат выборки.

---

Способ 2. Запрос к регистру сведений контактной информации (Современный стандарт БСП)

В современных конфигурациях 1С, построенных на базе Библиотеки стандартных подсистем (БСП), таких как 1С:ERP, 1С:КА, 1С:УТ 11, 1С:Бухгалтерия 3.0, контактная информация хранится в независимом регистре сведений РегистрСведений.КонтактнаяИнформация. Прямое обращение к табличным частям справочников считается устаревшим подходом.

Рассмотрим более производительный и архитектурно верный вариант построения запроса с использованием регистра сведений. Это позволяет избежать неявных левых соединений и ускорить работу на СУБД:


// Пример запроса с использованием регистра сведений контактной информации БСП
Запрос = Новый Запрос;
Запрос.УстановитьПараметр("НачалоПериода", НачалоПериода);
Запрос.УстановитьПараметр("КонецПериода", КонецПериода);
Запрос.УстановитьПараметр("ДоговорУслуга", ДоговорУслуга);
// Рекомендуется указывать конкретный вид контактной информации (например, Email контрагента)
Запрос.УстановитьПараметр("ВидКИ", Справочники.ВидыКонтактнойИнформации.EmailКонтрагента);

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

Посмотрим на преимущества этого метода. Во-первых, обращение к плоской таблице регистра сведений выполняется быстрее, чем к табличной части справочника. Во-вторых, использование параметра &ВидКИ (ссылка на предопределенный элемент справочника ВидыКонтактнойИнформации) гарантирует, что мы выберем строго один конкретный вид почты (например, «Почта для отправки счетов»), полностью исключая дубли строк.

---

Способ 3. Использование программного интерфейса БСП (Без прямых запросов)

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

Разберем, как получить Email контрагентов с помощью общего модуля УправлениеКонтактнойИнформацией:


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

// Получаем контактную информацию пакетным методом БСП
ТаблицаАдресов = УправлениеКонтактнойИнформацией.КонтактнаяИнформацияОбъектов(
    СписокКонтрагентов, 
    Перечисления.ТипыКонтактнойИнформации.АдресЭлектроннойПочты
);

// Теперь мы можем легко сопоставить данные в коде
Для Каждого СтрокаТЧ Из Объект.ТабличнаяЧасть Цикл
    
    // Ищем полученный Email в таблице адресов
    ПараметрыОтбора = Новый Структура("Объект", СтрокаТЧ.Контрагент);
    НайденныеСтроки = ТаблицаАдресов.НайтиСтроки(ПараметрыОтбора);
    
    ТекущийEmail = "";
    Если НайденныеСтроки.Количество() > 0 Тогда
        ТекущийEmail = НайденныеСтроки[0].Представление;
    КонецЕсли;
    
    // Проверяем условия бизнес-логики
    ДоговорУслуга = (СтрокаТЧ.Договор.ВидВзаиморасчетов = Объект.ДоговорУслуга);
    
    Если ЗначениеЗаполнено(ТекущийEmail) ИЛИ ДоговорУслуга Тогда
        // Выполняем заполнение или отправку
        СтрокаТЧ.АдресЭП = ТекущийEmail;
        СтрокаТЧ.ДобавлятьВДокумент = ИСТИНА;
    Иначе
        СтрокаТЧ.ДобавлятьВДокумент = ЛОЖЬ;
    КонецЕсли;
    
КонецЦикла;

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

---

Резюме и рекомендации

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

← На главную