Как в запросе 1С правильно соединить таблицу продаж с периодическим регистром сведений на определенную дату?

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

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

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

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

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

  1. Неправильное понимание механизма виртуальной таблицы СрезПоследних. Основная задача среза последних — вернуть самое свежее актуальное значение ресурса на переданную дату или ранее. Если мы запрашиваем срез на 01.05.2016, а последняя запись была внесена 01.04.2016, система абсолютно корректно с точки зрения логики среза вернет запись за апрель. В нашей же задаче требуется жесткое условие: «если записи на 01.05 нет, то должно быть пусто». Срез последних по своей природе предназначен для получения непрерывного среза состояния, а не проверки дискретного факта записи.
  2. Превращение внешнего соединения во внутреннее через секцию ГДЕ. Проанализируем типичную ошибку в тексте запроса:
    
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КомментарииДляОтчетаПоМенеджерам КАК Комментарии
        ПО ТаблицаПродаж.Номенклатура = Комментарии.Номенклатура
    ГДЕ
        Комментарии.Период = &НачалоПериода
    
    Когда мы накладываем условие фильтрации на присоединенную правую таблицу в предложении ГДЕ, СУБД отсекает все записи, где правая часть содержит NULL (так как условие NULL = &НачалоПериода дает ЛОЖЬ). В результате ЛЕВОЕ СОЕДИНЕНИЕ фактически превращается во ВНУТРЕННЕЕ, и из отчета полностью выпадают все продажи, к которым еще не успели добавить комментарий.

Способ 1. Соединение с реальной таблицей регистра и перенос условия в секцию ПО

Самое простое и эффективное решение — отказаться от виртуальной таблицы СрезПоследних и обратиться напрямую к реальной таблице регистра сведений. При этом условие на точное совпадение даты необходимо перенести непосредственно в условия соединения ПО.

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


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

Разберем, почему это работает:

  1. Мы используем реальную физическую таблицу РегистрСведений.КомментарииДляОтчета, где хранятся только фактически созданные записи.
  2. Условие Комментарии.Период = &НачалоПериода находится в блоке ПО. Если для строки продаж в регистре нет комментария за указанную дату, условие соединения не срабатывает, но сама строка продаж не удаляется — в колонке Комментарий формируется значение NULL.
  3. Важный нюанс времени: поле Период в платформе 1С всегда содержит время. Если периодичность регистра установлена в значение «Месяц», платформа автоматически нормализует дату к началу месяца (например, 01.05.2016 00:00:00). Если же регистр периодичен по дням или по позициям регистратора, для сравнения дат следует использовать функцию НАЧАЛОПЕРИОДА либо оператор МЕЖДУ:

ПО ПродажиОбороты.Номенклатура = Комментарии.Номенклатура
    И Комментарии.Период МЕЖДУ &НачалоПериода И &КонецПериода

Способ 2. Предварительная выборка комментариев через временную таблицу

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

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


ВЫБРАТЬ
    Комментарии.Номенклатура КАК Номенклатура,
    Комментарии.ЗаказПокупателя КАК ЗаказПокупателя,
    Комментарии.Комментарий КАК Комментарий
ПОМЕСТИТЬ ВТ_КомментарииЗаПериод
ИЗ
    РегистрСведений.КомментарииДляОтчета КАК Комментарии
ГДЕ
    Комментарии.Период = &НачалоПериода
ИНДЕКСИРОВАТЬ ПО
    Номенклатура,
    ЗаказПокупателя
;

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

В этом сценарии секция ГДЕ используется совершенно корректно, так как фильтрация выполняется на этапе формирования временной таблицы, а не при итоговом левом соединении с продажами.

Способ 3. Использование СрезаПоследних с проверкой даты фактической записи

Бывают задачи, когда менеджер мог оставить комментарий не строго 1 мая, а, например, 15 мая, и этот комментарий должен действовать до конца месяца, но ни в коем случае не подтягиваться из прошлого месяца (апреля). В таком случае можно использовать таблицу СрезПоследних, передав в нее дату окончания периода, но в условиях соединения дополнительно ограничить период снизу.

Посмотрим на пример такого соединения:


ВЫБРАТЬ
    Продажи.Номенклатура КАК Номенклатура,
    КомментарииСрез.Комментарий КАК Комментарий
ИЗ
    ВТ_Продажи КАК Продажи
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КомментарииДляОтчета.СрезПоследних(&КонецПериода, ) КАК КомментарииСрез
        ПО Продажи.Номенклатура = КомментарииСрез.Номенклатура
            И КомментарииСрез.Период МЕЖДУ &НачалоПериода И &КонецПериода

Проанализируем логику этого условия:

  1. Таблица СрезПоследних(&КонецПериода) возвращает самое последнее состояние на конец анализируемого месяца.
  2. Условие И КомментарииСрез.Период МЕЖДУ &НачалоПериода И &КонецПериода проверяет, когда именно была сделана эта последняя запись.
  3. Если запись была создана в текущем месяце — она успешно соединяется. Если же запись была сделана в предыдущих месяцах (например, в апреле), условие в ПО не выполняется, и результатом соединения будет NULL, что в точности соответствует исходным требованиям.
← На главную