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

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

При интеграции 1С со сторонними сервисами или интернет-площадками часто возникает задача идентификации контрагентов и их документов по переданным контактным данным. Рассмотрим распространенную ситуацию: во входящей таблице значений передаются идентификаторы строк и два контакта — основной (СамаПочта) и дополнительный (СамаПочта2). Наша цель — найти самый ранний заказ клиента, оформленный на партнера с указанным адресом (первым либо вторым), и вернуть корректный номер и дату этого документа.

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

Проанализируем исходный вариант запроса, с которым часто сталкиваются разработчики при первой реализации подобной выборки:


ВЫБРАТЬ
    Мейл.Представление КАК Мейл,
    МАКСИМУМ(ЗаказКлиента.Номер) КАК Номер,
    МИНИМУМ(ЗаказКлиента.Дата) КАК Дата
ИЗ
    Документ.ЗаказКлиента КАК ЗаказКлиента
    // Соединение с контактной информацией...
СГРУППИРОВАТЬ ПО
    Мейл.Представление

В такой конструкции кроются критические ошибки:

  1. Рассинхронизация номера и даты. Агрегатные функции МАКСИМУМ и МИНИМУМ в 1С рассчитываются независимо. Запрос вернет минимальную дату первого заказа, но номер будет выбран максимальный по алфавиту среди всех заказов данного партнера. В итоге данные документа окажутся искаженными.
  2. Дублирование логики для двух почтовых ящиков. Попытка создать параллельные пакеты запросов для СамаПочта и СамаПочта2 с последующим сложным сопоставлением сильно усложняет код и снижает производительность СУБД.
  3. Неэффективная фильтрация через списки значений. Передача параметров через В (&Представление) приводит к разрастанию текста результирующего SQL-запроса. Намного быстрее соединять данные напрямую через временную таблицу.

Архитектурное решение: нормализация входных данных

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

Вместо дублирования веток запроса под каждый e-mail нормализуем исходную таблицу значений с помощью конструкции ОБЪЕДИНИТЬ ВСЕ. Мы «развернем» строки таблицы так, чтобы каждому исходному идентификатору ИД соответствовала одна запись с адресом электронной почты, отсекая пустые строки. Это позволит единообразно искать заказы как по первому, так и по второму адресу.

Пошаговый алгоритм построения запроса

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

Пример оптимизированного запроса

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


ВЫБРАТЬ
    ВходныеДанные.ИД КАК ИД,
    ВходныеДанные.Тип КАК Тип,
    ВходныеДанные.СамаПочта КАК СамаПочта,
    ВходныеДанные.СамаПочта2 КАК СамаПочта2
ПОМЕСТИТЬ ВТ_Исходная
ИЗ
    &майл КАК ВходныеДанные
ИНДЕКСИРОВАТЬ ПО
    ИД
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ВТ_Исходная.ИД КАК ИД,
    ВТ_Исходная.СамаПочта КАК АдресЭП
ПОМЕСТИТЬ ВТ_РазвернутыеАдреса
ИЗ
    ВТ_Исходная КАК ВТ_Исходная
ГДЕ
    ВТ_Исходная.СамаПочта <> ""

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ
    ВТ_Исходная.ИД,
    ВТ_Исходная.СамаПочта2
ИЗ
    ВТ_Исходная КАК ВТ_Исходная
ГДЕ
    ВТ_Исходная.СамаПочта2 <> ""

ИНДЕКСИРОВАТЬ ПО
    АдресЭП,
    ИД
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    Адреса.ИД КАК ИД,
    МИНИМУМ(Заказ.Дата) КАК МинДата
ПОМЕСТИТЬ ВТ_МинимальныеДаты
ИЗ
    ВТ_РазвернутыеАдреса КАК Адреса
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Партнеры.КонтактнаяИнформация КАК КИ
        ПО Адреса.АдресЭП = КИ.Представление
            И (КИ.Вид = ЗНАЧЕНИЕ(Справочник.ВидыКонтактнойИнформации.EmailПартнера))
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.ЗаказКлиента КАК Заказ
        ПО (Заказ.Партнер = КИ.Ссылка)
            И (Заказ.Проведен)
СГРУППИРОВАТЬ ПО
    Адреса.ИД

ИНДЕКСИРОВАТЬ ПО
    ИД,
    МинДата
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    МинДаты.ИД КАК ИД,
    МИНИМУМ(Заказ.Ссылка) КАК ПервыйЗаказСсылка
ПОМЕСТИТЬ ВТ_ПервыеЗаказы
ИЗ
    ВТ_МинимальныеДаты КАК МинДаты
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_РазвернутыеАдреса КАК Адреса
        ПО МинДаты.ИД = Адреса.ИД
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Партнеры.КонтактнаяИнформация КАК КИ
        ПО Адреса.АдресЭП = КИ.Представление
            И (КИ.Вид = ЗНАЧЕНИЕ(Справочник.ВидыКонтактнойИнформации.EmailПартнера))
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.ЗаказКлиента КАК Заказ
        ПО (Заказ.Партнер = КИ.Ссылка)
            И (Заказ.Дата = МинДаты.МинДата)
            И (Заказ.Проведен)
СГРУППИРОВАТЬ ПО
    МинДаты.ИД

ИНДЕКСИРОВАТЬ ПО
    ПервыйЗаказСсылка
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    Исходная.СамаПочта КАК СамаПочта,
    Исходная.СамаПочта2 КАК СамаПочта2,
    Исходная.Тип КАК Тип,
    Исходная.ИД КАК ИД,
    ЕСТЬNULL(ЗаказКлиента.Номер, "") КАК Номер,
    ЕСТЬNULL(ЗаказКлиента.Дата, ДАТАВРЕМЯ(1, 1, 1)) КАК Дата
ИЗ
    ВТ_Исходная КАК Исходная
        ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ПервыеЗаказы КАК ПервыеЗаказы
        ПО Исходная.ИД = ПервыеЗаказы.ИД
        ЛЕВОЕ СОЕДИНЕНИЕ Документ.ЗаказКлиента КАК ЗаказКлиента
        ПО ПервыеЗаказы.ПервыйЗаказСсылка = ЗаказКлиента.Ссылка
УПОРЯДОЧИТЬ ПО
    Исходная.СамаПочта

Особенности настройки параметров на встроенном языке

Обратим внимание на передачу параметров. Благодаря тому, что адреса уже находятся внутри временной таблицы, отпадает необходимость передавать громоздкие списки СписокЗаказов и СписокЗаказов2 через метод УстановитьПараметр:


Запрос = Новый Запрос;
Запрос.Текст = ТекстЗапроса;
Запрос.УстановитьПараметр("майл", Параметры.ТЗВыгрузки);

РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
    // Обработка полученных данных
КонецЦикла;

Такой подход полностью исключает рассинхронизацию полей номера и даты, корректно обрабатывает случаи отсутствия второго e-mail адреса и обеспечивает высокую скорость выполнения запроса за счет использования индексов по полям связей.

← На главную