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

Программист 1С v8.3 (Управляемые формы) 1С:Управление торговлей Управленческий учет IT и автоматизация бизнеса
← На главную

При разработке отчетов или обработок в 1С часто возникает задача пересчета сумм документов в управленческую или регламентированную валюту на момент совершения операции. Если мы используем виртуальную таблицу СрезПоследних с одним параметром даты, мы получаем курс только на один момент времени. Но что делать, если в выборке сотни документов с разными датами, и для каждого нужно получить свой актуальный курс?

В этой статье мы подробно разберем, как правильно реализовать получение курсов валют на даты документов (реализаций, поступлений) в одном пакетном запросе, избежав дублирования строк и ошибок производительности. Этот подход является альтернативой другим методам оптимизации, таким как пакетная выборка данных для множества разрозненных запросов.

Проблема: Почему простой ЛЕВОЕ СОЕДИНЕНИЕ не работает?

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

Посмотрим на пример "наивного" запроса, который приводит к ошибкам:


ВЫБРАТЬ
    Продажи.Ссылка,
    Продажи.Дата,
    Курсы.Курс
ИЗ
    Документ.РеализацияТоваровУслуг КАК Продажи
        ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КурсыВалют КАК Курсы
        ПО Курсы.Валюта = Продажи.Валюта
        И Курсы.Период <= Продажи.Дата

В чем здесь ошибка?

Проблема заключается в условии Курсы.Период <= Продажи.Дата. Если курсы валют загружаются каждый день, а документ введен сегодня, то под это условие подпадут все записи курсов за всю историю ведения базы до текущего момента. В результате, одна строка документа "размножится" на количество дней, за которые были введены курсы. Вместо одной суммы вы получите колоссальные значения, а отчет будет формироваться вечно.

Второй неверный подход — использовать жесткое равенство Курсы.Период = Продажи.Дата (или начало дня даты документа). Это не сработает, потому что:

  1. Курсы могут не устанавливаться в выходные и праздничные дни (ЦБ РФ не обновляет курсы ежедневно).
  2. Если документ введен 5-го числа, а курс последний раз обновлялся 3-го, нам нужно взять курс от 3-го числа, а равенство вернет NULL.

Решение 1: Классический метод через временные таблицы

Самый надежный и производительный способ (особенно для больших выборок) — это эмуляция логики "Срез Последних" вручную через временные таблицы. Чтобы отлаживать подобные многоэтапные запросы, удобно использовать специализированные инструменты, например, функциональную консоль запросов, которая полностью поддерживает пакеты и временные таблицы — для этого подойдет функциональная консоль отладки запросов и СКД. Рассмотрим этот алгоритм по шагам.

Суть алгоритма сводится к трем этапам:

  1. Подготовить таблицу дат документов.
  2. Найти максимальную дату курса, которая не превышает дату документа.
  3. По найденной дате получить само значение курса и кратности.

Шаг 1. Получаем список документов и дат

Сначала выберем все необходимые нам документы во временную таблицу. На этом этапе полезно привести дату документа к началу дня (если курсы в вашей системе хранятся с точностью до дня, как это обычно бывает в типовых конфигурациях).


ВЫБРАТЬ
    Реализация.Ссылка КАК ДокументСсылка,
    Реализация.ВалютаДокумента КАК Валюта,
    НАЧАЛОПЕРИОДА(Реализация.Дата, ДЕНЬ) КАК ДатаКурса
ПОМЕСТИТЬ ВТ_Документы
ИЗ
    Документ.РеализацияТоваровУслуг КАК Реализация
ГДЕ
    Реализация.Дата МЕЖДУ &НачалоПериода И &КонецПериода
;

Шаг 2. Находим дату актуального курса

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

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


ВЫБРАТЬ
    ВТ_Документы.ДокументСсылка,
    ВТ_Документы.Валюта,
    МАКСИМУМ(КурсыВалют.Период) КАК ПериодКурса
ПОМЕСТИТЬ ВТ_МаксимальныеДатыКурсов
ИЗ
    ВТ_Документы КАК ВТ_Документы
        ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.КурсыВалют КАК КурсыВалют
        ПО ВТ_Документы.Валюта = КурсыВалют.Валюта
        И КурсыВалют.Период <= ВТ_Документы.ДатаКурса
        
СГРУППИРОВАТЬ ПО
    ВТ_Документы.ДокументСсылка,
    ВТ_Документы.Валюта
;

Обратите внимание: здесь мы используем ВНУТРЕННЕЕ СОЕДИНЕНИЕ. Если курса для валюты вообще нет в истории (что маловероятно, но возможно), документ отсеется. Если важно сохранить документы без курсов, используйте ЛЕВОЕ СОЕДИНЕНИЕ, но тогда функция МАКСИМУМ может вернуть NULL.

