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