При переходе к разработке на платформе 1С:Предприятие 7.7 разработчики часто сталкиваются с особенностями встроенного языка запросов и работы с регистрами. Типичная задача — при заполнении табличной части документа определить наличие свободного остатка товара на складах (подразделениях) и подставить подразделение с положительным остатком. Разберем по шагам характерные ошибки, возникающие при составлении запросов в 1С 7.7, а также рассмотрим оптимальные варианты решения этой задачи.
Рассмотрим исходную ситуацию: при попытке выполнить запрос к регистру остатков результирующая таблица значений оказывается пустой либо содержит некорректные пустые строки. Выделим основные причины такого поведения:
Группировка: в языке запросов 1С 7.7 итоговые функции (например, КонОст()) не рассчитываются без явного указания группировок измерений. Без группировки запрос не может сформировать строки выборки.;. Конструкция условия пишется строго в виде |Условие(Выражение);, а не через вынос оператора сравнения за скобки.Без Групп.Номенклатура), это может приводить к неоднозначности при вычислении условия. Рекомендуется добавлять префиксы к переменным запроса (например, зНоменклатура, зПодразделение).ПолучитьСтроку() необходимо обязательно вызвать метод ВыбратьСтроки().Разберем правильную архитектуру выборки данных с помощью объекта Запрос. Вместо выполнения запроса в цикле мы сначала соберем список нужных позиций в СписокЗначений, выполним один запрос с отбором по подразделениям и номенклатуре, после чего выгрузим результат в таблицу значений для быстрого сопоставления.
Посмотрим на пример реализации:
// 1. Собираем список номенклатуры из подчиненного справочника
СписНоменклатуры = СоздатьОбъект("СписокЗначений");
Товары = СоздатьОбъект("Справочник.СпецпредложенияТовары");
Товары.ИспользоватьВладельца(Пакет);
Товары.ВыбратьЭлементы();
Пока Товары.ПолучитьЭлемент() = 1 Цикл
Если Товары.ПометкаУдаления() = 0 Тогда
СписНоменклатуры.ДобавитьЗначение(Товары.Номенклатура);
КонецЕсли;
КонецЦикла;
// 2. Формируем и выполняем единый запрос к регистру остатков
Запрос = СоздатьОбъект("Запрос");
ТекстЗапроса =
"//{{ЗАПРОС(ОстаткиПоСкладам)
|зНоменклатура = Регистр.Склад.Номенклатура;
|зПодразделение = Регистр.Склад.Подразделение;
|Количество = Регистр.Склад.Количество;
|Функция КоличествоКонОст = КонОст(Количество);
|Группировка зНоменклатура без групп;
|Группировка зПодразделение без групп;
|Условие(зНоменклатура в СписНоменклатуры);
|"//}}ЗАПРОС
;
ТЗОстатков = СоздатьОбъект("ТаблицаЗначений");
Если Запрос.Выполнить(ТекстЗапроса) = 1 Тогда
// Выгружаем только детальные записи без итоговых строк группировок
Запрос.Выгрузить(ТЗОстатков, 1, 0);
КонецЕсли;
// 3. Заполняем строки документа, находя остатки в полученной ТЗ
Товары.ВыбратьЭлементы();
Пока Товары.ПолучитьЭлемент() = 1 Цикл
Если Товары.ПометкаУдаления() = 0 Тогда
НоваяСтрока();
Номенклатура = Товары.Номенклатура;
Количество = 1;
// Ищем первое подразделение с остатком >= 1
ТЗОстатков.ВыбратьСтроки();
Пока ТЗОстатков.ПолучитьСтроку() = 1 Цикл
Если (ТЗОстатков.зНоменклатура = Товары.Номенклатура) И (ТЗОстатков.КоличествоКонОст >= 1) Тогда
Подразделение = ТЗОстатков.зПодразделение;
Прервать;
КонецЕсли;
КонецЦикла;
КонецЕсли;
КонецЦикла;
Обратим внимание на параметры метода Выгрузить(ТЗОстатков, 1, 0): передача (1, 0) формирует плоскую структуру колонок и исключает промежуточные строки итогов.
Если требуется получить остатки без использования сложного механизма запросов, в платформе 1С 7.7 предусмотрены прямые методы работы с итогами регистров остатков.
Вариант А: Использование метода ВыгрузитьИтоги()
Этот способ позволяет получить снимок текущих остатков регистра по всем измерениям в одну таблицу значений за одно обращение к базе данных:
РегСклад = СоздатьОбъект("Регистр.Склад");
ТЗОстатков = СоздатьОбъект("ТаблицаЗначений");
// Выгружаем измерения (1) и ресурсы (1) регистра
РегСклад.ВыгрузитьИтоги(ТЗОстатков, 1, 1);
// После этого обходим товары и ищем строки в ТЗОстатков
Товары = СоздатьОбъект("Справочник.СпецпредложенияТовары");
Товары.ИспользоватьВладельца(Пакет);
Товары.ВыбратьЭлементы();
Пока Товары.ПолучитьЭлемент() = 1 Цикл
Если Товары.ПометкаУдаления() = 0 Тогда
НоваяСтрока();
Номенклатура = Товары.Номенклатура;
Количество = 1;
ТЗОстатков.ВыбратьСтроки();
Пока ТЗОстатков.ПолучитьСтроку() = 1 Цикл
Если (ТЗОстатков.Номенклатура = Товары.Номенклатура) И (ТЗОстатков.Количество >= 1) Тогда
Подразделение = ТЗОстатков.Подразделение;
Прервать;
КонецЕсли;
КонецЦикла;
КонецЕсли;
КонецЦикла;
Вариант Б: Использование методов УстановитьЗначениеФильтра() и ВыбратьИтоги()
Если для измерения Номенклатура в конфигураторе включен признак «Отбор» (быстрая фильтрация движений), можно перебирать остатки конкретной позиции напрямую через регистр:
РегСклад = СоздатьОбъект("Регистр.Склад");
Товары = СоздатьОбъект("Справочник.СпецпредложенияТовары");
Товары.ИспользоватьВладельца(Пакет);
Товары.ВыбратьЭлементы();
Пока Товары.ПолучитьЭлемент() = 1 Цикл
Если Товары.ПометкаУдаления() = 0 Тогда
НоваяСтрока();
Номенклатура = Товары.Номенклатура;
Количество = 1;
// Устанавливаем фильтр по текущей номенклатуре
РегСклад.УстановитьЗначениеФильтра("Номенклатура", Товары.Номенклатура, 1);
РегСклад.ВыбратьИтоги();
Пока РегСклад.ПолучитьИтог() = 1 Цикл
Если РегСклад.Количество >= 1 Тогда
Подразделение = РегСклад.Подразделение;
Прервать;
КонецЕсли;
КонецЦикла;
КонецЕсли;
КонецЦикла;
Регистр период не указан явно, данные берутся на текущую ТА. При заполнении исторических документов (проведенных ранее ТА) необходимо задавать период расчета остатков: |Период по ТекущийДокумент; или вызывать РегСклад.ВременныйРасчет(1); РегСклад.РассчитатьРегистрыНа(ТекущийДокумент);.ПриВыбореЗначения() внутри алгоритмов массового заполнения строк, так как эта процедура предназначена исключительно для реакции на интерактивные действия пользователя. Заполняйте реквизиты строк напрямую в коде.