Как правильно получить остатки в 1С и избежать критических ошибок в коде и архитектуре

Программист 1С v8.3 (Обычные формы) Управленческий учет IT и автоматизация бизнеса
← На главную

При разработке в системе «1С:Предприятие» начинающие специалисты часто сталкиваются с ситуацией, когда код работает некорректно или сильно замедляет систему при увеличении объемов данных. Типичный пример — попытка получить остатки товаров на складе по определенным параметрам. На первый взгляд задача кажется простой, но в процессе ее реализации можно совершить целый комплекс грубых ошибок: от неоптимальных запросов до грубых нарушений в проектировании базы данных.

В этой статье мы подробно разберем практический кейс, проанализируем ошибки в коде и архитектуре, а также рассмотрим правильные шаблоны проектирования и написания запросов для получения остатков в соответствии со стандартами разработки фирмы «1С».

Исходная проблема: почему код не работает?

Рассмотрим фрагмент кода, с которым возникли сложности у автора темы:


Процедура КоманднаяПанель2Действие9(Кнопка) 
    Для каждого стр Из Материалы Цикл 
        Запрос = Новый Запрос; 
        Запрос.Текст = "ВЫБРАТЬ 
                       | Склад41000Центральный.ДоговорСбыта КАК Договор, 
                       | Склад41000Центральный.Количество 
                       |ИЗ 
                       | РегистрНакопления.Склад41000Центральный КАК Склад41000Центральный 
                       |ГДЕ 
                       | Склад41000Центральный.Артикул.Артикул = &Артик 
                       | И Склад41000Центральный.ДоговорСбыта = &Договор"; 
        Запрос.УстановитьПараметр("Артик", СокрЛП(стр.АртикулМатериала)); 
        Запрос.УстановитьПараметр("Договор",СокрЛП(ДоговорСбытаСКоторогоСписываем)); 
        Результат = Запрос.Выполнить.Выбрать; 
        Пока Результат.Следующий Цикл 
            стр.КоличествоНаСкладеПКИ = Результат.Количество ; 
        КонецЦикла; 
        Сообщить(Результат.договор); 
        НеХватаетНаСкладе; 
    КонецЦикла; 
    Записать; 
КонецПроцедуры

Проанализируем этот код. В нем содержится сразу несколько критических уязвимостей и ошибок, которые приводят к неработоспособности алгоритма и потенциальному зависанию всей информационной базы. Выясним причины этих проблем пошагово.

---

Ошибка 1. Запросы внутри цикла

Конструкция Для каждого стр Из Материалы Цикл, внутри которой инициализируется и выполняется Новый Запрос, является злейшим врагом производительности 1С. Это классический антипаттерн.

Когда мы выполняем запрос в цикле, происходит следующее:

  1. На каждой итерации цикла платформа отправляет отдельный сетевой запрос к СУБД (MS SQL, PostgreSQL и др.).
  2. СУБД вынуждена заново компилировать план запроса, выделять ресурсы, выполнять поиск по индексам и возвращать результат обратно на сервер приложений 1С.
  3. Если в табличной части Материалы будет хотя бы 100 строк, система совершит 100 отдельных обращений к базе данных. При работе нескольких пользователей это мгновенно приведет к критическому снижению производительности и блокировкам таблиц.

Правильное решение: Собрать все необходимые параметры из табличной части в массив или таблицу значений, передать их в один общий запрос, выполнить его один раз, а затем обработать полученный массив данных.

---

Ошибка 2. Обращение к физической таблице регистра вместо виртуальной

В тексте запроса автор обращается к таблице РегистрНакопления.Склад41000Центральный напрямую. Это физическая таблица, в которой хранятся все движения (приходы и расходы) за все время существования базы данных.

Пытаясь получить остаток таким способом, мы заставляем СУБД сканировать всю историю движений по указанным артикулам и договорам. На реальной базе с тысячами документов этот запрос будет выполняться чрезвычайно долго и вернет не актуальный текущий остаток, а детальные записи обо всех операциях.

