К статье
Практический материал

Разработка и Конфигурация · 10 мин

Как получить курс валюты на дату документа в запросе 1С

Как в запросе 1С получить курс валюты на дату документа: виртуальная таблица СрезПоследних, соединение по периоду, типовые ошибки и примеры кода запроса.

Курсы валют в конфигурациях 1С хранятся в периодическом регистре сведений «КурсыВалют», где каждая запись привязана к конкретной дате. Документ же может быть проведён в любой день, и курс на эту дату в регистре напрямую может отсутствовать — загрузка курсов выполняется не ежедневно. Поэтому простое соединение таблицы документов с регистром по равенству дат не работает: нужно получать последнее известное значение курса на дату документа.

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

Как устроено хранение курсов и почему простое соединение не работает

Регистр сведений «КурсыВалют» — периодический, с периодичностью «в пределах секунды» или «в пределах дня» в зависимости от конфигурации. Измерение регистра — валюта, ресурсы — курс и кратность. Запись появляется только тогда, когда курс загружен или введён вручную: если в выходные загрузка не выполнялась, записей за эти дни нет.

Из-за этого запрос вида ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КурсыВалют ПО Курсы.Период = Документ.Дата вернёт NULL для всех документов, даты которых не совпали с датами загрузки. Даже при совпадении дат возникает вторая проблема: поле «Дата» документа содержит время, а период регистра обычно указывает на начало дня, поэтому равенство не выполняется даже в день загрузки курса.

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

Вариант 1: СрезПоследних, когда дата одна и известна заранее

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

ВЫБРАТЬ

Курсы.Валюта КАК Валюта,

Курсы.Курс КАК Курс,

Курсы.Кратность КАК Кратность

ИЗ

РегистрСведений.КурсыВалют.СрезПоследних(&Период, ) КАК Курсы

Параметр передаётся из кода: Запрос.УстановитьПараметр("Период", ДокументОбъект.Дата);. Если нужен курс только одной валюты, добавьте отбор во второй параметр виртуальной таблицы: СрезПоследних(&Период, Валюта = &Валюта) — это ускорит выборку.

Учтите, что в типовых конфигурациях на БСП вместо прямого обращения к регистру часто используют функцию РаботаСКурсамиВалют.ПолучитьКурсВалюты(Валюта, Дата) — она возвращает структуру с курсом и кратностью и корректно обрабатывает взаимные курсы. Внутри запроса эту функцию вызвать нельзя, поэтому для запросов остаётся срез или соединение.

Вариант 2: курс на дату каждого документа через соединение и группировку

Когда в выборке много документов с разными датами, виртуальная таблица не подходит — параметр у неё один. В этом случае соединяют таблицу документов с физической таблицей регистра по условию неравенства, а затем группировкой выбирают максимальный период, не превышающий дату документа.

ВЫБРАТЬ

Реализация.Ссылка КАК Документ,

Реализация.Дата КАК ДатаДокумента,

Реализация.Валюта КАК Валюта,

МАКСИМУМ(Курсы.Период) КАК ПериодКурса

ПОМЕСТИТЬ ВТ_ПериодыКурсов

ИЗ

Документ.РеализацияТоваровУслуг КАК Реализация

ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КурсыВалют КАК Курсы

ПО Курсы.Валюта = Реализация.Валюта

И Курсы.Период <= Реализация.Дата

ГДЕ

Реализация.Проведен

СГРУППИРОВАТЬ ПО

Реализация.Ссылка,

Реализация.Дата,

Реализация.Валюта

;

ВЫБРАТЬ

Периоды.Документ КАК Документ,

Курсы.Курс КАК Курс,

Курсы.Кратность КАК Кратность

ИЗ

ВТ_ПериодыКурсов КАК Периоды

ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КурсыВалют КАК Курсы

ПО Курсы.Валюта = Периоды.Валюта

И Курсы.Период = Периоды.ПериодКурса

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

Порядок действий
  1. 1
    Соедините таблицу документов с регистром КурсыВалют по валюте и условию Период <= ДатаДокумента
  2. 2
    Сгруппируйте по документу и вычислите МАКСИМУМ(Курсы.Период)
  3. 3
    Вторым соединением по валюте и найденному периоду получите Курс и Кратность
  4. 4
    Проверьте документы, где курс равен NULL — для них загрузите курсы за нужный период

Учёт кратности и пересчёт суммы в рубли

Курс без кратности непригоден для пересчёта: для большинства валют кратность равна 1, но для валют с низким номиналом курс указывается за 10 или 100 единиц. Формула пересчёта суммы из валюты в рубли: СуммаРуб = СуммаВал * Курс / Кратность.

ВЫБРАТЬ

Периоды.Документ КАК Документ,

Реализация.СуммаДокумента * Курсы.Курс / Курсы.Кратность КАК СуммаРуб

ИЗ

ВТ_ПериодыКурсов КАК Периоды

ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КурсыВалют КАК Курсы

ПО Курсы.Валюта = Периоды.Валюта

