При разработке собственных механизмов нумерации в 1С разработчики часто сталкиваются с неожиданным поведением запросов: передав в параметр периода начало текущего года, запрос все равно возвращает прошлогоднюю запись. Разберем по шагам, почему виртуальная таблица СрезПоследних работает именно так, в чем заключается ошибка и как правильно составить запрос к регистру сведений для вычисления максимального номера.
Проанализируем исходный запрос разработчика:
Запрос = Новый Запрос;
Запрос.УстановитьПараметр("ВыбПериод", НачалоГода(Источник.Дата));
Запрос.УстановитьПараметр("ВыбСобственник", Источник.Кос_Собственник);
Запрос.УстановитьПараметр("ВыбУдаленный", Ложь);
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1
| Кос_НомераСчетовСрезПоследних.НомерЧислом
|ИЗ
| РегистрСведений.Кос_НомераСчетов.СрезПоследних(
| &ВыбПериод,
| Собственник = &ВыбСобственник
| И Удаленный = &ВыбУдаленный) КАК Кос_НомераСчетовСрезПоследних
|
|УПОРЯДОЧИТЬ ПО
| Кос_НомераСчетовСрезПоследних.НомерЧислом УБЫВ";
Главное заблуждение заключается в понимании параметра Период виртуальной таблицы среза. Этот параметр не задает интервал отбора, а определяет точку во времени (момент среза), на которую вычисляется актуальное состояние регистра.
Выясним причину попадания прошлогодней записи в результат:
&ВыбПериод значение НачалоГода(Источник.Дата) (например, 01.01.2024 00:00:00), платформа ищет самую актуальную запись, сделанную на эту дату или ранее.СрезПоследних группирует данные по уникальным комбинациям измерений регистра. Она возвращает самую свежую по времени запись для конкретной комбинации измерений, а вовсе не строку с максимальным значением ресурса НомерЧислом.Если перед нами стоит задача имитации нумератора — найти максимальный номер с отбором по собственнику и за конкретный год — использовать срез последних категорически не подходит. Обратимся напрямую к физической (реальной) таблице регистра сведений РегистрСведений.Кос_НомераСчетов.
Посмотрим на пример правильного запроса с агрегацией:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ЕСТЬNULL(МАКСИМУМ(Кос_НомераСчетов.НомерЧислом), 0) КАК МаксимальныйНомер
|ИЗ
| РегистрСведений.Кос_НомераСчетов КАК Кос_НомераСчетов
|ГДЕ
| Кос_НомераСчетов.Период МЕЖДУ &НачалоПериода И &КонецПериода
| И Кос_НомераСчетов.Собственник = &ВыбСобственник
| И Кос_НомераСчетов.Удаленный = &ВыбУдаленный";
Запрос.УстановитьПараметр("НачалоПериода", НачалоГода(Источник.Дата));
Запрос.УстановитьПараметр("КонецПериода", КонецГода(Источник.Дата));
Запрос.УстановитьПараметр("ВыбСобственник", Источник.Кос_Собственник);
Запрос.УстановитьПараметр("ВыбУдаленный", Ложь);
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
СледующийНомер = Выборка.МаксимальныйНомер + 1;
КонецЕсли;
Разберем ключевые детали этой реализации:
Период МЕЖДУ &НачалоПериода И &КонецПериода ограничивает выборку рамками нужного года. Значение КонецГода(...) возвращает точное окончание года (31.12.ГГГГ 23:59:59).ГОД(Период) = &Год. Применение функций к полям таблиц в блоке ГДЕ мешает СУБД использовать стандартные индексы по полю Период, что провоцирует полное сканирование таблицы (Full Table Scan).МАКСИМУМ: Вместо конструкции ВЫБРАТЬ ПЕРВЫЕ 1 ... УПОРЯДОЧИТЬ ПО ... УБЫВ эффективнее использовать агрегатную функцию МАКСИМУМ. Обертка ЕСТЬNULL(..., 0) сразу возвращает 0, если записей за текущий год еще нет, избавляя от лишних проверок на NULL в коде.ГДЕ (Собственник, Удаленный), должны быть измерениями регистра либо иметь включенное свойство Индексировать, чтобы запрос выполнялся быстро даже при миллионах строк.Рассмотрим риски, возникающие при имитации нумератора через запросы. В многопользовательской среде простой поиск максимального номера с последующей записью нового значения в регистр подвержен «состоянию гонки» (Race Condition). Если два пользователя одновременно создадут документ, оба запроса прочитают один и тот же максимальный номер и попытаются создать дубликаты.
Для предотвращения коллизий рекомендуем учесть следующие архитектурные варианты:
БлокировкаДанных по измерениям регистра и периоду.Собственник, Год; ресурс: ТекущийНомер). При генерации номера в транзакции считывается число, прибавляется единица и перезаписывается обратно в регистр. Это исключает перебор исторических данных.