При разработке и сопровождении конфигураций на платформе 1С 7.7 нередко возникает задача программного поиска максимального номера документа определенного вида. На первый взгляд задача кажется тривиальной, однако специфика платформы накладывает ряд ограничений, которые необходимо учитывать для получения корректного результата.
Разберем основные особенности платформы и выясним, почему типовые подходы могут возвращать неверные данные:
ВыбратьДокументы() считывает данные строго по оси времени (по дате и времени создания). Последний по дате документ далеко не всегда является документом с наибольшим номером, особенно если документы вводились «задним числом» или номера корректировались пользователями вручную. Метод ОбратныйПорядок(1) лишь разворачивает выборку по времени, но не сортирует документы по номеру._1SJOURN) поле номера документа DOCNO всегда хранится в виде строки фиксированной длины. Даже если в конфигураторе для нумератора установлен числовой тип, на уровне базы данных это строковое поле. Лексикографическое сравнение строк дает результат, отличный от математического: например, строка "2" при обычном сравнении будет больше строки "10". Поэтому при поиске максимума строковые номера обязательно требуется приводить к числу.Новый() и вызвать УстановитьНовыйНомер() для определения последнего значения является ненадежной в многопользовательском режиме. Платформа 1С 7.7 использует системный механизм блокировок (таблицу 1SDNLOCK). Если в этот момент другие пользователи открыли форму нового документа, но еще не записали его, счетчик уже сдвинется. Если же пользователь закроет документ без сохранения, в нумерации образуется пропуск, а метод вернет завышенный номер, которого физически нет в базе.Рассмотрим рабочие и проверенные способы решения этой задачи в зависимости от используемой архитектуры базы данных.
Если объем документов за расчетный период невелик, наиболее простым и надежным решением без использования внешних компонент является однопроходный цикл по выборке документов с явным приведением номера к числовому типу через встроенную функцию Число().
Разберем пример программного кода:
Функция ПолучитьМаксимальныйНомерДокумента(ВидДокумента, ДатаНачала, ДатаОкончания)
Перем Док, МаксНомер, ТекНомер;
МаксНомер = 0;
Док = СоздатьОбъект("Документ." + ВидДокумента);
// Ограничиваем выборку рамками периода нумерации
Док.ВыбратьДокументы(ДатаНачала, ДатаОкончания);
Пока Док.ПолучитьДокумент() = 1 Цикл
// Приводим строковый номер к числу для корректного сравнения
ТекНомер = Число(Док.НомерДок);
Если ТекНомер > МаксНомер Тогда
МаксНомер = ТекНомер;
КонецЕсли;
КонецЦикла;
Возврат МаксНомер;
КонецФункции
Проанализируем работу этого алгоритма: метод выполняет линейный проход по документам за заданный период с временной сложностью O(N), не расходуя лишнюю оперативную память на промежуточные коллекции, и гарантированно находит максимальное числовое значение среди фактически записанных документов.
Если полученный список документов или номеров требуется использовать для дальнейшего анализа, отображения в интерфейсе или выгрузки, удобно применить объект ТаблицаЗначений.
Посмотрим на реализацию данного подхода:
ТЗ = СоздатьОбъект("ТаблицаЗначений");
ТЗ.НоваяКолонка("Документ", "Документ");
ТЗ.НоваяКолонка("НомерЧислом", "Число", 10, 0);
Док = СоздатьОбъект("Документ.РасходнаяНакладная");
Док.ВыбратьДокументы(НачГода(РабочаяДата()), КонГода(РабочаяДата()));
Пока Док.ПолучитьДокумент() = 1 Цикл
ТЗ.НоваяСтрока();
ТЗ.Документ = Док.ТекущийДокумент();
ТЗ.НомерЧислом = Число(Док.НомерДок);
КонецЦикла;
// Сортируем таблицу по убыванию числового номера
ТЗ.Сортировать("НомерЧислом-");
Если ТЗ.КоличествоСтрок() > 0 Тогда
МаксимальныйНомер = ТЗ.ПолучитьЗначение(1, "НомерЧислом");
Сообщить("Максимальный номер: " + МаксимальныйНомер);
Иначе
Сообщить("За выбранный период документы не найдены.");
КонецЕсли;
В этом примере мы создаем явную числовую колонку НомерЧислом. Метод Сортировать("НомерЧислом-") выполняет быструю сортировку по убыванию, благодаря чему максимальный номер всегда оказывается в первой строке таблицы.
В клиент-серверном варианте работы (1С 7.7 на MS SQL Server) для высоконагруженных баз с сотнями тысяч документов перебор через метаданные может выполняться медленно. Наиболее производительным решением в таком случае является выполнение прямого SQL-запроса к таблице журнала документов _1SJOURN через внешние компоненты (например, 1С++ / ODBCRecordset).
Разберем структуру SQL-запроса для получения максимального номера:
// Пример SQL-запроса через прямой доступ к БД
// Идентификатор вида документа (IDDOCDEF) и диапазон дат передаются параметрами
ТекстЗапроса = "
|SELECT
| MAX(CAST(DOCNO AS NUMERIC(10, 0))) AS MaxDocNo
|FROM
| _1SJOURN (NOLOCK)
|WHERE
| IDDOCDEF = :ИДВидаДокумента
| AND DATE_TIME_IDDOC >= :НачалоПериода
| AND DATE_TIME_IDDOC <= :КонецПериода
| AND ISMARK = 0
|";
Рассмотрим ключевые моменты этого запроса:
CAST(DOCNO AS NUMERIC(10, 0)) выполняет преобразование строкового поля базы в числовой тип на стороне СУБД перед выполнением агрегатной функции MAX().IDDOCDEF ограничивает выборку только нужным видом документа.DATE_TIME_IDDOC обеспечивает поиск строго в пределах текущего периода нумерации.ISMARK = 0 исключает из расчета помеченные на удаление документы (при необходимости это условие можно скорректировать).Использование прямого запроса к СУБД позволяет получить результат практически мгновенно, не нагружая сервер приложений выборкой тяжелых объектных структур платформы.