Шаг 3. Получаем значения курса и кратности

Теперь у нас есть точная дата курса для каждого документа. Осталось соединить таблицу ВТ_МаксимальныеДатыКурсов обратно с регистром КурсыВалют, но теперь уже по точному равенству периодов.

Важно: Никогда не забывайте про поле Кратность. Многие валюты (например, Вьетнамский донг или Японская иена) имеют курс не за 1 единицу, а за 10, 100 или 1000 единиц. Если вы упустите кратность, ваши суммы будут неверны на несколько порядков.


ВЫБРАТЬ
    ВТ_МаксимальныеДатыКурсов.ДокументСсылка,
    ЕСТЬNULL(КурсыВалют.Курс, 1) КАК Курс,
    ЕСТЬNULL(КурсыВалют.Кратность, 1) КАК Кратность
ИЗ
    ВТ_МаксимальныеДатыКурсов КАК ВТ_МаксимальныеДатыКурсов
        ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КурсыВалют КАК КурсыВалют
        ПО ВТ_МаксимальныеДатыКурсов.Валюта = КурсыВалют.Валюта
        И ВТ_МаксимальныеДатыКурсов.ПериодКурса = КурсыВалют.Период

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

СуммаВРублях = СуммаВВалюте * (Курс / Кратность)

Решение 2: Коррелированный подзапрос (для современных версий платформы)

Если выборка данных не очень велика, или вы хотите написать более компактный код без трех временных таблиц, можно воспользоваться возможностями языка запросов 1С, позволяющими делать вложенные запросы в списке выборки. Это выглядит элегантнее.

Давайте посмотрим, как это делается:


ВЫБРАТЬ
    Реализация.Ссылка,
    Реализация.СуммаДокумента,
    ЕСТЬNULL(
        (ВЫБРАТЬ ПЕРВЫЕ 1
            Курсы.Курс
        ИЗ
            РегистрСведений.КурсыВалют КАК Курсы
        ГДЕ
            Курсы.Валюта = Реализация.ВалютаДокумента
            И Курсы.Период <= Реализация.Дата
        УПОРЯДОЧИТЬ ПО
            Курсы.Период УБЫВ), 1) КАК АктуальныйКурс,
            
    ЕСТЬNULL(
        (ВЫБРАТЬ ПЕРВЫЕ 1
            Курсы.Кратность
        ИЗ
            РегистрСведений.КурсыВалют КАК Курсы
        ГДЕ
            Курсы.Валюта = Реализация.ВалютаДокумента
            И Курсы.Период <= Реализация.Дата
        УПОРЯДОЧИТЬ ПО
            Курсы.Период УБЫВ), 1) КАК АктуальнаяКратность
ИЗ
    Документ.РеализацияТоваровУслуг КАК Реализация
ГДЕ
    Реализация.Дата МЕЖДУ &НачалоПериода И &КонецПериода

Преимущества: Код читается легко, все в одном запросе.
Недостатки: Может работать медленнее на больших объемах данных, так как подзапрос выполняется для каждой строки основной таблицы. Кроме того, приходится дублировать подзапрос для Курса и для Кратности.

Решение 3: Использование СКД (Система Компоновки Данных)

Если ваша задача — создать отчет, то писать сложные запросы зачастую вообще не нужно. СКД умеет "соединять" наборы данных, автоматически применяя параметры.

Рассмотрим, как это настроить в конструкторе схемы компоновки данных:

  1. Создайте Набор данных - запрос (Назовем его "Документы"). Выберите там данные из документов.
  2. Создайте второй Набор данных - запрос (Назовем его "Курсы"). В тексте запроса напишите:
    
    ВЫБРАТЬ Курс, Кратность, Валюта, Период ИЗ РегистрСведений.КурсыВалют.СрезПоследних(&Период, )
    
    Обратите внимание, параметр &Период здесь обязателен.
  3. Перейдите на вкладку Связи наборов данных.
  4. Добавьте связь:
    • Источник связи: Документы
    • Приемник связи: Курсы
    • Условие связи: Валюта = Валюта
    • Параметры: Установите параметр Период в значение поля источника Дата (или Период).

При такой настройке СКД сама для каждой строки из набора "Документы" выполнит виртуальное получение среза последних на конкретную дату этой строки. Это очень мощный механизм, который избавляет от написания "многоэтажных" запросов. Для еще более сложных отчетов можно расширить стандартные возможности, используя, например, инструменты для пользовательской настройки СКД или применяя нестандартные приемы, такие как использование двух разных схем в одном отчете.

Рекомендации и важные нюансы

В завершение проанализируем несколько важных моментов, которые помогут избежать ошибок в будущем:

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

← На главную