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