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

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

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

Способ 1. Использование конструкции ИТОГИ ПЕРИОДАМИ

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

Основная хитрость заключается не только в тексте запроса, но и в способе обработки его результата в коде 1С. Посмотрим на пример кода:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |    КОЛИЧЕСТВО(РАЗЛИЧНЫЕ КорректировкаРеализации.Ссылка) КАК Количество,
    |    НАЧАЛОПЕРИОДА(КорректировкаРеализации.Дата, МЕСЯЦ) КАК МесяцДоков
    |ИЗ
    |    Документ.КорректировкаРеализации КАК КорректировкаРеализации
    |ГДЕ
    |    КорректировкаРеализации.Дата МЕЖДУ &ДатаНачала И &ДатаОкончания
    |
    |СГРУППИРОВАТЬ ПО
    |    НАЧАЛОПЕРИОДА(КорректировкаРеализации.Дата, МЕСЯЦ)
    |
    |УПОРЯДОЧИТЬ ПО
    |    МесяцДоков
    |ИТОГИ
    |    СУММА(Количество)
    |ПО
    |    МесяцДоков ПЕРИОДАМИ(МЕСЯЦ, &ДатаНачала, &ДатаОкончания)";

Запрос.УстановитьПараметр("ДатаНачала", Объект.ДатаНачала);
Запрос.УстановитьПараметр("ДатаОкончания", Объект.ДатаОкончания);

РезультатЗапроса = Запрос.Выполнить();
// Ключевой момент: использование параметра "Все" в методе Выбрать
ВыборкаМесяцДоков = РезультатЗапроса.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам, "МесяцДоков", "Все");

Пока ВыборкаМесяцДоков.Следующий() Цикл
    // Здесь мы получим даже те месяцы, где Количество равно NULL или 0
    Сообщить("Месяц: " + ВыборкаМесяцДоков.МесяцДоков + " Кол-во: " + ВыборкаМесяцДоков.Количество);
КонецЦикла;

Проанализируем ситуацию: в блоке ИТОГИ мы указали ключевое слово ПЕРИОДАМИ. Оно сообщает платформе, что группировка по полю МесяцДоков должна быть дополнена всеми календарными периодами между &ДатаНачала и &ДатаОкончания. Но будьте внимательны: если вы просто вызовете РезультатЗапроса.Выбрать(), пустые месяцы не появятся. Обязательно нужно указать третий параметр "Все" в методе Выбрать().

Способ 2. Создание таблицы периодов через левое соединение

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

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

  1. Создадим временную таблицу чисел (от 0 до 9).
  2. Перемножим её саму на себя, чтобы получить последовательность (0-99, 0-999 и т.д.).
  3. Используем функцию ДОБАВИТЬКДАТЕ, чтобы превратить числа в даты месяцев.

Посмотрим на пример такого порождающего запроса:


"ВЫБРАТЬ 0 КАК Цифра ПОМЕСТИТЬ ВТ_Цифры ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 1 ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 2 
// ... и так далее до 9
;
ВЫБРАТЬ
    ДОБАВИТЬКДАТЕ(&ДатаНачала, МЕСЯЦ, т1.Цифра + т2.Цифра * 10) КАК МесяцПериода
ПОМЕСТИТЬ ВТ_Календарь
ИЗ
    ВТ_Цифры КАК т1
    КРОСС СОЕДИНЕНИЕ ВТ_Цифры КАК т2
ГДЕ
    ДОБАВИТЬКДАТЕ(&ДатаНачала, МЕСЯЦ, т1.Цифра + т2.Цифра * 10) <= &ДатаОкончания
;
ВЫБРАТЬ
    Календарь.МесяцПериода КАК Месяц,
    ЕСТЬNULL(КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Док.Ссылка), 0) КАК Количество
ИЗ
    ВТ_Календарь КАК Календарь
    ЛЕВОЕ СОЕДИНЕНИЕ Документ.КорректировкаРеализации КАК Док
    ПО (НАЧАЛОПЕРИОДА(Док.Дата, МЕСЯЦ) = Календарь.МесяцПериода)
    И (Док.Дата МЕЖДУ &ДатаНачала И &ДатаОкончания)
СГРУППИРОВАТЬ ПО
    Календарь.МесяцПериода"

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

Способ 3. Использование производственного календаря

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

Рассмотрим по шагам этот вариант:

  1. Обращаемся к регистру сведений за указанный период.
  2. Группируем записи по НАЧАЛОПЕРИОДА(Дата, МЕСЯЦ).
  3. Соединяем результат с нашей таблицей документов.

Этот метод считается более «элегантным», так как не требует искусственного порождения строк через кросс-соединения цифр. Однако он накладывает зависимость на заполнение календаря в базе. Если календарь на следующий год не заполнен, отчет за этот период будет пустым.

Способ 4. Передача таблицы периодов как параметра

Иногда логику формирования временной шкалы проще вынести в программный код. Мы можем создать ТаблицаЗначений в коде 1С, заполнить её нужными датами в цикле и передать в запрос.

Разберем пример реализации:


ТЗ_Периоды = Новый ТаблицаЗначений;
ТЗ_Периоды.Колонки.Добавить("Месяц", Новый ОписаниеТипов("Дата"));

ТекДата = НачалоМесяца(Отчет.ДатаНачала);
Пока ТекДата <= Отчет.ДатаОкончания Цикл
    НоваяСтрока = ТЗ_Периоды.Добавить();
    НоваяСтрока.Месяц = ТекДата;
    ТекДата = ДобавитьМесяцу(ТекДата, 1);
КонецЦикла;

Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ * ПОМЕСТИТЬ ВТ_Периоды ИЗ &ТЗ_Периоды КАК ТЗ;
    |ВЫБРАТЬ Периоды.Месяц, КОЛИЧЕСТВО(Док.Ссылка) ИЗ ВТ_Периоды КАК Периоды
    |ЛЕВОЕ СОЕДИНЕНИЕ Документ.КорректировкаРеализации КАК Док ПО ...";
Запрос.УстановитьПараметр("ТЗ_Периоды", ТЗ_Периоды);

Проанализируем этот подход: он максимально прозрачен и легко отлаживается. Вы точно знаете, какие месяцы попали в запрос. Использование временной таблицы из параметра &ТЗ_Периоды — отличный способ объединить мощь встроенного языка и гибкость SQL-запросов.

Резюмируем: если вам нужен просто вывод в отчет или консоль — используйте ИТОГИ ПЕРИОДАМИ и метод Выбрать(,, "Все"). Если данные нужны для дальнейшей сложной обработки внутри запроса — выбирайте ЛЕВОЕ СОЕДИНЕНИЕ с предварительно сформированной таблицей периодов (через кросс-соединение цифр или передачу Таблицы Значений).

← На главную