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

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

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

Задача №1: Аналог «Среза последних» в запросе для таблицы валют

Рассмотрим ситуацию: у нас есть таблица (или справочник) КурсыВалют со структурой Валюта | Дата | Курс. Нам нужно получить актуальные курсы на определенную дату. Главная ошибка начинающих — попытка найти просто максимальную дату в таблице без привязки к конкретной валюте. В этом случае, если курс доллара обновился вчера, а курс евро — неделю назад, запрос вернет только курс доллара, так как его дата «максимальнее».

Разберем правильный алгоритм решения:

  1. Получение максимальных дат в разрезе валют: Сначала создаем временную таблицу, в которой для каждой валюты находим наиболее позднюю дату, не превышающую наш период.
  2. Соединение с исходными данными: Полученную «карту» (Валюта + МаксДата) соединяем с основной таблицей по обоим полям одновременно, чтобы вытащить ресурс Курс.

Проанализируем пример кода, реализующий этот подход:


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

// Шаг 2: Получаем курсы, соединяясь по паре Валюта + Дата
ВЫБРАТЬ
    Курсы2.Валюта,
    Курсы2.Дата,
    Курсы2.Курс
ИЗ
    ВТ_МаксДаты КАК Т_Даты
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.КурсыВалют КАК Курсы2
    ПО Т_Даты.Валюта = Курсы2.Валюта
    И Т_Даты.МаксДата = Курсы2.Дата

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

Задача №2: Отчет по отклонениям остатков на каждый день

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

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

Рассмотрим, как правильно настроить запрос к этой таблице:


ВЫБРАТЬ
    ОстаткиИОбороты.Период КАК Период,
    ОстаткиИОбороты.Номенклатура КАК Номенклатура,
    ОстаткиИОбороты.Склад КАК Склад,
    ОстаткиИОбороты.КоличествоКонечныйОстаток КАК Остаток
ИЗ
    РегистрНакопления.ОстаткиТоваровНаСкладах.ОстаткиИОбороты(&НачДата, &КонДата, День, , ) КАК ОстаткиИОбороты

Параметр День в периодичности позволяет платформе рассчитать остаток на конец каждого дня. Однако, чтобы получить строки даже для тех дней, когда движений не было, в реальном отчете (например, в СКД) нужно использовать роль поля периода и настройки дополнения периодов. Если же мы пишем чистый запрос, нам придется соединять данные с «календарем».

Разберем логику сравнения с пределами:

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

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

Правильный подход к расчету разницы:


ВЫБРАТЬ
    Т_Остатки.Период,
    Т_Остатки.Номенклатура,
    Т_Остатки.Склад,
    Т_Остатки.Остаток,
    // Получаем лимиты через вложенный запрос или соединение
    ВЫБОР
        КОГДА Т_Остатки.Остаток < ЕСТЬNULL(Т_Лимиты.МинЗапас, 0)
            ТОГДА Т_Остатки.Остаток - Т_Лимиты.МинЗапас
        КОГДА Т_Остатки.Остаток > ЕСТЬNULL(Т_Лимиты.МаксЗапас, 999999)
            ТОГДА Т_Остатки.Остаток - Т_Лимиты.МаксЗапас
        ИНАЧЕ 0
    КОНЕЦ КАК Отклонение
ИЗ
    ВТ_ОстаткиПоДням КАК Т_Остатки
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ПределыОстатков.СрезПоследних(&КонДата) КАК Т_Лимиты
    ПО Т_Остатки.Номенклатура = Т_Лимиты.Номенклатура
    И Т_Остатки.Склад = Т_Лимиты.Склад

Внимание: Использование СрезПоследних в соединении внутри запроса допустимо только если нам нужен срез на одну фиксированную дату (конец периода). Если же лимиты менялись внутри периода отчета и нам важно учитывать эти изменения ежедневно, необходимо использовать соединение с физической таблицей регистра сведений по условию Т_Остатки.Период >= Т_Лимиты.Период с последующей группировкой и выбором максимальной даты лимита.

Типичные ошибки, которых следует избегать

  1. Использование функции СУММА для остатков: Остаток — это состояние на момент времени. Если вы сложите остаток за понедельник (10 шт) и вторник (10 шт), вы не получите 20 шт. Вы получите бессмысленное число. В запросах всегда используйте поле КоличествоКонечныйОстаток (или начальный) и никогда не агрегируйте его функцией СУММА() в разрезе временных периодов.
  2. Забытые индексы: При работе с виртуальными таблицами ОстаткиИОбороты всегда передавайте параметры НачалоПериода и КонецПериода. Это позволяет СУБД использовать индексы и не сканировать лишние данные.
  3. Игнорирование NULL: При левом соединении с таблицей лимитов (если для товара лимит не установлен), вы получите NULL. Обязательно обрабатывайте такие значения функцией ЕСТЬNULL(), иначе арифметические операции (сложение, вычитание) вернут NULL вместо результата.

Выясним причину, почему такие задачи часто дают на собеседованиях. Они проверяют не только знание синтаксиса ВЫБРАТЬ...ИЗ, но и понимание архитектуры данных 1С. Умение отличать остатки от оборотов и понимание того, как платформа дополняет периоды — это базовые навыки качественной разработки отчетов, которые напрямую влияют на производительность системы и достоверность данных для бизнеса.

← На главную