При разработке отчетов по продажам (например, план-фактного анализа, отчетов по валовой прибыли или оценке рентабельности) программисты часто сталкиваются с необходимостью сопоставить данные по нескольким бухгалтерским счетам. Классический пример — получение в одной строке по каждой номенклатурной позиции (или номенклатурной группе) сразу трех показателей: Выручки (кредитовый оборот счета 90.01), Себестоимости (дебетовый оборот счета 90.02) и НДС (дебетовый оборот счета 90.03).
Попытки решить эту задачу «в лоб» через множественные соединения виртуальных таблиц или некорректно настроенные объединения часто приводят либо к дублированию строк, либо к критическому падению производительности базы данных. В этой статье мы подробно разберем причины возникновения таких ошибок и рассмотрим три эффективных метода решения этой задачи.
В исходном сообщении темы была сделана попытка использовать виртуальную таблицу РегистрБухгалтерии.Хозрасчетный.ОборотыДтКт для каждого счета отдельно с последующим применением конструкции ПОЛНОЕ СОЕДИНЕНИЕ во временных таблицах. Давайте разберем, почему этот подход ошибочен:
ОборотыДтКт создает колоссальную и неоправданную нагрузку на СУБД.NULL). Кроме того, полные соединения очень ресурсоемки и при больших объемах данных могут приводить к зависанию отчета или перегрузке памяти сервера.Рассмотрим правильные и оптимизированные варианты решения этой задачи.
---
Самый быстрый и производительный способ получить данные по нескольким счетам в одну строку — использовать виртуальную таблицу РегистрБухгалтерии.Хозрасчетный.Обороты и распределить суммы по колонкам с помощью конструкции ВЫБОР КОГДА внутри агрегатной функции СУММА. Этот прием позволяет СУБД прочитать данные из базы за один проход, без создания временных таблиц и соединений.
Проанализируем пример такого запроса:
ВЫБРАТЬ
ХозрасчетныйОбороты.Субконто1 КАК Номенклатура,
СУММА(ВЫБОР
КОГДА ХозрасчетныйОбороты.Счет = &Счет90_01
ТОГДА ХозрасчетныйОбороты.СуммаОборотКт
ИНАЧЕ 0
КОНЕЦ) КАК Выручка,
СУММА(ВЫБОР
КОГДА ХозрасчетныйОбороты.Счет = &Счет90_02
ТОГДА ХозрасчетныйОбороты.СуммаОборотДт
ИНАЧЕ 0
КОНЕЦ) КАК Себестоимость,
СУММА(ВЫБОР
КОГДА ХозрасчетныйОбороты.Счет = &Счет90_03
ТОГДА ХозрасчетныйОбороты.СуммаОборотДт
ИНАЧЕ 0
КОНЕЦ) КАК НДС,
СУММА(ВЫБОР
КОГДА ХозрасчетныйОбороты.Счет = &Счет90_01
ТОГДА ХозрасчетныйОбороты.СуммаОборотКт
ИНАЧЕ 0
КОНЕЦ) - СУММА(ВЫБОР
КОГДА ХозрасчетныйОбороты.Счет = &Счет90_03
ТОГДА ХозрасчетныйОбороты.СуммаОборотДт
ИНАЧЕ 0
КОНЕЦ) КАК СуммаБезНДС
ИЗ
РегистрБухгалтерии.Хозрасчетный.Обороты(
&НачалоПериода,
&КонецПериода,
Период,
Счет В (&СписокСчетов90),
,
Организация = &Организация,
,
) КАК ХозрасчетныйОбороты
СГРУППИРОВАТЬ ПО
ХозрасчетныйОбороты.Субконто1
Преимущества этого подхода:
NULL (вместо них в полях будут нули, что исключает ошибки при арифметических операциях).СуммаБезНДС).---
Если специфика учета требует детального разграничения источников данных (например, если счета находятся в разных регистрах или к ним применяются разные отборы), то вместо соединений следует использовать операцию ОБЪЕДИНИТЬ ВСЕ с последующей группировкой полученных результатов.
Разберем по шагам, как правильно построить такой запрос:
Посмотрим на пример реализации этой схемы в коде запроса:
// Шаг 1. Получаем данные по первому счету (Выручка)
ВЫБРАТЬ
ХозрасчетныйОбороты.Субконто1 КАК Номенклатура,
ХозрасчетныйОбороты.СуммаОборотКт КАК Выручка,
0 КАК Себестоимость,
0 КАК НДС
ПОМЕСТИТЬ ВТ_ВсеДанные
ИЗ
РегистрБухгалтерии.Хозрасчетный.Обороты(&НачалоПериода, &КонецПериода, , Счет = &Счет90_01, , Организация = &Организация, , ) КАК ХозрасчетныйОбороты
ОБЪЕДИНИТЬ ВСЕ
// Шаг 2. Получаем данные по второму счету (Себестоимость)
ВЫБРАТЬ
ХозрасчетныйОбороты.Субконто1,
0,
ХозрасчетныйОбороты.СуммаОборотДт,
0
ИЗ
РегистрБухгалтерии.Хозрасчетный.Обороты(&НачалоПериода, &КонецПериода, , Счет = &Счет90_02, , Организация = &Организация, , ) КАК ХозрасчетныйОбороты
ОБЪЕДИНИТЬ ВСЕ
// Шаг 3. Получаем данные по третьему счету (НДС)
ВЫБРАТЬ
ХозрасчетныйОбороты.Субконто1,
0,
0,
ХозрасчетныйОбороты.СуммаОборотДт
ИЗ
РегистрБухгалтерии.Хозрасчетный.Обороты(&НачалоПериода, &КонецПериода, , Счет = &Счет90_03, , Организация = &Организация, , ) КАК ХозрасчетныйОбороты
;
////////////////////////////////////////////////////////////////////////////////
// Шаг 4. Схлопываем строки по Номенклатуре в итоговой выборке
ВЫБРАТЬ
ВТ_ВсеДанные.Номенклатура КАК Номенклатура,
СУММА(ВТ_ВсеДанные.Выручка) КАК Выручка,
СУММА(ВТ_ВсеДанные.Себестоимость) КАК Себестоимость,
СУММА(ВТ_ВсеДанные.НДС) КАК НДС,
СУММА(ВТ_ВсеДанные.Выручка - ВТ_ВсеДанные.Себестоимость - ВТ_ВсеДанные.НДС) КАК Прибыль
ИЗ
ВТ_ВсеДанные КАК ВТ_ВсеДанные
СГРУППИРОВАТЬ ПО
ВТ_ВсеДанные.Номенклатура
Важная деталь: Обратите внимание, что в каждом из объединяемых запросов мы создаем одинаковое количество колонок, но "зануляем" те показатели, которые не относятся к текущему счету. При финальной группировке функция СУММА аккуратно складывает эти нули с реальными значениями из других пакетов, формируя красивую итоговую строку для каждого товара.
---
Если ваш отчет разрабатывается на базе Системы компоновки данных (СКД), вы можете максимально упростить текст запроса, переложив всю работу по формированию колонок и строк на саму систему компоновки.
Для этого в СКД достаточно написать простейший плоский запрос, выбирающий данные "как есть":
ВЫБРАТЬ
ХозрасчетныйОбороты.Субконто1 КАК Номенклатура,
ХозрасчетныйОбороты.Счет КАК Счет,
ВЫБОР
КОГДА ХозрасчетныйОбороты.Счет.Вид = ЗНАЧЕНИЕ(ВидСчета.Активный)
ТОГДА ХозрасчетныйОбороты.СуммаОборотДт
ИНАЧЕ ХозрасчетныйОбороты.СуммаОборотКт
КОНЕЦ КАК СуммаОборот
ИЗ
РегистрБухгалтерии.Хозрасчетный.Обороты(, , , Счет В (&СписокСчетов), , , , ) КАК ХозрасчетныйОбороты
После этого перейдем к настройке структуры отчета на вкладке "Настройки" в схеме компоновки данных:
СуммаОборот в список Ресурсов со стандартным выражением Сумма(СуммаОборот).Номенклатура.Счет.При формировании отчета СКД автоматически создаст кросс-таблицу. В строках будут выведены ваши товары (номенклатура), в колонках — счета (90.01, 90.02, 90.03), а на их пересечении отобразятся корректные суммы оборотов. Этот вариант требует минимум кода и отлично подходит для динамических отчетов, где состав счетов может меняться пользователем в настройках.
---
При реализации любого из предложенных методов в реальной базе важно учитывать нюансы типовых конфигураций (например, 1С:Бухгалтерия предприятия 3.0).
На субсчетах счета 90 "Продажи" по умолчанию нет субконто "Номенклатура". Вся аналитика там ведется исключительно по укрупненным "Номенклатурным группам" (видам деятельности). Если в вашем отчете требуется анализировать продажи именно до конкретного товара (номенклатурной позиции), у вас есть два пути:
ВыручкаИСебестоимостьПродаж). Работать с ними гораздо проще и быстрее, так как в них всегда присутствует измерение "Номенклатура".Применяйте данные подходы в зависимости от требований к производительности вашей системы и архитектуры конфигурации!