Как в СКД получить последнюю цену номенклатуры и корректно просуммировать ее по складу и в общем итоге?

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

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

Рассмотрим подробнее эту проблему и пути ее решения, основываясь на опыте разработчиков 1С.

Исходная постановка задачи и начальный запрос

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


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

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ
    ЦеныНоменклатуры.Номенклатура,
    NULL,
    NULL,
    ЦеныНоменклатуры.Номенклатура.ЕдиницаИзмерения,
    NULL,
    ЦеныНоменклатуры.Регистратор,
    ЦеныНоменклатуры.Цена,
    ТИПЗНАЧЕНИЯ(ЦеныНоменклатуры.Регистратор),
    ЦеныНоменклатуры.ВидЦены
ИЗ
    РегистрСведений.ЦеныНоменклатуры КАК ЦеныНоменклатуры

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

Решение с использованием функции СКД: ВычислитьВыражениеСГруппировкойМассив

Для того чтобы просуммировать именно последнюю цену для каждой номенклатуры, а не все цены, которые могли попасть в выборку, можно использовать специфическую функцию СКД — ВычислитьВыражениеСГруппировкойМассив.

Рассмотрим шаги по реализации этого решения:

  1. Добавление поля "Цена" в ресурсы отчета:

    На закладке "Ресурсы" в СКД необходимо добавить поле "Цена" (или как оно названо в вашем запросе). Затем для него следует задать выражение, которое позволит выполнить двойное агрегирование.

  2. Использование функции ВычислитьВыражениеСГруппировкойМассив:

    Функция ВычислитьВыражениеСГруппировкойМассив предназначена для выполнения агрегации по определенным полям группировки, а затем обработки полученных результатов на более высоком уровне. В нашем случае мы хотим сначала получить "цену" для каждой "номенклатуры", а затем просуммировать эти значения.

    Выражение для ресурса будет выглядеть так:

    Сумма(ВычислитьВыражениеСГруппировкойМассив("Цена", "Номенклатура"))

    Разберем это выражение:

    • "Цена": Это первое выражение внутри функции ВычислитьВыражениеСГруппировкойМассив. Оно указывает, какое поле должно быть получено для каждой группировки. В данном контексте, без явной агрегатной функции (например, Минимум(Цена) или Максимум(Цена)), СКД по умолчанию выбирает одно значение из группы. Это может быть как преимуществом, так и потенциальным недостатком, если в группе действительно несколько разных цен на одну номенклатуру, и нам нужна *конкретная* "последняя", а не "любая". Тем не менее, если исходный запрос уже использует СрезПоследних, то для каждой номенклатуры должно быть *одно* значение цены, и это выражение сработает корректно.
    • "Номенклатура": Это второе выражение. Оно указывает, по каким полям необходимо сгруппировать данные перед тем, как взять значение "Цены". То есть, для каждой уникальной "Номенклатуры" будет сформирован "массив" из цен, из которого затем будет выбрано одно значение (если их несколько).
    • Сумма(...): Внешняя агрегатная функция. Она просуммирует результаты, полученные от ВычислитьВыражениеСГруппировкойМассив. Таким образом, мы получаем сумму последних цен по складу или в общем итоге.

    Автор темы подтвердил, что именно это выражение решило его задачу.

  3. Настройка поля "Рассчитывать по..." для агрегации по складу и общему итогу:

    Для корректного суммирования по складу и получения общего итога, необходимо правильно настроить поле "Рассчитывать по..." для вашего ресурса "Цена".

    • Суммирование по складу: В свойствах ресурса "Цена" на закладке "Ресурсы" установите флажок напротив поля "Склад" в колонке "Рассчитывать по...". Это укажет СКД, что суммирование должно происходить на уровне каждой группировки по складу.
    • Общий итог: Если вы хотите получить общий итог по всему отчету, убедитесь, что в "Рассчитывать по..." не указано ничего или выбрано соответствующее поле для общего итога (иногда обозначается как "ОбщийИтог" или просто отсутствие конкретной группировки). СКД автоматически посчитает сумму по всем данным, для которых нет более мелких группировок.

Критика и уточнение понятия "последней" цены

Важно отметить, что использование ВычислитьВыражениеСГруппировкойМассив("Цена", "Номенклатура") без явной агрегатной функции внутри (например, Минимум(Цена) или Максимум(Цена)) может быть не всегда надежным в строгом смысле "последней" цены, если исходный запрос по каким-то причинам может возвращать несколько цен для одной номенклатуры в рамках одной детальной записи отчета СКД (что маловероятно при правильном использовании СрезПоследних). Если в рамках одной "Номенклатуры" после работы СрезПоследних все же остается несколько цен (например, из-за объединения с другими наборами данных, не учтенных в исходном запросе автора), то СКД может выбрать "не пойми какую цену наугад" из этого массива. Однако, если ваш запрос гарантирует, что для каждой номенклатуры в детальных записях СКД будет *только одна* строка с ценой (что обычно обеспечивает СрезПоследних), то данное решение является рабочим.

Более надежные подходы для получения "последней" цены

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

1. Использование функции Последняя (Last) в ресурсах СКД

Язык выражений СКД имеет функцию Последняя (Last). Она позволяет получить значение из последней записи в рамках текущей группировки. Для ее корректной работы критически важна правильная сортировка данных.

  1. Настройка сортировки: Убедитесь, что на закладке "Наборы данных" или "Настройки" для вашего набора данных установлена сортировка по полю, которое определяет "последнюю" запись (например, ПериодСекунда или ДатаУстановкиЦены) в убывающем порядке. Это гарантирует, что "последняя" запись действительно будет последней в рамках группировки.

  2. Определение ресурса: Добавьте новый ресурс, например, "ПоследняяЦенаНоменклатуры", и задайте для него следующее выражение:

    Сумма(ВычислитьВыражениеСГруппировкойМассив("Последняя(Цена)", "Номенклатура"))

    Здесь Последняя(Цена) внутри ВычислитьВыражениеСГруппировкойМассив явным образом указывает СКД взять именно последнюю цену для каждой номенклатуры, учитывая установленную сортировку.

2. Использование нескольких наборов данных и их связь

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

  1. Первый набор данных (Основные данные): Создайте набор данных для получения основных сведений, например, остатков товаров на складах. В вашем случае это будет примерно такой запрос:

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

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

    Здесь &НаДату и &ВидЦены – это параметры СКД, которые вы можете настроить для отчета.

  3. Связь наборов данных: На закладке "Связи наборов данных" создайте новую связь между первым и вторым наборами по полю Номенклатура.

  4. Определение ресурса: Теперь в ресурсах вы можете просто просуммировать поле Цена из второго набора данных:

    Сумма(Цена)

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

3. Предварительный расчет "последней" цены в основном запросе

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


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

В этом случае СрезПоследних будет присоединен к каждому товару, и для каждой номенклатуры будет получена актуальная цена на указанную дату &НаДату. Далее в ресурсах можно будет просто использовать Сумма(Цена).

Важные нюансы и рекомендации

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

← На главную