Для примера возьмем таблицу документов "Заявка" с реквизитами Дата, Сумма, Валюта, и представим, что нам нужно вывести список заявок с суммами, пересчитанными в рубли по курсу на дату каждой заявки.
У проблемы есть четыре решения:
1. Предварительный расчет
---------------------------------
В документ "Заявка" добавляется реквизит "СуммаРуб", которая расчитывается и заполняется при записи заявки.
Такой вариант вполне применим и даже предпочтителен для ряда частных случаев, но универсальным не является. Кроме того, если курс по каким-либо причинам менялся после записи документа (например, документ имеет плановый характер и введен будущей датой), необходимо будет перезаписать документ для обновления рублевой суммы.
2. Административные методы
---------------------------------
Необходимо обеспечить, чтобы в регистр курсов курсы заносились на каждый календарный день. То есть курс вводится в регистр не только на дату, когда он установлен, но и на все последующие даты, для которых он действует.
Тогда решение задачи будет очень простым:
ВЫБРАТЬ
Дата, Сумма, Заявки.Валюта, Сумма*Курс КАК СуммаРуб
ИЗ
Документ.Заявка КАК Заявки
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КурсыВалют КАК Курсы
ПО (Заявки.Валюта = Курсы.Валюта) И
(Заявки.Дата = Курсы.Период)
Однако, как и все административные методы, этот чувствителен к человеческому фактору и соблюдению регламента. Также, он не решает вопрос, если сушествуют документы, введенные будущими датами (например, плановые заявки).
3. Хитрый запрос
---------------------------------
Используем двойное соединение с регистром курсов для получения действующего курса на каждую дату:
ВЫБРАТЬ
Дата, Сумма, ЗаявкиСДатамиКурсов.Валюта, Сумма*Курс КАК СуммаРуб
ИЗ
(ВЫБРАТЬ
Ссылка, Дата, Сумма, Заявки.Валюта, МАКСИМУМ(Курсы1.Период) КАК ДатаКурса
ИЗ
Документ.Заявка КАК Заявки
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КурсыВалют КАК Курсы1
ПО (Заявки.Валюта = Курсы1.Валюта) И
(Заявки.Дата >= Курсы1.Период)
СГРУППИРОВАТЬ ПО
Ссылка, Дата, Сумма, Заявки.Валюта) КАК ЗаявкиСДатамиКурсов
ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.КурсыВалют КАК Курсы2
ПО (ЗаявкиСДатамиКурсов.Валюта = Курсы2.Валюта) И
(ЗаявкиСДатамиКурсов.ДатаКурса = Курсы2.Период)
Этот теоретически работающий запрос может выполняться очень долго.
4. Доработка регистра курсов
---------------------------------
Именно этому варианту посвящена данная статья. Доработка регистра заключается в добавлении индексированного реквизита "СледующийПериод", в котором для каждой записи будет храниться дата следующего курса (то есть дата, начиная с которой текущая запись перестает действовать), а также добавление кода в модуль регистра курсов валют для автоматического заполнения нового реквизита.
Решение задачи получается почти таким же простым и быстрым, как и в варианте 2:
ВЫБРАТЬ
Дата, Сумма, Заявки.Валюта, Сумма*Курс КАК СуммаРуб
ИЗ
Документ.Заявка КАК Заявки
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КурсыВалют КАК Курсы
ПО (Заявки.Валюта = Курсы.Валюта) И
(Заявки.Дата >= Курсы.Период) И
(Заявки.Дата < Курсы.СледующийПериод)
Наибольшие сложности этого метода связаны с доработкой модуля регистра курсов валют для автоматической поддержки актуальности служебного реквизита "СледующийПериод". Когда в регистр добавляется новая запись следует:
1. Определить СледующийПериод для добавляемой записи.
2. Скорректировать СледующийПериод для уже существующей записи, стоящей по хронологии перед добавляемой записью.
Cледующий код решает эту задачу (его необходимо добавить в процедуру "ПриЗаписи" модуля регистра курсов):
// Подготовимся к заполнению поля "Следующий период": определим область пространства измерений,
//которые подвергаются изменениям
ОтборДатаМин = Неопределено; ОтборДатаМакс = Неопределено; ОтборВалюта = Неопределено;
Если не ОбменДанными.Загрузка Тогда
Если Замещение Тогда
ОтборДатаМин = ?(Отбор.Период.Использование, Отбор.Период.Значение, '00010101');
ОтборДатаМакс = ?(Отбор.Период.Использование, Отбор.Период.Значение, '39991231');
ОтборВалюта = ?(Отбор.Валюта.Использование, Отбор.Валюта.Значение, Неопределено);
ИначеЕсли Количество() > 0 Тогда
ТЗ = Выгрузить();
ТЗ.Сортировать("Период");
ОтборДатаМин = ТЗ[0].Период;
ОтборДатаМакс = ТЗ[ТЗ.Количество()-1].Период;
ТЗ.Свернуть("Валюта");
ОтборВалюта = ТЗ.ВыгрузитьКолонку("Валюта");
КонецЕсли;
КонецЕсли;
// Заполнение поля "Следующий период": находим все записи, попавшие в изменяемый период,
// и для каждой определяем дату следующего курса
Если ОтборДатаМин <> Неопределено Тогда
Записи = РегистрыСведений.КурсыВалют.СоздатьНаборЗаписей();
Записи.ОбменДанными.Загрузка = Истина;
РЗ = оВыполнитьЗапрос("ВЫБРАТЬ
| Период, Валюта, Курс, Кратность, СледующийПериод
|ИЗ
| РегистрСведений.КурсыВалют КАК КурсыВалют
|ГДЕ
| СледующийПериод = &ПустаяДата
| ИЛИ (Период <= &ДатаМакс И (Период >= &ДатаМин ИЛИ СледующийПериод >= &ДатаМин)
| " + ?(ОтборВалюта = Неопределено, "", ?(ТипЗнч(ОтборВалюта) = Тип("Массив"),
"И Валюта В (&Валюта)", "И Валюта = &Валюта")) + ")",
Новый Структура("ДатаМин, ДатаМакс, Валюта, ПустаяДата",
ОтборДатаМин, ОтборДатаМакс, ОтборВалюта, '00010101'));
Выборка = РЗ.Выбрать();
Пока Выборка.Следующий() Цикл
Зн = оВыполнитьЗапросВСкаляр("ВЫБРАТЬ ПЕРВЫЕ 1
| Период КАК Период
|ИЗ
| РегистрСведений.КурсыВалют КАК КурсыВалют
|ГДЕ
| Валюта = &Валюта И Период > &Период
|УПОРЯДОЧИТЬ ПО
| Период",
Новый Структура("Валюта, Период", Выборка.Валюта, Выборка.Период));
Зн = ?(Зн = NULL, '39991231', Зн);
Если Зн <> Выборка.СледующийПериод Тогда
Записи.Очистить();
Записи.Отбор.Период.Установить(Выборка.Период);
Записи.Отбор.Валюта.Установить(Выборка.Валюта);
Запись = Записи.Добавить();
Запись.Период = Выборка.Период;
Запись.Валюта = Выборка.Валюта;
Запись.Курс = Выборка.Курс;
Запись.Кратность = Выборка.Кратность;
Запись.СледующийПериод = Зн;
Записи.Записать(Истина);
КонецЕсли;
КонецЦикла;
КонецЕсли;
P.S. В коде используются две вспомогательные функции (оВыполнитьЗапрос и оВыполнитьЗапросВСкаляр):
// Функция выполняет произвольный запрос
//
// Параметры
// ТекстЗапроса – Строка
// Параметры – Структура – параметры запроса
//
// Возвращаемое значение:
// РезультатЗапроса
//
Функция оВыполнитьЗапрос(ТекстЗапроса, Параметры = Неопределено) Экспорт
Перем Запрос, Зн;
Запрос = Новый Запрос(ТекстЗапроса);
Если Параметры <> Неопределено Тогда
Для каждого Зн из Параметры Цикл
Запрос.УстановитьПараметр(Зн.Ключ, Зн.Значение)
КонецЦикла;
КонецЕсли;
Возврат Запрос.Выполнить()
КонецФункции
// Функция выполняет произвольный запрос и возвращает значение из
// первой строки и первой колонки результата.
// Имеет смысл, применяя эту функцию, использовать в тексте запроса конструкцию
// "ВЫБРАТЬ ПЕРВЫЕ 1"
//
// Параметры
// ТекстЗапроса – Строка
// Параметры – Структура – параметры запроса
//
// Возвращаемое значение:
// Значение из первой строки и первой колонки результата запроса.
// Если результат запроса пустой, возвращается NULL
//
Функция оВыполнитьЗапросВСкаляр(ТекстЗапроса, Параметры = Неопределено) Экспорт
Перем Выборка;
Выборка = оВыполнитьЗапрос(ТекстЗапроса, Параметры).Выбрать(ОбходРезультатаЗапроса.Прямой);
Возврат ?(Выборка.Следующий(), Выборка[0], NULL);
КонецФункции
// Функция выполняет произвольный запрос и выгружает его результат в таблицу значений
//
// Параметры
// ТекстЗапроса – Строка
// Параметры – Структура – параметры запроса
//
// Возвращаемое значение:
// ТаблицаЗначений
//
Функция оВыполнитьЗапросВТаблицу(ТекстЗапроса, Параметры = Неопределено) Экспорт
Возврат оВыполнитьЗапрос(ТекстЗапроса, Параметры).Выгрузить(ОбходРезультатаЗапроса.Прямой)
КонецФункции
// Функция выполняет произвольный запрос и выгружает первую колонку его результата
// в массив
//
// Параметры
// ТекстЗапроса – Строка
// Параметры – Структура – параметры запроса
//
// Возвращаемое значение:
// Массив
//
Функция оВыполнитьЗапросВМассив(ТекстЗапроса, Параметры = Неопределено) Экспорт
Возврат оВыполнитьЗапросВТаблицу(ТекстЗапроса, Параметры).ВыгрузитьКолонку(0)
КонецФункции
// Функция выполняет произвольный запрос и выгружает первую колонку его результата
// в список значений
//
// Параметры
// ТекстЗапроса – Строка
// Параметры – Структура – параметры запроса
//
// Возвращаемое значение:
// Список значений
//
Функция оВыполнитьЗапросВСписок(ТекстЗапроса, Параметры = Неопределено) Экспорт
Перем Рез;
Рез = Новый СписокЗначений;
Рез.ЗагрузитьЗначения(оВыполнитьЗапросВМассив(ТекстЗапроса, Параметры));
Возврат Рез
КонецФункции
Вступайте в нашу телеграмм-группу Инфостарт
