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