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