Правильное решение: Для получения остатков необходимо всегда использовать виртуальную таблицу Остатки (например, РегистрНакопления.Склад41000Центральный.Остатки). Платформа 1С оптимизирует обращение к этой таблице на уровне СУБД, используя предварительно рассчитанные итоги на конец каждого месяца, что позволяет получить результат мгновенно.

---

Ошибка 3. Несоответствие типов данных (Строка vs Ссылка)

В параметрах запроса автор использует функцию СокрЛП():

Запрос.УстановитьПараметр("Договор", СокрЛП(ДоговорСбытаСКоторогоСписываем));

Функция СокрЛП преобразует переданный в нее объект в тип Строка. При этом измерение ДоговорСбыта в регистре накопления имеет ссылочный тип (например, СправочникСсылка.ДоговорыКонтрагентов).

В СУБД ссылки хранятся в виде уникальных 16-байтных двоичных идентификаторов (UUID). Когда СУБД пытается сравнить уникальный идентификатор ссылки с текстовой строкой (например, с наименованием договора "Договор №12"), сравнение всегда возвращает ложь (Ложь). В результате запрос всегда будет возвращать пустой результат, и остатки никогда не будут найдены.

Правильное решение: Передавать в параметры запроса чистые ссылки без какого-либо преобразования в текст. Очистите код от лишних функций форматирования строк при работе с объектными типами данных.

---

Ошибка 4. Избыточное разыменование полей («через точку»)

В секции условий мы видим следующую конструкцию:

Склад41000Центральный.Артикул.Артикул = &Артик

Здесь происходит обращение к реквизиту через два уровня вложенности. При трансляции запроса в SQL каждое такое обращение «через точку» преобразуется в операцию левого соединения (LEFT OUTER JOIN) с таблицей соответствующего справочника.

Если поле имеет составной тип данных, СУБД выполнит соединения со всеми таблицами, входящими в этот тип. Это колоссально перегружает оптимизатор запросов СУБД. Кроме того, дублирование имени (Артикул.Артикул) говорит о некорректной структуре данных — артикул должен храниться непосредственно в измерении регистра или в реквизитах номенклатуры без лишней вложенности.

---

Ошибка 5. Ошибки проектирования метаданных

Название регистра Склад41000Центральный указывает на фундаментальную ошибку в архитектуре конфигурации. Создание отдельных регистров под конкретные физические склады — это тупиковый путь развития системы.

Представьте, что завтра у компании появится "Склад42000Западный". Разработчику придется создавать новый регистр в конфигураторе, переписывать все документы, отчественность и логику проведения.

Правильное решение: Создать один универсальный регистр накопления (например, ТоварыНаСкладах) и добавить в него измерение Склад с типом ссылки на справочник Склады. Это позволит масштабировать систему без изменения метаданных и программного кода.

---

Пошаговое руководство по исправлению кода

Разберем, как правильно переписать данный алгоритм, избавившись от всех вышеописанных проблем. Применим паттерн проектирования с использованием временных таблиц.

Шаг 1. Подготовка исходных данных

Вместо обхода табличной части в цикле, мы выгрузим ее данные в отдельную таблицу значений. Это позволит нам передать все строки одним пакетом в запрос.


// Выгружаем только необходимые колонки из табличной части для оптимизации памяти
ТаблицаИсходныхДанных = Материалы.Выгрузить( , "АртикулМатериала");

Шаг 2. Создание пакетного запроса

Мы напишем пакетный запрос. В первом пакете мы поместим наши исходные данные во временную таблицу. Во втором пакете мы соединим эту временную таблицу с виртуальной таблицей остатков нашего регистра.


