При работе с запросами в системе 1С:Предприятие программисты часто сталкиваются с ситуацией, когда результат выборки кажется случайным или нелогичным. Одной из классических проблем является работа с производственным календарем, когда при попытке получить «следующие N рабочих дней» в выборку внезапно попадает дата из конца года (например, 28 декабря 2024 года), хотя исходная дата была установлена на середину лета.
В этой статье мы подробно разберем, почему СУБД возвращает данные именно в таком порядке, как работает функция АВТОНОМЕРЗАПИСИ и как правильно составить запрос, чтобы результат был детерминированным и корректным.
Рассмотрим ситуацию на конкретном примере. Программист пытается получить 13 рабочих дней, начиная с определенной даты, используя временную таблицу и нумерацию строк. Код исходного запроса выглядит следующим образом:
ВЫБРАТЬ ПЕРВЫЕ 13
ДанныеПроизводственногоКалендаря.Дата КАК ДатаКалендаря,
АВТОНОМЕРЗАПИСИ() КАК Номер
ПОМЕСТИТЬ ВТ_РабочиеДни
ИЗ
РегистрСведений.ДанныеПроизводственногоКалендаря КАК ДанныеПроизводственногоКалендаря
ГДЕ
ДанныеПроизводственногоКалендаря.ВидДня = &ВидДня_РабочийДень
И ДанныеПроизводственногоКалендаря.Дата >= &ИсходнаяДата;
При выполнении этого кода, если параметр &ИсходнаяДата равен 15.07.2024, в таблицу ВТ_РабочиеДни под номером 1 внезапно попадает 28.12.2024. Это ломает всю дальнейшую логику расчетов. Проанализируем, почему это происходит.
Самая важная деталь, которую необходимо запомнить: в языке SQL (и в запросах 1С) порядок строк в результате не гарантирован, если не указано предложение УПОРЯДОЧИТЬ ПО.
Когда мы пишем ВЫБРАТЬ ПЕРВЫЕ 13 без сортировки, мы фактически говорим системе: «Дай мне любые 13 записей, которые первыми попадутся тебе под руку при сканировании таблицы». СУБД (будь то MS SQL Server, PostgreSQL или любая другая) возвращает данные в том порядке, в котором они физически расположены в файлах базы данных или в соответствии с выбранным индексом.
Рассмотрим подробнее, почему именно 28 декабря становится «первым». В 2024 году 28 декабря — это рабочая суббота (перенос выходного дня). В типовых конфигурациях 1С при заполнении производственного календаря записи о переносах или ручных корректировках часто записываются в таблицу базы данных отдельно от основного массива дат. В итоге физический адрес этой записи в таблице может оказаться в самом начале или в месте, которое индекс сканирует первым. Без команды УПОРЯДОЧИТЬ ПО планировщик запросов SQL выбирает наиболее «дешевый» путь получения 13 строк, и дата 28.12.2024 просто оказывается в их числе.
Функция АВТОНОМЕРЗАПИСИ в 1С тесно связана с тем порядком, в котором записи поступают из СУБД. Она присваивает порядковый номер строке в момент её формирования. Если в запросе нет сортировки, то номер 1 получит та строка, которая первой пришла из базы данных.
Выясним причину зависимости от кэша: иногда запрос может работать правильно несколько раз подряд, а на следующий день выдать ошибку. Это происходит из-за того, что СУБД может хранить план запроса и результаты в кэше. После перезагрузки сервера или очистки кэша 1С физический порядок чтения данных может измениться, и «случайная» дата снова вылезет в топ.
Для того чтобы запрос всегда возвращал хронологически верные данные, необходимо добавить явное упорядочивание в тот момент, когда мы формируем выборку ПЕРВЫЕ 13. Исправим наш запрос, добавив УПОРЯДОЧИТЬ ПО непосредственно перед помещением во временную таблицу.
Разберем по шагам правильную структуру:
ВЫБРАТЬ ПЕРВЫЕ 13
ДанныеПроизводственногоКалендаря.Дата КАК ДатаКалендаря
ПОМЕСТИТЬ ВТ_РабочиеДни
ИЗ
РегистрСведений.ДанныеПроизводственногоКалендаря КАК ДанныеПроизводственногоКалендаря
ГДЕ
ДанныеПроизводственногоКалендаря.ВидДня = &ВидДня_РабочийДень
И ДанныеПроизводственногоКалендаря.Дата >= &ИсходнаяДата
УПОРЯДОЧИТЬ ПО
ДанныеПроизводственногоКалендаря.Дата; // Обязательная сортировка
В этом случае платформа 1С сначала отсортирует все рабочие дни по возрастанию даты, начиная от &ИсходнаяДата, и только потом возьмет первые 13. Теперь 28.12.2024 окажется в конце списка (если он вообще туда попадет), а не в начале.
Как отмечают эксперты, выборка из регистра сведений может быть не самой оптимальной, если данных очень много. Рассмотрим альтернативный метод: создание временной таблицы с «запасом» дней в коде 1С и последующее соединение с календарем.
Проанализируем ситуацию: нам нужно 13 рабочих дней. Максимальное количество подряд идущих выходных (например, в январе) обычно не превышает 10. Значит, если мы возьмем период в 30 календарных дней, мы гарантированно найдем в нем 13 рабочих.
ДанныеПроизводственногоКалендаря.ПЕРВЫЕ 13.Такой подход работает быстрее на больших базах, так как СУБД использует индекс по конкретному списку дат, а не сканирует диапазон «от даты и до бесконечности».
Подводя итог, выделим главные правила написания надежных запросов в 1С:
АВТОНОМЕРЗАПИСИ — это лишь инструмент нумерации. Она не создает порядок сама по себе, а лишь фиксирует тот порядок, который был задан в УПОРЯДОЧИТЬ ПО.Посмотрим на финальный пример кода, который является наиболее устойчивым и правильным для решения задачи получения 13 рабочих дней:
ВЫБРАТЬ ПЕРВЫЕ 13
Календарь.Дата КАК ДатаРабочая
ИЗ
РегистрСведений.ДанныеПроизводственногоКалендаря КАК Календарь
ГДЕ
Календарь.Дата >= &ИсходнаяДата
И Календарь.ВидДня = ЗНАЧЕНИЕ(Перечисление.ВидыДнейПроизводственногоКалендаря.Рабочий)
// Если используете конкретный календарь, добавьте условие по нему
И Календарь.ПроизводственныйКалендарь = &Календарь
УПОРЯДОЧИТЬ ПО
ДатаРабочая ВОЗР;
Использование этого шаблона гарантирует, что никакие «случайные» рабочие субботы из конца года не испортят ваши расчеты, а программа будет работать стабильно независимо от настроек конкретного сервера базы данных.