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