При построении отчетов по продажам часто возникает задача вывести не просто текущую цену товара, а ту цену, которая действовала именно в момент совершения сделки. Обычное соединение с виртуальной таблицей СрезПоследних часто приводит к ошибкам: либо запрос возвращает пустые значения, либо показывает одну и ту же цену на весь период отчета, игнорируя изменения цен внутри этого периода. Разберем, почему так происходит, и как составить правильный запрос.
Рассмотрим типичную ситуацию: вы пытаетесь соединить таблицу оборотов продаж с виртуальной таблицей цен. Основная проблема заключается в том, что виртуальная таблица СрезПоследних без параметров выдает срез данных на текущий момент (на дату сеанса). Если же в параметры таблицы передать &КонецПериода, она выдаст срез на одну конкретную дату для всех строк запроса.
Проанализируем ситуацию: если цена товара изменилась 15-го числа, а отчет строится за весь месяц, то при использовании СрезПоследних(&КонецПериода) все продажи — и от 1-го, и от 30-го числа — получат цену, актуальную на конец месяца. Это искажает данные в управленческом учете. Попытка добавить условие связи Продажи.Период >= Цены.Период к виртуальной таблице СрезПоследних также является ошибкой, так как это нарушает логику работы индексов и может привести к получению нескольких записей или неверных данных.
Для решения этой задачи в 1С выработан классический алгоритм, который работает быстро и гарантирует точность данных. Мы разберем его по шагам, используя механизм временных таблиц.
Первым делом нам необходимо выбрать все факты продаж за интересующий нас период. Важно сгруппировать данные по номенклатуре, характеристике и периоду (дате), чтобы уменьшить количество записей для дальнейшей обработки.
ВЫБРАТЬ
ПродажиОбороты.Номенклатура КАК Номенклатура,
ПродажиОбороты.Характеристика КАК Характеристика,
ПродажиОбороты.Период КАК Период,
СУММА(ПродажиОбороты.КоличествоОборот) КАК Количество,
СУММА(ПродажиОбороты.СуммаОборот) КАК Выручка
ПОМЕСТИТЬ ВТ_Продажи
ИЗ
РегистрНакопления.Продажи.Обороты(&НачПериода, &КонПериода, День, ) КАК ПродажиОбороты
СГРУППИРОВАТЬ ПО
ПродажиОбороты.Номенклатура,
ПродажиОбороты.Характеристика,
ПродажиОбороты.Период;
Важный момент: Мы используем периодичность День (или Запись, если нужно время до секунды) в виртуальной таблице оборотов, чтобы иметь привязку к конкретному моменту времени для получения цены.
Теперь нам нужно выяснить, в какой именно день была установлена та цена, которая действовала на момент каждой продажи. Для этого мы соединяем наши продажи с физической таблицей регистра сведений ЦеныНоменклатуры.
ВЫБРАТЬ
ВТ_Продажи.Номенклатура КАК Номенклатура,
ВТ_Продажи.Характеристика КАК Характеристика,
ВТ_Продажи.Период КАК ПериодПродажи,
МАКСИМУМ(ЦеныНоменклатуры.Период) КАК ПериодЦены
ПОМЕСТИТЬ ВТ_ДатыЦен
ИЗ
ВТ_Продажи КАК ВТ_Продажи
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры КАК ЦеныНоменклатуры
ПО ВТ_Продажи.Номенклатура = ЦеныНоменклатуры.Номенклатура
И ВТ_Продажи.Характеристика = ЦеныНоменклатуры.Характеристика
И ЦеныНоменклатуры.ВидЦен = &ВидЦен
И ЦеныНоменклатуры.Период <= ВТ_Продажи.Период
СГРУППИРОВАТЬ ПО
ВТ_Продажи.Номенклатура,
ВТ_Продажи.Характеристика,
ВТ_Продажи.Период;
Разберем, что здесь происходит: мы берем продажу и ищем все записи в регистре цен, которые были сделаны до или в день этой продажи (ЦеныНоменклатуры.Период <= ВТ_Продажи.Период). С помощью функции МАКСИМУМ мы находим дату самой последней установки цены перед продажей.
На финальном этапе мы соединяем таблицу продаж с таблицей найденных дат, а затем — снова с физической таблицей регистра цен, чтобы получить само числовое значение цены.
ВЫБРАТЬ
ВТ_Продажи.Номенклатура КАК Номенклатура,
ВТ_Продажи.Характеристика КАК Характеристика,
ВТ_Продажи.Период КАК Период,
ВТ_Продажи.Количество КАК Количество,
ВТ_Продажи.Выручка КАК Выручка,
ЕСТЬNULL(ЦеныНоменклатуры.Цена, 0) КАК ЦенаУчетная,
ВТ_Продажи.Количество * ЕСТЬNULL(ЦеныНоменклатуры.Цена, 0) КАК СуммаСебестоимость
ИЗ
ВТ_Продажи КАК ВТ_Продажи
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ДатыЦен КАК ВТ_ДатыЦен
ПО ВТ_Продажи.Номенклатура = ВТ_ДатыЦен.Номенклатура
И ВТ_Продажи.Характеристика = ВТ_ДатыЦен.Характеристика
И ВТ_Продажи.Период = ВТ_ДатыЦен.ПериодПродажи
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры КАК ЦеныНоменклатуры
ПО ВТ_ДатыЦен.Номенклатура = ЦеныНоменклатуры.Номенклатура
И ВТ_ДатыЦен.Характеристика = ЦеныНоменклатуры.Характеристика
И ВТ_ДатыЦен.ПериодЦены = ЦеныНоменклатуры.Период
И (ЦеныНоменклатуры.ВидЦен = &ВидЦен);
Номенклатура, Характеристика, Период). Это критически важно для производительности. В 1С это делается командой ИНДЕКСИРОВАТЬ ПО.ЕСТЬNULL. Если для товара никогда не устанавливалась цена указанного типа, запрос вернет NULL, что приведет к пустоте в вычислениях. ЕСТЬNULL(Цена, 0) решит эту проблему.ГДЕ после соединения. Всегда включайте его в условие связи ПО или в параметры виртуальной таблицы, чтобы отсечь лишние данные как можно раньше.Проанализировав данный подход, мы видим, что он позволяет избежать "зависаний" запроса и некорректных данных, которые часто возникают при попытке "скрестить" виртуальные таблицы с историческими данными в одном простом соединении.