При разработке и администрировании баз данных на платформе 1С:Предприятие часто возникает необходимость скорректировать или полностью удалить движения по регистрам накопления для конкретного типа документов. Например, когда нужно очистить движения по документу ПлатежноеПоручениеВходящее в регистре ВзаиморасчетыСКонтрагентами, оставив движения других документов нетронутыми. При этом начинающие разработчики часто сталкиваются с ошибками при попытке передать тип документа в качестве параметра запроса или при обработке дат закрытия периода.
В этой статье мы подробно разберем, как правильно составить запрос к регистру накопления с отбором по типу регистратора, как обойти типичные ошибки синтаксиса, настроить ограничение по дате изменения данных, а также рассмотрим важные вопросы оптимизации производительности и сохранения ссылочной целостности базы данных.
Начнем с анализа типичной ошибки, которую совершают многие начинающие программисты. Попытка сравнить реквизит Регистратор с параметром напрямую выглядит в коде следующим образом:
// Ошибочный вариант запроса
Запрос.Текст =
"ВЫБРАТЬ
| ВзаиморасчетыСКонтрагентами.Регистратор
|ИЗ
| РегистрНакопления.ВзаиморасчетыСКонтрагентами КАК ВзаиморасчетыСКонтрагентами
|ГДЕ
| ВзаиморасчетыСКонтрагентами.Регистратор.Ссылка = &ПлатежноеПоручениеВходящее";
При попытке выполнить такой код система выдает ошибку "Не задано значение параметра 'ПлатежноеПоручениеВходящее'". Почему это происходит? Разработчик ожидает, что платформа сама поймет, что под параметром подразумевается тип метаданных, однако в 1С параметры запроса всегда требуют явной передачи значений во встроенном языке с помощью методов установки параметров.
Кроме того, использование конструкции Регистратор.Ссылка не имеет практического смысла, так как поле Регистратор уже само по себе является ссылочным типом данных. Но главная проблема кроется в производительности СУБД. Поле Регистратор в регистрах накопления имеет составной тип данных. Если мы обращаемся к нему через точку (например, Регистратор.Дата или Регистратор.Ссылка), СУБД выполняет неявное левое соединение (LEFT JOIN) со всеми таблицами документов, которые могут выступать регистраторами для данного регистра. В крупных конфигурациях это может привести к соединению с десятками или сотнями таблиц, что колоссально снизит скорость работы системы.
Для того чтобы ограничить выборку документов только определенным типом без использования внешних параметров, в языке запросов 1С предусмотрен специальный оператор ССЫЛКА. С его помощью мы явно указываем СУБД, к таблице какого конкретно документа необходимо обратиться.
Рассмотрим правильный вариант написания условия в запросе:
ГДЕ ВзаиморасчетыСКонтрагентами.Регистратор ССЫЛКА Документ.ПлатежноеПоручениеВходящее
Важный синтаксический нюанс: Обратите внимание, что в тексте запроса тип объекта метаданных указывается строго в единственном числе — Документ.ПлатежноеПоручениеВходящее. Если вы напишете во множественном числе Документы (как это принято во встроенном языке 1С), платформа вернет ошибку "Таблица не найдена".
Часто перед нами стоит задача не просто удалить движения за всю историю ведения учета, а установить жесткую временную границу. Например, нам нужно очистить движения, сделанные только начиная с определенной даты (или наоборот — строго в определенном диапазоне), чтобы не затронуть прошлые закрытые периоды.
Для этого в условие запроса мы добавляем проверку по полю Период регистра накопления. Запрос с ограничением по датам будет выглядеть следующим образом:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ РАЗЛИЧНЫЕ
| ВзаиморасчетыСКонтрагентами.Регистратор КАК Регистратор
|ИЗ
| РегистрНакопления.ВзаиморасчетыСКонтрагентами КАК ВзаиморасчетыСКонтрагентами
|ГДЕ
| ВзаиморасчетыСКонтрагентами.Регистратор ССЫЛКА Документ.ПлатежноеПоручениеВходящее
| И ВзаиморасчетыСКонтрагентами.Период >= &ДатаНачала";
Обратите внимание на ключевое слово РАЗЛИЧНЫЕ в блоке ВЫБРАТЬ. Один документ-регистратор может делать десятки и сотни записей в одной таблице регистра. Чтобы наша выборка не содержала дублирующихся ссылок на один и тот же документ, мы группируем результат запроса с помощью РАЗЛИЧНЫЕ.
Для передачи даты границы в запрос мы должны использовать штатные механизмы платформы. В старых конфигурациях или на интернет-форумах можно встретить обращение напрямую к структуре параметров: Запрос.Параметры.Вставить("ИмяПараметра", Значение). Этот способ рабочий, но он нарушает стандарты разработки фирмы "1С".
Правильным и наиболее надежным методом является использование встроенной функции УстановитьПараметр():
// Правильная установка параметров
ДатаГраницы = НачалоДня(ТекущаяДата()); // К примеру, сегодняшнее число
Запрос.УстановитьПараметр("ДатаНачала", ДатаГраницы);
Этот метод гарантирует корректную валидацию типов данных, предотвращает случайные ошибки и поддерживается Конструктором запросов 1С в автоматическом режиме.
Если мы просто запустим цикл обхода выборки и будем очищать наборы записей для каждого документа, то на больших базах данных мы столкнемся с катастрофическим падением производительности. Причина проста: при каждой записи пустого набора платформа инициирует транзакцию СУБД, обновляет индексы таблиц и выполняет пересчет итогов регистра накопления.
Чтобы выполнить операцию быстро и безопасно, применим комплексную оптимизацию.
Проанализируем программную реализацию оптимизированного цикла удаления с использованием транзакций:
РезультатЗапроса = Запрос.Выполнить();
Если Не РезультатЗапроса.Пустой() Тогда
Выборка = РезультатЗапроса.Выбрать();
// Открываем транзакцию базы данных
НачатьТранзакцию();
Попытка
СчетчикДокументов = 0;
Пока Выборка.Следующий() Цикл
// Создаем пустой набор записей с отбором по регистратору
Набор = РегистрыНакопления.ВзаиморасчетыСКонтрагентами.СоздатьНаборЗаписей();
Набор.Отбор.Регистратор.Установить(Выборка.Регистратор);
// Записываем пустой набор (это удаляет все движения данного документа)
Набор.Записать(Истина);
СчетчикДокументов = СчетчикДокументов + 1;
// Порционное сохранение данных для избежания переполнения транзакционного лога
Если СчетчикДокументов % 1000 = 0 Тогда
ЗафиксироватьТранзакцию();
НачатьТранзакцию();
КонецЕсли;
КонецЦикла;
// Фиксируем оставшиеся документы в транзакции
ЗафиксироватьТранзакцию();
Исключение
// В случае возникновения системной ошибки откатываем все изменения
ОтменитьТранзакцию();
ВызватьИсключение КраткоеПредставлениеОшибки(ИнформацияОбОшибке());
КонецПопытки;
КонецЕсли;
Программное удаление записей через запись пустого набора (метод Набор.Записать(Истина)) имеет один существенный побочный эффект. Сам документ-регистратор в интерфейсе программы по-прежнему будет числиться в статусе "Проведен" (рядом с ним будет отображаться зеленая галочка проведения).
Это может запутать пользователей и привести к системным ошибкам. Если бухгалтер зайдет в такой документ и просто перепроведет его, платформа заново сформирует движения в регистре ВзаиморасчетыСКонтрагентами, и вся ваша работа по удалению записей будет аннулирована.
Для предотвращения подобных ситуаций есть два решения:
ДокументОбъект = Выборка.Регистратор.ПолучитьОбъект();
Если ДокументОбъект <> Неопределено Тогда
ДокументОбъект.Записать(РежимЗаписиДокумента.ОтменаПроведения);
КонецЕсли;
// Проверка даты запрета изменения перед обработкой документа
Если ДатыЗапретаИзменения.ИзменениеЗапрещено(Выборка.Регистратор) Тогда
// Документ находится в закрытом периоде, пропускаем его обработку
Продолжить;
КонецЕсли;
Выбор конкретного метода зависит от ваших бизнес-требований. Если вам необходимо, чтобы документ оставался проведенным, но физически не имел записей в конкретном регистре, используйте первый метод (прямая запись пустого набора в регистр). Если документ должен полностью прекратить свое влияние на учетную систему, используйте метод отмены проведения документа.