Рады приветствовать вас, уважаемые коллеги! Сегодня мы детально и по шагам разберем одну очень распространенную практическую задачу, с которой часто сталкиваются разработчики на платформе «1С:Предприятие 8». Представьте ситуацию: в вашей конфигурации есть бизнес-процесс, на основании которого пользователи создают подчиненные документы (например, ОценкаЗначимостиРиска). Логика системы требует, чтобы при попытке создания нового документа программа проверяла, существуют ли уже созданные ранее документы по этому бизнес-процессу. Если они есть, то вместо создания очередной пустой карточки система должна автоматически перенаправить пользователя и открыть последний актуальный (самый свежий по дате) документ.
В этой статье мы подробно проанализируем, как правильно решить эту задачу, избежав типичных ошибок производительности, разберем, почему попытки обойтись «без запросов» вредят вашей базе данных, и напишем красивый, оптимизированный код для управляемых форм.
Давайте сначала проанализируем код, который изначально использовал автор темы на форуме. В его логике было заложено несколько критических проблем, которые могли привести к нестабильной работе программы в реальной базе данных:
КоличествоДокументов = 2), выполнялся второй запрос на поиск самого документа. Это классический антипаттерн «двойного обращения» к СУБД.КоличествоДокументов = 2) очень ненадежна. Если в системе произойдет сбой или администратор создаст третий документ вручную, условие перестанет выполняться, и логика перенаправления пользователя на существующий документ полностью сломается.КОЛИЧЕСТВО) это абсолютно лишнее действие, нагружающее память сервера приложений.Выяснив основные слабые места, давайте разберем, как сделать этот механизм надежным, быстрым и простым в обслуживании.
На форумах начинающие разработчики часто спрашивают: «Можно ли обойтись без написания запроса?». Технически в платформе «1С:Предприятие» существует метод выборки объектов через менеджер менеджера документа:
Выборка = Документы.ОценкаЗначимостиРиска.Выбрать();
Однако использовать этот метод для решения нашей задачи категорически не рекомендуется по следующим причинам:
Выбрать() позволяет установить отбор только по стандартным полям (таким как период или ответственный, если для него настроено индексирование). Вы не сможете напрямую отфильтровать выборку по вашему реквизиту БизнесПроцесс. В результате программе придется прочитать абсолютно все документы данного типа из базы данных в оперативную память и проверять каждый документ в цикле. Если в базе скопились десятки тысяч документов, это приведет к полному зависанию интерфейса пользователя.Именно поэтому использование механизма запросов (язык SQL на уровне платформы 1С) является единственным профессиональным и верным решением. Запрос позволяет СУБД выполнить поиск на уровне индексов таблицы и вернуть на сервер приложений только одну нужную нам ссылку.
Для того чтобы получить ровно один самый свежий документ по конкретному бизнес-процессу, нам необходимо использовать конструкцию ПЕРВЫЕ 1 и упорядочить результат по убыванию. Рассмотрим этот запрос подробнее:
ВЫБРАТЬ ПЕРВЫЕ 1
ОценкаЗначимостиРиска.Ссылка КАК Ссылка
ИЗ
Документ.ОценкаЗначимостиРиска КАК ОценкаЗначимостиРиска
ГДЕ
ОценкаЗначимостиРиска.БизнесПроцесс = &БизнесПроцесс
УПОРАДОЧИТЬ ПО
ОценкаЗначимостиРиска.МоментВремени УБЫВ
Давайте проанализировать ключевые особенности этого запроса:
Конструкция ПЕРВЫЕ 1: Этот оператор указывает СУБД, что нас интересует только одна запись. Как только база данных найдет первую строку, соответствующую условиям сортировки и отбора, она мгновенно прекратит сканирование таблицы. Это колоссально экономит ресурсы сервера.
Сортировка по Моменту Времени: Обратите внимание, что мы сортируем результат не по полю Дата, а по виртуальному полю МоментВремени. Почему это важно? Дата в 1С хранится с точностью до секунды. Если в рамках одной секунды (например, при групповой обработке или программном создании) будут созданы два документа по одному бизнес-процессу, сортировка по дате станет нестабильной — СУБД может вернуть эти документы в случайном порядке. МоментВремени включает в себя не только дату и время, но и уникальный идентификатор ссылки (GUID), что гарантирует абсолютно точную и последовательную сортировку на уровне базы данных. В качестве альтернативы можно использовать конструкцию: УПОРАДОЧИТЬ ПО Дата УБЫВ, Ссылка УБЫВ.
Теперь оформим наш запрос в виде готовой функции на языке BSL. Эту функцию лучше всего разместить в модуле менеджера документа ОценкаЗначимостиРиска с директивой экспорта, либо непосредственно в модуле формы (если она используется локально). Мы рассмотрим вариант функции, которую можно вызывать из любого места конфигурации.
Обратите внимание на то, как мы возвращаем результат. Нам не нужно использовать циклы Пока Выборка.Следующий(). Достаточно использовать простое условие:
Функция НайтиПоследнийДокументПоБП(СсылкаНаБП) Экспорт
// Проверим корректность переданного параметра
Если НЕ ЗначениеЗаполнено(СсылкаНаБП) Тогда
Возврат Неопределено;
КонецЕсли;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1
| ОценкаЗначимостиРиска.Ссылка КАК Ссылка
|ИЗ
| Документ.ОценкаЗначимостиРиска КАК ОценкаЗначимостиРиска
|ГДЕ
| ОценкаЗначимостиРиска.БизнесПроцесс = &БизнесПроцесс
|УПОРАДОЧИТЬ ПО
| ОценкаЗначимостиРиска.МоментВремени УБЫВ";
Запрос.УстановитьПараметр("БизнесПроцесс", СсылкаНаБП);
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
// Если в выборке есть запись, сразу возвращаем найденную ссылку
Если Выборка.Следующий() Тогда
Возврат Выборка.Ссылка;
КонецЕсли;
// Если документов по данному бизнес-процессу не найдено
Возврат Неопределено;
КонецФункции
Теперь свяжем нашу поисковую функцию с логикой открытия формы нового документа. Наша задача — перехватить процесс создания, когда пользователь пытается открыть форму нового, еще не записанного в базу документа.
Для этого мы будем использовать обработчик события ПриОткрытии (для обычных форм) или ПриСозданииНаСервере (рекомендуется для управляемых форм). Рассмотрим пример для управляемого интерфейса, так как он является стандартом для современных конфигураций 1С.
В модуле формы документа пишем следующий код:
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
// Проверяем, что это абсолютно новый, еще не сохраненный в базе документ
Если НЕ ЗначениеЗаполнено(Объект.Ссылка) Тогда
// Проверяем, передан ли и заполнен ли реквизит БизнесПроцесс
Если ЗначениеЗаполнено(Объект.БизнесПроцесс) Тогда
// Ищем последний созданный документ по этому бизнес-процессу
СсылкаНаСуществующийДок = Документы.ОценкаЗначимостиРиска.НайтиПоследнийДокументПоБП(Объект.БизнесПроцесс);
Если ЗначениеЗаполнено(СсылкаНаСуществующийДок) Тогда
// Сохраняем ссылку во временное хранилище параметров формы,
// чтобы открыть её на клиенте, и отменяем создание текущего документа
Параметры.Вставить("СсылкаДляОткрытияВзамен", СсылкаНаСуществующийДок);
Отказ = Истина;
КонецЕсли;
КонецЕсли;
КонецЕсли;
КонецПроцедуры
И на клиентской стороне в обработчике ПриОткрытии мы выполняем перенаправление:
&НаКлиенте
Процедура ПриОткрытии(Отказ)
// Проверяем, был ли установлен параметр перенаправления на сервере
Если Параметры.Свойство("СсылкаДляОткрытияВзамен") Тогда
Отказ = Истина; // Отменяем открытие текущей (новой) формы
// Открываем форму существующего найденного документа
ПараметрыОткрытия = Новый Структура("Ключ", Параметры.СсылкаДляОткрытияВзамен);
ОткрытьФорму("Документ.ОценкаЗначимостиРиска.ФормаОбъекта", ПараметрыОткрытия);
КонецЕсли;
КонецПроцедуры
Давайте подведем итог проделанной нами работы. Мы значительно упростили логику, избавились от лишних запросов к базе данных и повысили скорость работы интерфейса. Чтобы ваши решения всегда работали быстро и без сбоев, придерживайтесь следующих правил:
Выбрать(), если вам нужно найти конкретный документ по произвольному реквизиту. Всегда используйте запросы с конструкцией ГДЕ.ПЕРВЫЕ 1. Это минимизирует нагрузку на СУБД.МоментВремени, а не обычную дату. Это убережет вашу систему от логических коллизий, когда несколько документов создаются в одну секунду.Применяя эти несложные, но крайне важные правила оптимизации, вы сможете создавать надежные и быстродействующие механизмы в любых прикладных решениях на платформе «1С:Предприятие 8»!