Перед нами стоит задача: используя запросы 1С, сформировать таблицу, которая будет содержать даты начала и конца каждого дня в указанном диапазоне, ограниченном НачДата и КонДата. Эта задача является одной из часто встречающихся в разработке отчетов, аналитических инструментов или обработок, где требуется выполнить какие-либо действия для каждого дня в определенном периоде. Рассмотрим несколько подходов к решению этой проблемы, от самых простых, опирающихся на существующие в конфигурации данные, до более универсальных и программных методов.
Одним из первых методов, который был предложен на форуме, является соединение с производственным календарём. Этот способ может оказаться весьма удобным, если в вашей информационной базе уже ведется и регулярно обновляется производственный календарь, содержащий все календарные дни года.
Суть метода: Мы можем обратиться к данным производственного календаря, который в конфигурациях 1С обычно представлен как регистр сведений и содержит записи для каждого дня года, с указанием его типа (рабочий, выходной, праздничный). Соединяясь с этой таблицей, мы можем легко выбрать все необходимые нам дни в заданном диапазоне.
Реализация в запросе: Для реализации этого подхода нам понадобится виртуальная таблица РегистрСведений.ПроизводственныйКалендарь.ДниКалендаря. Важно, что в параметрах этой виртуальной таблицы мы можем указать требуемый период, что позволит нам получить только нужные даты, не перегружая запрос избыточными данными.
ВЫБРАТЬ
ДниКалендаря.ДатаДень КАК Дата,
НАЧАЛОПЕРИОДА(ДниКалендаря.ДатаДень, ДЕНЬ) КАК НачалоДня,
КОНЕЦПЕРИОДА(ДниКалендаря.ДатаДень, ДЕНЬ) КАК КонецДня
ИЗ
РегистрСведений.ПроизводственныйКалендарь.ДниКалендаря(&НачДата, &КонДата) КАК ДниКалендаря
В данном примере &НачДата и &КонДата выступают в качестве параметров вашего запроса, которые будут передаваться в виртуальную таблицу, определяя границы интересующего нас диапазона.
Преимущества: Главными достоинствами этого метода являются его простота в реализации, особенно при уже существующем и корректно заполненном производственном календаре, а также отсутствие необходимости в сложных конструкциях для генерации последовательности чисел вручную.
Недостатки: Самым критическим недостатком является необходимость постоянного контроля за актуальностью и полнотой заполнения производственного календаря. Если календарь не актуален (например, не заполнен на будущие годы) или содержит пробелы, запрос может вернуть неполные или некорректные данные. Кроме того, этот метод может быть избыточным, если нам нужны именно *все* дни в диапазоне, а не только рабочие или выходные, которые обычно являются основной целью ведения производственного календаря.
Этот подход является значительно более универсальным, поскольку он полностью независим от наличия, актуальности или корректности заполнения производственного календаря. Он базируется на принципе генерации последовательности чисел непосредственно в запросе, а затем использовании этих чисел для формирования последовательности дат. Участники форума обозначили его как "умножение таблиц (получение чисел из цифр) +добавить к дате +начало периода +конец периода". Разберем этот метод по шагам.
Для генерации последовательности чисел мы будем использовать технику декартова произведения (или перемножения) небольших временных таблиц. Нам нужно получить числа от 0 до необходимого количества дней в периоде. Для этого мы можем создать несколько временных таблиц, каждая из которых содержит цифры от 0 до 9, а затем комбинировать их. Такой подход позволяет получить достаточно длинную последовательность чисел. Например, для диапазона до 9999 дней (что обычно покрывает период в несколько лет) нам потребуется четыре таких таблицы.
Базовая таблица для цифр: В первую очередь, сформируем простую временную таблицу, содержащую цифры от 0 до 9. Это будет наш строительный блок.
ВЫБРАТЬ 0 КАК Число
ПОМЕСТИТЬ Цифры
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ 1
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ 2
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ 3
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ 4
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ 5
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ 6
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ 7
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ 8
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ 9;
Эта временная таблица Цифры будет содержать 10 строк, каждая с одной цифрой.
Генерация последовательности чисел: Теперь, когда у нас есть базовая таблица, мы можем использовать ее для генерации более длинной последовательности чисел. Применяя декартово произведение этой таблицы с самой собой несколько раз, мы формируем числа. Каждая дополнительная таблица увеличивает максимальное генерируемое число в 10 раз.
ВЫБРАТЬ
(С1.Число * 1000 + С2.Число * 100 + С3.Число * 10 + С4.Число) КАК НомерДня
ПОМЕСТИТЬ ПоследовательностьДней
ИЗ
Цифры КАК С1,
Цифры КАК С2,
Цифры КАК С3,
Цифры КАК С4
ГДЕ
(С1.Число * 1000 + С2.Число * 100 + С3.Число * 10 + С4.Число) <= РАЗНОСТЬДАТ(&НачДата, &КонДата, ДЕНЬ);
В этом запросе мы выполняем декартово произведение таблицы Цифры четыре раза (С1, С2, С3, С4), что позволяет нам сгенерировать числа от 0 до 9999. Формула (С1.Число * 1000 + С2.Число * 100 + С3.Число * 10 + С4.Число) формирует уникальное число из каждой комбинации цифр.
Функция РАЗНОСТЬДАТ(&НачДата, &КонДата, ДЕНЬ) вычисляет количество полных дней между начальной и конечной датами. Например, для диапазона с 01.01.2023 по 03.01.2023 эта функция вернет 2. Поскольку мы генерируем НомерДня, начиная с 0, последовательность 0, 1, 2 соответствует 3 дням, что точно соответствует количеству дней в диапазоне, включая обе граничные даты.
Если вам потребуется сгенерировать большее количество дней (например, до 100 000), вы можете добавить ещё одну временную таблицу Цифры КАК С5 и соответствующим образом модифицировать формулу для НомерДня, например: (С1.Число * 10000 + С2.Число * 1000 + С3.Число * 100 + С4.Число * 10 + С5.Число).
После успешной генерации временной таблицы ПоследовательностьДней, содержащей числа, представляющие собой индексы дней (от 0 до числа дней в периоде минус 1), мы можем использовать эти индексы для добавления их к начальной дате. Таким образом, мы получим каждую дату в заданном диапазоне.
Добавление чисел к начальной дате: Для этой операции мы воспользуемся функцией ДОБАВИТЬКДАТЕ, которая позволяет прибавлять определенное количество интервалов (в нашем случае, дней) к заданной дате.
ВЫБРАТЬ
ДОБАВИТЬКДАТЕ(&НачДата, ДЕНЬ, ПоследовательностьДней.НомерДня) КАК Дата
ПОМЕСТИТЬ ВсеДаты
ИЗ
ПоследовательностьДней КАК ПоследовательностьДней
ГДЕ
ДОБАВИТЬКДАТЕ(&НачДата, ДЕНЬ, ПоследовательностьДней.НомерДня) <= &КонДата;
Здесь мы используем ДОБАВИТЬКДАТЕ(&НачДата, ДЕНЬ, ПоследовательностьДней.НомерДня) для получения каждой даты. Условие ДОБАВИТЬКДАТЕ(&НачДата, ДЕНЬ, ПоследовательностьДней.НомерДня) <= &КонДата служит дополнительной гарантией того, что мы не выйдем за пределы конечной даты, хотя предыдущее условие генерации НомерДня уже должно было это обеспечить. Это также важно, если вы генерируете числа "с запасом" и хотите точно обрезать до &КонДата.
Имея сформированную последовательность дат во временной таблице ВсеДаты, нам остался последний шаг — определить точное начало и конец каждого дня, как это было изначально запрошено автором темы. Для этого мы используем стандартные функции 1С: НАЧАЛОПЕРИОДА и КОНЕЦПЕРИОДА.
ВЫБРАТЬ
НАЧАЛОПЕРИОДА(ВсеДаты.Дата, ДЕНЬ) КАК НачалоДня,
КОНЕЦПЕРИОДА(ВсеДаты.Дата, ДЕНЬ) КАК КонецДня
ИЗ
ВсеДаты КАК ВсеДаты
УПОРЯДОЧИТЬ ПО
НачалоДня;
Функция НАЧАЛОПЕРИОДА(<Дата>, ДЕНЬ) возвращает дату и время, установленное на 00:00:00 указанного дня. Аналогично, КОНЕЦПЕРИОДА(<Дата>, ДЕНЬ) возвращает дату и время, установленное на 23:59:59 указанного дня. Это позволяет нам получить точные границы каждого дня в сформированной последовательности.
Таким образом, полный запрос с использованием подхода программной генерации чисел будет представлять собой последовательность из трех временных таблиц, выполняемых в рамках одного запроса:
////////////////////////////////////////////////////////////////////////////////
// Шаг 1: Создаем временную таблицу с цифрами от 0 до 9.
// Эта таблица будет использоваться как основа для генерации чисел.
ВЫБРАТЬ 0 КАК Число
ПОМЕСТИТЬ Цифры
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ 1
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ 2
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ 3
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ 4
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ 5
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ 6
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ 7
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ 8
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ 9;
////////////////////////////////////////////////////////////////////////////////
// Шаг 2: Генерируем последовательность чисел (индексов дней) от 0
// до количества дней в заданном диапазоне минус 1.
// Для этого используется декартово произведение таблицы "Цифры".
ВЫБРАТЬ
(С1.Число * 1000 + С2.Число * 100 + С3.Число * 10 + С4.Число) КАК НомерДня
ПОМЕСТИТЬ ПоследовательностьДней
ИЗ
Цифры КАК С1,
Цифры КАК С2,
Цифры КАК С3,
Цифры КАК С4
ГДЕ
// Ограничиваем количество генерируемых чисел до необходимого диапазона.
// РАЗНОСТЬДАТ возвращает количество интервалов,
// поэтому для включения КонДата нужно (НачДата + НомерДня)
(С1.Число * 1000 + С2.Число * 100 + С3.Число * 10 + С4.Число) <= РАЗНОСТЬДАТ(&НачДата, &КонДата, ДЕНЬ);
////////////////////////////////////////////////////////////////////////////////
// Шаг 3: Вычисляем начальные и конечные даты для каждого дня
// на основе сгенерированных номеров и начальной даты.
ВЫБРАТЬ
НАЧАЛОПЕРИОДА(ДОБАВИТЬКДАТЕ(&НачДата, ДЕНЬ, ПоследовательностьДней.НомерДня), ДЕНЬ) КАК НачалоДня,
КОНЕЦПЕРИОДА(ДОБАВИТЬКДАТЕ(&НачДата, ДЕНЬ, ПоследовательностьДней.НомерДня), ДЕНЬ) КАК КонецДня
ИЗ
ПоследовательностьДней КАК ПоследовательностьДней
ГДЕ
// Фильтруем, чтобы окончательно убедиться, что даты не превышают КонДата.
ДОБАВИТЬКДАТЕ(&НачДата, ДЕНЬ, ПоследовательностьДней.НомерДня) <= &КонДата
УПОРЯДОЧИТЬ ПО
НачалоДня;
Этот подход позволяет получить последовательность до 10 000 дней (и более, если добавить дополнительные перемножения таблиц), что в большинстве практических сценариев полностью покрывает потребности в генерации дат для аналитических или операционных задач. Его главное преимущество — это самодостаточность и независимость от внешних данных или настроек.
Один из участников дискуссии справедливо отметил существование генератора запросов в консоли запросов ИР (Инструменты Разработчика). Это мощный и удобный инструмент, который может автоматически создавать шаблоны запросов, в том числе и для генерации последовательности дат. Если вы активно используете Инструменты Разработчика или аналогичные расширения, вы можете значительно упростить себе жизнь, воспользовавшись их встроенными возможностями.
Принцип работы: Обычно такие генераторы предоставляют пользовательский интерфейс, где вы можете указать начальную и конечную дату, а затем автоматически формируют SQL-подобный текст запроса. Этот запрос, как правило, основан на тех же принципах, что и "Подход 2" (генерация последовательности чисел), но избавляет разработчика от необходимости вручную писать сложную конструкцию с декартовым произведением.
Преимущества: Ключевыми преимуществами являются быстрота получения готового, рабочего запроса и минимизация ошибок, которые могут возникнуть при ручном написании сложной логики. Это особенно полезно для разработчиков, которые не хотят каждый раз изобретать велосипед.
Недостатки: Основной недостаток — это зависимость от наличия и конкретной версии инструмента (Консоль запросов ИР). Кроме того, для новичков может быть не сразу очевидно, как использовать этот функционал, хотя обычно интерфейс интуитивно понятен.
При работе с запросами, особенно когда речь идет о генерации данных или обработке больших объемов информации, всегда следует уделять внимание вопросам производительности и эффективности.
Использование временных таблиц: Мы уже активно применяли временные таблицы (ВТ) в нашем примере с генерацией чисел. Временные таблицы являются мощным инструментом для оптимизации сложных запросов. Они позволяют разбить комплексный запрос на несколько более мелких и управляемых этапов, сохраняя промежуточные результаты. Это не только улучшает читаемость кода, но и часто повышает производительность, так как система управления базой данных (СУБД) может более эффективно оптимизировать запросы, работающие с ВТ. В 1С управление временными таблицами в сложных сценариях может быть реализовано через объект МенеджерВременныхТаблиц.
Виртуальные таблицы: Как мы выяснили, виртуальные таблицы, такие как РегистрСведений.ПроизводственныйКалендарь.ДниКалендаря, представляют собой динамически формируемые структуры данных. Они не хранятся физически в базе данных, а генерируются "на лету" в момент выполнения запроса на основании переданных в них параметров. Это обеспечивает гибкость и эффективность, позволяя получать только нужные данные без излишних операций чтения из постоянных таблиц.
Без привязки к производственному календарю для всех дней: Если ваша задача состоит именно в получении всех календарных дней в заданном диапазоне, независимо от того, являются ли они рабочими, выходными или праздничными, то метод генерации числовой последовательности (Подход 2) является наиболее предпочтительным. Он полностью автономен, не требует проверки актуальности производственного календаря и гарантированно предоставляет полный набор дат в указанном интервале.
Выбирая подходящий подход, всегда учитывайте специфику вашей текущей задачи, требования к производительности, а также наличие и актуальность данных (например, заполненного производственного календаря) и используемых инструментов. Генерация последовательности чисел в запросе является наиболее универсальным и независимым методом, подходящим для подавляющего большинства случаев, когда требуется получить все дни в заданном диапазоне.