← Назад к вопросам

Курс валюты на дату каждого документа

2.0 Middle🔥 171 комментариев
#Запросы и оптимизация

Условие

Напишите запрос, который получает курс валюты на дату каждого документа.

Дано:

  • Документ "РеализацияТоваровУслуг" с реквизитом "Валюта"
  • Регистр сведений "КурсыВалют" (периодический) с измерением "Валюта" и ресурсом "Курс"

Требования

  • Для каждого документа получить актуальный курс валюты на дату документа
  • Использовать левое соединение (документы без курса тоже должны попасть в выборку)

Пример результата

ДокументДатаВалютаКурс
Реал-00101.01USD90.50
Реал-00202.01EUR98.30

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

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

Это стандартная задача при работе с многовалютными документами. Нужно получить курс, который был актуален на дату документа.

Решение с использованием ПЕРИОДИЧНОСТЬ

Это основной подход в 1С для работы с периодическими регистрами сведений.

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

Проблема: неправильный результат

Вышеприведённый запрос имеет проблему — он вернёт несколько курсов для одного документа (все курсы, которые были <= даты документа).

Правильное решение с МАКСИМУМ

Для получения последнего актуального курса используем группировку:

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

Проблема: медленный запрос при больших объёмах

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

Оптимизированное решение

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

Решение с использованием цикла (для небольших объёмов)

&НаСервере
Процедура ПолучитьКурсыДляДокументов()
    
    // Сначала получаем документы
    Запрос = Новый Запрос;
    Запрос.Текст = 
    "ВЫБРАТЬ
    |    РТУ.Ссылка КАК Документ,
    |    РТУ.Дата КАК Дата,
    |    РТУ.Валюта КАК Валюта
    |ИЗ
    |    Документ.РеализацияТоваровУслуг КАК РТУ
    |ГДЕ
    |    РТУ.Проведен = Истина";
    
    Выборка = Запрос.Выполнить().Выбрать();
    
    ТаблицаРезультатов = Новая ТаблицаЗначений();
    ТаблицаРезультатов.Колонки.Добавить("Документ");
    ТаблицаРезультатов.Колонки.Добавить("Дата", Новый ОписаниеТипов("Дата"));
    ТаблицаРезультатов.Колонки.Добавить("Валюта");
    ТаблицаРезультатов.Колонки.Добавить("Курс", Новый ОписаниеТипов("Число"));
    
    Пока Выборка.Следующий() Цикл
        НоваяСтрока = ТаблицаРезультатов.Добавить();
        НоваяСтрока.Документ = Выборка.Документ;
        НоваяСтрока.Дата = Выборка.Дата;
        НоваяСтрока.Валюта = Выборка.Валюта;
        
        // Ищем курс на дату документа
        НоваяСтрока.Курс = ПолучитьКурсНаДату(Выборка.Валюта, Выборка.Дата);
    КонецЦикла;
    
    // Теперь ТаблицаРезультатов содержит нужные данные
КонецПроцедуры

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

Альтернатива: использование ЕСТЬNULL с МАКСИМУМ в подзапросе

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

Ключевые моменты

1. ЛЕВОЕ СОЕДИНЕНИЕ — документы без курса попадут в выборку с ЕСТЬNULL

2. Условие Период <= Дата — получаем курс на дату документа или раньше

3. МАКСИМУМ(Период) — берём последний актуальный курс

4. Группировка — без группировки запрос вернёт дублей

5. ЕСТЬNULL(курс, 1) — если курса нет, используем курс 1

Рекомендация

Для большинства случаев используй второе решение с МАКСИМУМ — оно правильное и быстрое. Если остаётся медленным при большом объёме — переходи на трёхэтапное решение с двумя запросами.

Курс валюты на дату каждого документа | PrepBro