Запрос = Новый Запрос;
Запрос.Текст = 
"ВЫБРАТЬ
|	ИсходныеДанные.АртикулМатериала КАК Артикул
|ПОМЕСТИТЬ ВТ_ИсходныеДанные
|ИЗ
|	&ТаблицаИсходныхДанных КАК ИсходныеДанные
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
|	ВТ.Артикул КАК Артикул,
|	ЕСТЬNULL(ТоварыОстатки.КоличествоОстаток, 0) КАК КоличествоНаСкладе
|ИЗ
|	ВТ_ИсходныеДанные КАК ВТ
|	ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки(
|		&Период,
|		Номенклатура.Артикул В (ВЫБРАТЬ ВТ_ИсходныеДанные.Артикул ИЗ ВТ_ИсходныеДанные)
|		И ДоговорСбыта = &ДоговорСбыта
|	) КАК ТоварыОстатки
|	ПО ВТ.Артикул = ТоварыОстатки.Номенклатура.Артикул";

Проанализируем важные детали этого запроса:

Шаг 3. Передача параметров и правильная работа с датами

При получении остатков в процессе проведения документа критически важно правильно указать момент времени, на который эти остатки запрашиваются. Если передать просто дату документа, остатки будут получены без учета движений самого этого документа. Для решения этой проблемы мы используем объект Граница.


// Устанавливаем параметры запроса
Запрос.УстановитьПараметр("ТаблицаИсходныхДанных", ТаблицаИсходныхДанных);

// Передаем чистую ссылку на договор без использования строковых функций
Запрос.УстановитьПараметр("ДоговорСбыта", ДоговорСбытаСКоторогоСписываем);

// Формируем границу для получения остатков на момент проведения документа
ГраницаОстатков = Новый Граница(МоментВремени(), ВидГраницы.Исключая);
Запрос.УстановитьПараметр("Период", ГраницаОстатков);

Шаг 4. Обход результатов запроса и заполнение табличной части

После выполнения запроса мы получаем готовую выборку, которую можем быстро обойти и сопоставить с исходной табличной частью документа.


РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();

// Для быстрого поиска по табличной части создадим индекс
Материалы.Индексировать("АртикулМатериала");

Пока Выборка.Следующий() Цикл
    // Ищем строки в табличной части по артикулу
    ПараметрыПоиска = Новый Структура("АртикулМатериала", Выборка.Артикул);
    НайденныеСтроки = Материалы.НайтиСтроки(ПараметрыПоиска);
    
    Для каждого СтрокаТЧ Из НайденныеСтроки Цикл
        СтрокаТЧ.КоличествоНаСкладеПКИ = Выборка.КоличествоНаСкладе;
    КонецЦикла;
КонецЦикла;

---

Резюме лучших практик работы с остатками в 1С

Для того чтобы ваши решения всегда работали быстро и без сбоев, придерживайтесь следующих золотых правил разработки:

  1. Никогда не пишите запросы в циклах. Передавайте данные пакетом через параметры или временные таблицы.
  2. Всегда используйте виртуальные таблицы для получения остатков (.Остатки) и оборотов (.Обороты). Забудьте про обращение к физическим таблицам регистров для этих задач.
  3. Передавайте фильтры внутрь параметров виртуальных таблиц. Наложение условий в секции ГДЕ после обращения к виртуальной таблице сводит на нет все оптимизационные механизмы платформы.
  4. Соблюдайте строгую типизацию. Не сравнивайте ссылки со строками, полученными через СокрЛП. Передавайте в параметры запроса только объекты соответствующих ссылочных типов.
  5. Проектируйте гибкую архитектуру. Избегайте создания уникальных объектов метаданных (регистров, справочников) под конкретные аналитические разрезы вроде конкретного склада или подразделения. Используйте для этих целей измерения регистров.

Соблюдение этих несложных правил позволит вам писать качественный, легко масштабируемый и быстродействующий код, который без проблем выдержит работу сотен пользователей в высоконагруженных информационных базах.

← На главную