И Курсы.Период = Периоды.ПериодКурса

ЛЕВОЕ СОЕДИНЕНИЕ Документ.РеализацияТоваровУслуг КАК Реализация

ПО Реализация.Ссылка = Периоды.Документ

Во избежание деления на NULL (когда курс не найден) оберните выражение в ВЫБОР КОГДА Курсы.Курс ЕСТЬ NULL ТОГДА 0 ИНАЧЕ ... КОНЕЦ или используйте ЕСТЬNULL для курса и кратности. Деление на ноль в запросе вызовет ошибку выполнения, поэтому кратность стоит принудительно заменять на 1 при отсутствии данных.

Типовые ошибки и как их избежать

Самая частая ошибка — соединение по равенству дат без учёта времени в поле «Дата» документа. Вторая по распространённости — использование СрезПоследних с параметром-константой в отчёте, где документы имеют разные даты: курс получится одинаковым для всех строк, что незаметно исказит итоги.

Третья группа ошибок связана с производительностью. Соединение по неравенству без отбора по валюте заставляет платформу перебирать весь регистр для каждого документа. Всегда включайте условие по валюте в соединение, а при больших объёмах ограничивайте выборку документов периодом отчёта.

Типичные ошибки
01
Соединение по Курсы.Период = Документ.Дата — из-за времени в дате и пропусков в загрузке курс почти всегда NULL
02
СрезПоследних с одной датой для набора документов — все строки получают один и тот же курс
03
Игнорирование кратности — суммы по валютам с кратностью 10 или 100 завышаются в разы
04
Соединение с регистром без условия по валюте — запрос выполняется неприемлемо долго на больших базах
05
Отсутствие обработки NULL — деление на NULL даёт ошибку или пустые суммы в отчёте

Сравнение подходов и проверка результата

Сравнение вариантов

СрезПоследних с параметром

Подходит для одной даты: отчёт на дату, обработка одного документа. Быстро и просто, но не работает для набора разных дат.

Соединение с группировкой по МАКСИМУМ(Период)

Универсальный вариант для любого числа документов. Требует двух соединений и аккуратности с производительностью.

РаботаСКурсамиВалют.ПолучитьКурсВалюты

Для кода вне запроса: модуль объекта, обработка проведения. Учитывает взаимные курсы валют, но недоступен внутри текста запроса.

Проверить корректность запроса проще всего на контрольном примере. Возьмите документ, дата которого заведомо не совпадает с датой загрузки курса (например, воскресенье), и сравните полученный курс со значением в регистре на ближайшую предыдущую дату. Курс должен совпадать с последней записью до даты документа.

Контрольные точки

Как проверить результат

Курс для документа выходного дня
Норма

Равен курсу за ближайший предыдущий день загрузки

Тревожный признак

NULL или курс более поздней даты, чем дата документа

Количество строк результата
Норма

Равно числу документов в выборке

Тревожный признак

Строк больше — задвоение из-за соединения без группировки

Рублёвая сумма
Норма

Совпадает с суммой по данным проводок документа

Тревожный признак

Расходится в 10 или 100 раз — не учтена кратность

FAQ

Что делать, если на дату документа курс вообще не загружен ни разу до этого?

Соединение вернёт NULL, и это корректный сигнал: данных нет. Загрузите курсы за нужный период штатной обработкой загрузки курсов валют или введите курс вручную в регистре, после чего запрос начнёт возвращать значение без изменений в коде.

Можно ли получить курс на конец дня документа, а не на время документа?

Да. Замените в условии соединения дату документа на выражение КОНЕЦПЕРИОДА(Реализация.Дата, ДЕНЬ) — тогда в расчёт попадут и курсы, загруженные позже в тот же день. Для СрезПоследних передайте в параметр конец дня аналогичным образом.

Почему в запросе появляются задвоенные строки документов?

Причина почти всегда в соединении с физической таблицей регистра без группировки: к каждому документу присоединяются все записи курса с периодом меньше даты документа. Лечится группировкой по документу с агрегатом МАКСИМУМ(Период) и отдельным соединением для получения самого курса.

Как получить курс в отчёте на СКД?

В наборе данных СКД используется тот же текст запроса с соединением и группировкой. Если отчёт строится на фиксированную дату, передайте её параметром и используйте СрезПоследних — параметр удобно связать с пользовательской настройкой отчёта.

Чем отличается курс от кратности и зачем делить?

Курс показывает стоимость указанного количества единиц валюты в рублях, а кратность — это само количество. Рублёвая сумма вычисляется как СуммаВал × Курс / Кратность. Если кратность равна 1, деление ничего не меняет, но убирать его из формулы нельзя — для части валют кратность отлична от единицы.

Итог

Что важно запомнить

Для одной известной даты используйте СрезПоследних с параметром, для набора документов — соединение по условию Период <= Дата с группировкой по МАКСИМУМ(Период) и вторым соединением для получения курса. Всегда учитывайте кратность, обрабатывайте NULL и включайте отбор по валюте ради производительности.