Улучшение регистра курсов валют в v8

18.11.07

Разработка - Механизмы платформы 1С

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

Для примера возьмем таблицу документов "Заявка" с реквизитами Дата, Сумма, Валюта, и представим, что нам нужно вывести список заявок с суммами, пересчитанными в рубли по курсу на дату каждой заявки.

У проблемы есть четыре решения:

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)
КонецФункции

// Функция выполняет произвольный запрос и выгружает первую колонку его результата
// в список значений
//
// Параметры
//  ТекстЗапроса – Строка
//  Параметры    – Структура – параметры запроса
//
// Возвращаемое значение:
//  Список значений
//
Функция оВыполнитьЗапросВСписок(ТекстЗапроса, Параметры = Неопределено) Экспорт
  Перем Рез;
  Рез = Новый СписокЗначений;
  Рез.ЗагрузитьЗначения(оВыполнитьЗапросВМассив(ТекстЗапроса, Параметры));
  Возврат Рез
КонецФункции




См. также

Механизмы платформы 1С Программист Стажер Платформа 1С v8.3 Конфигурации 1cv8 Бесплатно (free)

Эта небольшая статья - некоторого рода шпаргалка по файловым потокам: как и зачем с ними работать, какие преимущества это дает.

23.06.2024    7443    bayselonarrend    20    

154

Механизмы платформы 1С Программист Стажер Платформа 1С v8.3 Конфигурации 1cv8 Бесплатно (free)

Пример использования «Сервисов интеграции» без подключения к Шине и без обменов.

13.03.2024    5942    dsdred    16    

80

Механизмы платформы 1С Программист Стажер Платформа 1С v8.3 Бесплатно (free)

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

24.01.2024    17663    YA_418728146    26    

71

Перенос данных 1C Механизмы платформы 1С Системный администратор Программист Стажер Платформа 1С v8.3 Бесплатно (free)

Вы все еще регистрируете изменения только на Планах обмена и Регистрах сведений?

11.12.2023    11221    dsdred    44    

130

Механизмы платформы 1С Программист Бесплатно (free)

Язык программирования 1С содержит много нюансов и особенностей, которые могут приводить к неожиданным для разработчика результатам. Сталкиваясь с ними, программист начинает лучше понимать логику платформы, а значит, быстрее выявлять ошибки и видеть потенциальные узкие места своего кода там, где позже можно было бы ещё долго медитировать с отладчиком в поисках источника проблемы. Мы рассмотрим разные примеры поведения кода 1С. Разберём результаты выполнения и ответим на вопросы «Почему?», «Как же так?» и «Зачем нам это знать?». 

06.10.2023    23756    SeiOkami    48    

135

Механизмы платформы 1С Системный администратор Платформа 1С v8.3 Бесплатно (free)

Начиная с версии платформы 8.3.22 1С снимает стандартные блокировки БД на уровне страниц. Делаем рабочий скрипт, как раньше.

14.09.2023    18828    human_new    27    

80

WEB-интеграция Универсальные функции Механизмы платформы 1С Программист Платформа 1С v8.3 Конфигурации 1cv8 Бесплатно (free)

При работе с интеграциями рано или поздно придется столкнуться с получением JSON файлов. И, конечно же, жизнь заставит проверять файлы перед тем, как записывать данные в БД.

28.08.2023    14729    YA_418728146    7    

166
Комментарии
Подписаться на ответы Инфостарт бот Сортировка: Древо развёрнутое
Свернуть все
1. tormozit 7229 18.11.07 21:43 Сейчас в теме
2. Gorky 19.11.07 07:29 Сейчас в теме
Да. Мне тоже такой метод посоветовал приятель, который сейчас работает в Канаде. Общий смысл такой, что надо кроме ДатаНачала в регистре сведений ставить ДатаКонца. Если бы это было реализовано на уровне платформы, то вообще проблем бы с запросами не было. Тем более, что делается это элементарно.
3. a.v.petuhov 19.11.07 11:10 Сейчас в теме
Но он имеет одно существенное неудобство - его сложно использовать в запросах в соединении с другими таблицами из-за того, что в общем случае курс валюты устанавливается не на каждый календарный день. Если на какую-то дату курс не установлен, должен браться курс на предыдущую дату, что на языке запросов громоздко выглядит и долго исполняется.

Может я совсем ничего не понял, но есть ведь виртуальная таблица "СрезПоследних". Она и возвращеает ближайший курс на указанную дату. Зачем тогда все ЭТО?
4. Skylark 70 19.11.07 15:55 Сейчас в теме
(3) +1
Я тоже впомнил про "срез последних". Или мы чего-то не догоняем - или автор осрамился.
5. _Reset 19.11.07 18:00 Сейчас в теме
пример просто приведен не совсем "наглядный".
проблемы обычно возникают когда надо вывести список заявок за разные даты и курсы валют.
тогда срезпоследних не покатит
6. clappa 902 20.11.07 00:56 Сейчас в теме
(3,4) Не, ребята, вы не поняли. СрезПоследних возвращает курс на одну конкретную дату, переданную в качестве параметра. А если нужно получить курсы на каждую дату в запросе, тогда он вам не поможет.
7. Yasen 490 03.01.08 20:36 Сейчас в теме
Решение интересное, спасибо
8. PRoman 73 07.09.10 09:24 Сейчас в теме
Удивительное рядом. Спасибо! :)
9. Il 30 03.05.12 07:07 Сейчас в теме
Хорошее решение! Пока база небольшая не заметно было. Спасибо за идею
10. Arxxximed 37 04.08.16 12:27 Сейчас в теме
Интересно, а нафига тогда после этой статьи, все равно слишком популярны статьи типа срез последних на каждую дату? И про пункт 3.Хитрый запрос: я насколько понял при срезе последних, как раз платформа доработатывает запрос к базе вот таким хитрым запросом, или что то типо того
Оставьте свое сообщение