Когда мы работаем с отчетами в 1С, особенно если используем Систему Компоновки Данных (СКД), часто возникает задача сравнения однотипных данных из одного источника по различным критериям. В данном случае, речь идет о сравнении цен на номенклатуру, предоставляемых двумя разными поставщиками или зарегистрированных для двух различных филиалов. Основная трудность, с которой мы сталкиваемся, заключается в том, чтобы получить две различные цены (например, "ЦенаПоставщика1" и "ЦенаПоставщика2") из одного и того же регистра сведений или накопления. При обычной попытке выбрать поле "Цена" дважды, мы получаем одинаковые значения, что делает сравнение невозможным.
Давайте разберем типичную ситуацию. Предположим, у нас есть регистр сведений или накопления, который хранит цены. Из обсуждения на форуме мы знаем, что наш регистр имеет следующую структуру:
Город, Филиал, Номенклатура, ТипЦены.Цена, СтараяЦена.Автор, ДатаЗаписи.Наша цель — выбрать две конкретные цены, соответствующие двум выбранным филиалам (или поставщикам), и затем рассчитать их разницу, а также разницу в процентах. Мы рассмотрим, почему возникает проблема получения одинаковых цен и как ее эффективно решить с помощью различных подходов в СКД.
Для успешного решения этой задачи в СКД существует несколько эффективных подходов. Мы подробно разберем каждый из них, чтобы вы могли выбрать наиболее подходящий для вашей конкретной ситуации.
Один из наиболее интуитивных и гибких методов — это добавление одного и того же регистра в СКД несколько раз, но каждый раз присваивая ему уникальный псевдоним. Это позволяет нам обращаться к одному и тому же регистру как к разным сущностям и устанавливать для каждого из этих "виртуальных" источников свои параметры отбора.
РегистрСведений.ЦеныНоменклатуры) дважды. Для удобства мы можем дать этим наборам данных понятные псевдонимы, например, ЦеныФилиала1 и ЦеныФилиала2.ЦеныФилиала1, мы переходим на вкладку "Параметры" или "Отбор" (в зависимости от способа настройки) и устанавливаем отбор по полю Филиал, привязывая его к параметру СКД, который мы назовем &ФилиалПервый. Этот параметр будет предоставлять пользователю выбор первого филиала. Аналогично, для второго источника, ЦеныФилиала2, мы устанавливаем отбор по полю Филиал, привязывая его к параметру &ФилиалВторой.Номенклатура (и, возможно, ТипЦены, Город, если эти измерения также важны для уникальности записи).
ЦеныФилиала1.Цена) и цену из второго источника (ЦеныФилиала2.Цена).Рассмотрим пример запроса, который СКД может сгенерировать или который мы могли бы написать вручную, используя этот подход:
ВЫБРАТЬ
ЦеныФилиала1.Номенклатура КАК Номенклатура,
ЦеныФилиала1.Цена КАК ЦенаПервогоФилиала,
ЦеныФилиала2.Цена КАК ЦенаВторогоФилиала
ИЗ
РегистрСведений.Цены КАК ЦеныФилиала1 // Первый экземпляр регистра с псевдонимом ЦеныФилиала1
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.Цены КАК ЦеныФилиала2 // Второй экземпляр регистра с псевдонимом ЦеныФилиала2
ПО ЦеныФилиала1.Номенклатура = ЦеныФилиала2.Номенклатура
И ЦеныФилиала1.ТипЦены = ЦеныФилиала2.ТипЦены // Важно учесть все ключевые измерения
И ЦеныФилиала2.Филиал = &ФилиалВторой // Отбор для второго филиала
ГДЕ
ЦеныФилиала1.Филиал = &ФилиалПервый // Отбор для первого филиала
В этом запросе мы явно указываем, какую цену мы берем из какого "виртуального" источника, обеспечивая их независимость. СКД предоставляет удобный интерфейс для построения таких связей без необходимости вручную писать сложный SQL-подобный запрос.
Этот метод является очень мощным и позволяет получить требуемые данные в рамках одного запроса, что часто бывает более производительным и наглядным, особенно для не слишком сложных условий. Он напрямую обращается к проблеме получения двух различных значений из одного поля на основе условий.
&Филиал1 и &Филиал2. Тип данных для них должен соответствовать типу поля Филиал из вашего регистра (например, СправочникСсылка.Филиалы). Эти параметры будут использоваться для выбора конкретных филиалов пользователем.ВЫБОР КОГДА для условного выбора цен, проецируя их в разные колонки.Рассмотрим подробнее пример запроса, который мы можем сформировать в конструкторе запросов:
ВЫБРАТЬ
РегистрЦен.Номенклатура КАК Номенклатура,
МАКСИМУМ(ВЫБОР
КОГДА РегистрЦен.Филиал = &Филиал1
ТОГДА РегистрЦен.Цена
ИНАЧЕ NULL
КОНЕЦ) КАК ЦенаФилиал1,
МАКСИМУМ(ВЫБОР
КОГДА РегистрЦен.Филиал = &Филиал2
ТОГДА РегистрЦен.Цена
ИНАЧЕ NULL
КОНЕЦ) КАК ЦенаФилиал2
ИЗ
РегистрСведений.Цены КАК РегистрЦен // Используем наш регистр
ГДЕ
РегистрЦен.Филиал В (&Филиал1, &Филиал2) // Ограничиваем выборку только нужными филиалами
СГРУППИРОВАТЬ ПО
РегистрЦен.Номенклатура // Группируем по номенклатуре для получения одной строки на товар
Давайте проанализируем этот запрос и его ключевые элементы:
РегистрЦен.Номенклатура КАК Номенклатура: Это наше основное измерение, по которому мы будем сравнивать цены. Оно является ключом для каждой строки результата.МАКСИМУМ(ВЫБОР КОГДА РегистрЦен.Филиал = &Филиал1 ТОГДА РегистрЦен.Цена ИНАЧЕ NULL КОНЕЦ) КАК ЦенаФилиал1: Здесь мы создаем поле ЦенаФилиал1. Для каждой записи регистра мы проверяем, соответствует ли ее Филиал значению параметра &Филиал1. Если условие истинно, то мы берем Цену из текущей записи; в противном случае возвращаем NULL. Функция МАКСИМУМ (или МИНИМУМ, СРЕДНЕЕ — любая агрегатная функция, которая игнорирует NULL) используется, чтобы агрегировать эти значения по Номенклатуре. Поскольку для каждой номенклатуры и филиала должна быть только одна актуальная цена (в большинстве случаев), МАКСИМУМ фактически выберет единственное не-NULL значение для каждой группы.ГДЕ РегистрЦен.Филиал В (&Филиал1, &Филиал2): Это условие в секции ГДЕ очень важно для оптимизации. Оно ограничивает исходную выборку только теми записями, которые относятся к одному из двух выбранных филиалов. Без этого условия запрос мог бы работать дольше, обрабатывая весь регистр, а затем уже применяя ВЫБОР КОГДА.СГРУППИРОВАТЬ ПО РегистрЦен.Номенклатура: Мы группируем результат по номенклатуре, чтобы получить одну строку для каждой номенклатурной позиции, содержащую обе цены. Если у вас в регистре есть другие измерения, влияющие на уникальность цены (например, ТипЦены), их также необходимо добавить в секцию СГРУППИРОВАТЬ ПО.Этот подход элегантно решает проблему получения двух цен из одного регистра, привязывая каждую из них к конкретному параметру, и является одним из наиболее рекомендуемых для таких задач.
Как упоминалось в обсуждении на форуме, использование временных таблиц (ВТ) также является рабочим вариантом. Этот метод может быть особенно полезен, когда условия отбора для каждого филиала достаточно сложны, требуется предварительная обработка данных или когда вам нужно выполнить несколько этапов выборки перед окончательным объединением.
&Филиал1) и помещаем их во временную таблицу. Назовем ее, например, ВТ_ЦеныФилиала1. Этот запрос будет простым и отфильтрованным только по первому филиалу.&Филиал2) и помещаем их во временную таблицу ВТ_ЦеныФилиала2.Номенклатуре (и другим ключевым полям, таким как ТипЦены), чтобы получить агрегированный результат.Пример структуры запроса с использованием временных таблиц:
// Шаг 1: Получаем цены для первого филиала и помещаем во временную таблицу
ВЫБРАТЬ
Цены.Номенклатура КАК Номенклатура,
Цены.Цена КАК Цена
ПОМЕСТИТЬ ВТ_ЦеныФилиала1
ИЗ
РегистрСведений.Цены КАК Цены
ГДЕ
Цены.Филиал = &Филиал1
И Цены.ТипЦены = &ТипЦены // Если тип цены также является параметром
;
// Шаг 2: Получаем цены для второго филиала и помещаем во временную таблицу
ВЫБРАТЬ
Цены.Номенклатура КАК Номенклатура,
Цены.Цена КАК Цена
ПОМЕСТИТЬ ВТ_ЦеныФилиала2
ИЗ
РегистрСведений.Цены КАК Цены
ГДЕ
Цены.Филиал = &Филиал2
И Цены.ТипЦены = &ТипЦены // Если тип цены также является параметром
;
////////////////////////////////////////////////////////////////////////////////
// Шаг 3: Соединяем временные таблицы для получения окончательного результата
ВЫБРАТЬ
ВТ_ЦеныФилиала1.Номенклатура КАК Номенклатура,
ВТ_ЦеныФилиала1.Цена КАК ЦенаПервогоФилиала,
ВТ_ЦеныФилиала2.Цена КАК ЦенаВторогоФилиала
ИЗ
ВТ_ЦеныФилиала1 КАК ВТ_ЦеныФилиала1
ПОЛНОЕ СОЕДИНЕНИЕ ВТ_ЦеныФилиала2 КАК ВТ_ЦеныФилиала2
ПО ВТ_ЦеныФилиала1.Номенклатура = ВТ_ЦеныФилиала2.Номенклатура
Этот подход может быть более наглядным для сложных запросов, но важно помнить о потенциальном увеличении объема передаваемых данных и времени выполнения, если регистры очень большие, так как каждый этап создания временной таблицы требует отдельной выборки и индексации.
Как только мы успешно получили поля ЦенаФилиал1 и ЦенаФилиал2 (любым из вышеперечисленных способов), расчет разницы и разницы в процентах становится стандартной задачей для вычисляемых полей в СКД. СКД предоставляет удобный механизм для создания таких полей.
Для создания поля, показывающего абсолютную разницу цен между двумя филиалами, мы используем простое арифметическое выражение. Переходим на вкладку "Вычисляемые поля" в СКД и добавляем новое поле с выражением:
ЦенаФилиал1 - ЦенаФилиал2
Это поле покажет, насколько цена первого филиала отличается от цены второго.
Для расчета процентной разницы важно учесть возможную ситуацию деления на ноль, если цена второго филиала (которая часто используется как база для сравнения) равна нулю. В этом случае мы можем вернуть 0 или NULL, в зависимости от требований к отчету. Выражение для вычисляемого поля будет выглядеть так:
ВЫБОР
КОГДА ЦенаФилиал2 = 0
ТОГДА 0 // Если цена второго филиала равна нулю, процентную разницу считать как 0 или
// можно использовать NULL, если требуется явное отсутствие значения.
ИНАЧЕ (ЦенаФилиал1 - ЦенаФилиал2) / ЦенаФилиал2 * 100
КОНЕЦ
Если требуется показать отсутствие процента при нулевой цене второго филиала, мы можем изменить выражение на:
ВЫБОР
КОГДА ЦенаФилиал2 = 0
ТОГДА NULL // Явно указываем отсутствие значения при делении на ноль
ИНАЧЕ (ЦенаФилиал1 - ЦенаФилиал2) / ЦенаФилиал2 * 100
КОНЕЦ
Не забудьте настроить формат вывода для поля РазницаПроцент, чтобы оно отображалось в виде процентов (например, с двумя знаками после запятой).
Проблема, когда поля ЦенаПервая и ЦенаВторая показывали одинаковые значения, обычно возникает, если мы неявно или не совсем корректно пытаемся получить два разных значения из одного и того же поля в одном наборе данных без надлежащего разделения контекста. Это происходит, если:
Цена дважды из одного и того же набора данных без использования псевдонимов или условных выражений, которые бы различали, к какому филиалу относится каждая из цен. Система просто проецирует одно и то же значение дважды.Цена без дополнительных условий, она не может определить, какую именно цену нужно вывести в каждую из двух колонок, и часто выводит одно и то же значение (например, первое попавшееся или относящееся к первому филиалу в списке, или просто последнее актуальное).Применяя подходы, описанные выше (множественные источники данных, ВЫБОР КОГДА в запросе или временные таблицы), мы явно разделяем логику получения цен для каждого филиала, тем самым гарантируя, что каждая колонка будет содержать цену именно того филиала, для которого она предназначена. Это позволяет избежать путаницы и получить корректное сравнение.
Сравнение цен различных поставщиков или филиалов на одну и ту же номенклатуру — это типовая задача для аналитических отчетов, и она хорошо решается с помощью мощного инструментария СКД. Используя возможности Системы Компоновки Данных и языка запросов 1С, мы можем эффективно получить и сравнить эти данные.
Мы рассмотрели три основных метода: использование нескольких источников данных в СКД с последующим соединением, применение конструкции ВЫБОР КОГДА непосредственно в запросе, а также подход с временными таблицами. Каждый из них имеет свои преимущества и может быть более или менее подходящим в зависимости от сложности вашей задачи и объема данных. Однако, для большинства случаев сравнения двух сущностей из одного регистра, метод с использованием ВЫБОР КОГДА в запросе СКД является наиболее оптимальным по производительности и простоте реализации.
Надеемся, что этот подробный разбор поможет вам успешно реализовать отчет по сравнению цен в вашей учебной или рабочей конфигурации 1С и справиться с аналогичными задачами в будущем. Помните, что практика и эксперименты с различными подходами — лучший способ освоить тонкости работы с СКД.