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