Пустые вложенные таблицы в списке выборки

Язык запросов

Синтаксис и применение Пустые вложенные таблицы в списке выборки в платформе 1С:Предприятие. Описание и примеры кода.

Полное описание, параметры и ограничения

Пустые вложенные таблицы в списке выборки

В случае, если в запросе используется объединение, и в некоторых частях объединения присутствуют вложенные таблицы, а в некоторых нет, возникает необходимость дополнения списка выборки полями – пустыми вложенными таблицами. Делается это при помощи ключевого слова ПУСТАЯТАБЛИЦА, после которого в скобках указываются псевдонимы полей, из которых будет состоять вложенная таблица.

Пример:

// Выбрать поля Номер и Состав
// из виртуальной таблицы Документ.РасхНакл
ВЫБРАТЬ Ссылка.Номер, ПУСТАЯТАБЛИЦА.(Ном, Тов, Кол) КАК Состав
ИЗ Документ.РасхНакл
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ Ссылка.Номер, Состав.(НомерСтроки, Товар, Количество)
ИЗ Документ.РасхНакл Документ.РасходнаяНакладная.Состав.*

Практические примеры

ПУСТАЯТАБЛИЦА: совместить подробный документ и заголовок без изменения его строк

Два собственных записанных документа имеют две/одну строки. UNION ALL возвращает реальный состав первого и синтетический пустой состав второго; его настоящая hidden-item/9 остаётся в исходнике до rollback.

Где и как запускать

Полный серверный общий модуль УчебныйЯзык только новой собственной пустой конфигурации УчебныеПредставления85 на 8.5.1.1343. Предоставленный BSL будущего ручного опыта временно записывает только свой минимальный fixture и откатывает обе ветви. BSL/запросы/откат здесь не выполнялись.

Подготовка

  1. В отдельной учебной базе 8.5.1.1343 создайте общий модуль УчебныйЯзык: Сервер=Истина, Глобальный=Ложь, ВызовСервера=Ложь, Привилегированный=Ложь. Клиентские и внешнее соединение отключены; ПовторноеИспользованиеВозвращаемыхЗначений=НеИспользовать.

  2. Вставьте весь приведённый текст в этот общий модуль. ВыполнитьПример — экспортная точка входа; остальные процедуры и функции располагаются рядом, а не внутри неё.

  3. В своей внешней учебной обработке создайте управляемую форму, команду Выполнить и кнопку. Клиентский обработчик Выполнить(Команда) вызывает свою процедуру ВыполнитьНаСервере(); она имеет директиву &НаСервереБезКонтекста и одну строку УчебныйЯзык.ВыполнитьПример();. Это серверный вызов общего модуля, а не разрешение клиенту прямо обращаться к серверной библиотеке.

  4. HTML только показывает BSL. Код в 1С не запускался; перечисленные результаты и отрицательные опыты проверяйте самостоятельно в собственном стенде.

  5. Создайте НОВУЮ отдельную пустую учебную базу, не копию приложения, на 8.5.1.1343. Имя конфигурации УчебныеПредставления85; совместимость отключена. Единственный сеанс, без фоновых/регламентных заданий, расширений базы/сеанса, подписок, общих реквизитов, регистров, планов, задач и бизнес-процессов. Нужны права читать/создавать/изменять только свои учебные объекты и read-only списки расширений; ошибки прав не считаются результатом опыта.

  6. Справочники → УчебныеПодписи85: Иерархический=Нет, владельцев нет, Тип кода=Строка, Длина кода=3, Длина наименования=20, Автонумерация=Нет, Основное представление=В виде наименования. Собственные реквизиты, табчасти и предопределённые элементы отсутствуют. В модуле объекта и модуле менеджера текст полностью пустой, в том числе обработчики получения полей/представления.

  7. Документы → УчебныйПакет85: Проведение=Запретить, движений и собственных реквизитов нет, Тип номера=Строка, Длина номера=8, Автонумерация=Нет. Единственная табчасть Строки85: Позиция — только Строка(20), переменная длина; Количество — только Число(4,0), неотрицательный. Проверка заполнения обоих реквизитов=Не проверять. Все объектные/менеджерские модули и формы документа пусты.

  8. Другие модули конфигурации/сеанса/приложения и обработчики оставьте пустыми. Native прямо говорит, что МодульОбъекта/МодульМенеджера в BSL всегда имеют значение Неопределено; из этого нельзя доказать пустой текст. Поэтому пустота модулей проверяется читателем в новом Конфигураторе, а не ложным сравнением этих свойств с Неопределено. Здесь Конфигуратор/1С не запускаются.

  9. Guard проверяет схему, основной вид представления, ФиксированныйМассив предопределённых имён=пустой, массивы расширений трёх native источников=пустые и отсутствие изменений метаданных расширениями в сеансе. Отдельные пустые модули/формы — обязательное ручное условие, не обещание такого guard.

  10. Основной код целиком заменяет свой серверный общий модуль УчебныйЯзык со всеми помощниками на уровне модуля. Для следующего урока замените его целиком, не склеивайте одноимённые экспортные точки. Каждый модуль создаёт только собственный минимальный fixture этого урока; заранее вручную записи не создавайте.

  11. После подготовки метаданных и полного модуля выполните Конфигурация → Обновить конфигурацию базы данных (F7). Запускайте свою внешнюю обработку в новом сеансе этой учебной базы. Само сохранение текста в Конфигураторе не обновляет конфигурацию базы данных.

  12. Новая собственная внешняя обработка УчебныйПускПустогоСостава85 → управляемая Форма → команда Выполнить с одноимённым действием и кнопкой; остальные модули пусты. Полный отдельный модуль формы:

    &НаКлиенте
    Процедура Выполнить(Команда)
    ВыполнитьНаСервере();
    Сообщить("Собственный опыт проверен; его учебные записи отменены");
    КонецПроцедуры

    &НаСервереБезКонтекста
    Процедура ВыполнитьНаСервере()
    УчебныйЯзык.ВыполнитьПример();
    КонецПроцедуры

  13. Это не опыт только в памяти: предоставленный BSL будущего ручного запуска содержит собственные Записать внутри собственной транзакции, но никогда не ЗафиксироватьТранзакцию/Проведение/Удалить. До неё запрещена активная чужая транзакция и проверена пустота трёх собственных источников. После обеих ветвей отката проверяются завершение транзакции и Число0 записей всех трёх источников. Здесь код и база не исполнялись/не менялись.

Процедура ВыполнитьПример() Экспорт
    ПроверитьСвойСтендПредставлений();
    Если ТранзакцияАктивна() Тогда
        ВызватьИсключение "Чужая или вложенная активная транзакция запрещена";
    КонецЕсли;
    ПроверитьОтсутствиеСвоихЗаписей();
    НачатьТранзакцию();
    Попытка
        ПроверитьСвойВводСтроки("X", 0);
        ПроверитьСвойВводСтроки("12345678901234567890", 9999);
        ПроверитьОтклонениеСвоейСтроки(Неопределено, 2);
        ПроверитьОтклонениеСвоейСтроки(7, 2);
        ПроверитьОтклонениеСвоейСтроки("", 2);
        ПроверитьОтклонениеСвоейСтроки("123456789012345678901", 2);
        ПроверитьОтклонениеСвоейСтроки("X", "2");
        ПроверитьОтклонениеСвоейСтроки("X", -1);
        ПроверитьОтклонениеСвоейСтроки("X", 10000);
        ПроверитьОтклонениеСвоейСтроки("X", 2.5);
        СвоиДокументы = Новый Массив;
        ОжиданияДокументов = Новый Массив;
        ОжиданияСтрок = Новый Массив;
        Объект = СоздатьСвойДокумент("OWN00001", Дата(2026, 10, 8, 12, 0, 0));
        ДобавитьСвоюСтроку(Объект, 1, "item-A", 2);
        ДобавитьСвоюСтроку(Объект, 2, "item-B", 3);
        СсылкаДокумента = ЗаписатьСвойДокумент(Объект);
        СвоиДокументы.Добавить(СсылкаДокумента);
        ОжиданияДокументов.Добавить(Новый Структура("Ссылка,Номер,Дата", СсылкаДокумента, "OWN00001", Дата(2026, 10, 8, 12, 0, 0)));
        ОжиданияСтрок.Добавить(Новый Структура("Ссылка,Номер,НомерСтроки,Позиция,Количество", СсылкаДокумента, "OWN00001", 1, "item-A", 2));
        ОжиданияСтрок.Добавить(Новый Структура("Ссылка,Номер,НомерСтроки,Позиция,Количество", СсылкаДокумента, "OWN00001", 2, "item-B", 3));
        Объект = СоздатьСвойДокумент("OWN00002", Дата(2026, 10, 6, 12, 0, 0));
        ДобавитьСвоюСтроку(Объект, 1, "hidden-item", 9);
        СсылкаДокумента = ЗаписатьСвойДокумент(Объект);
        СвоиДокументы.Добавить(СсылкаДокумента);
        ОжиданияДокументов.Добавить(Новый Структура("Ссылка,Номер,Дата", СсылкаДокумента, "OWN00002", Дата(2026, 10, 6, 12, 0, 0)));
        ОжиданияСтрок.Добавить(Новый Структура("Ссылка,Номер,НомерСтроки,Позиция,Количество", СсылкаДокумента, "OWN00002", 1, "hidden-item", 9));
        ПроверитьСвоиИсходныеДокументы(ОжиданияДокументов, ОжиданияСтрок);
        Подробный = СвоиДокументы[0];
        Заголовок = СвоиДокументы[1];
        ПроверитьОтклонениеСвоейПары(Неопределено, Заголовок, СвоиДокументы);
        ПроверитьОтклонениеСвоейПары("OWN00001", Заголовок, СвоиДокументы);
        ПроверитьОтклонениеСвоейПары(Документы.УчебныйПакет85.ПустаяСсылка(), Заголовок, СвоиДокументы);
        ПроверитьОтклонениеСвоейПары(Подробный, Подробный, СвоиДокументы);
        ПроверитьСвоюПаруДокументов(Подробный, Заголовок, СвоиДокументы);
        Запрос = Новый Запрос;
        Запрос.Текст = "ВЫБРАТЬ Док.Номер КАК Номер, ПУСТАЯТАБЛИЦА.(НомерСтроки,Позиция,Количество) КАК Состав
            |ИЗ Документ.УчебныйПакет85 КАК Док ГДЕ Док.Ссылка = &Заголовок
            |ОБЪЕДИНИТЬ ВСЕ
            |ВЫБРАТЬ Док.Номер КАК Номер, Док.Строки85.(НомерСтроки,Позиция,Количество) КАК Состав
            |ИЗ Документ.УчебныйПакет85 КАК Док ГДЕ Док.Ссылка = &Подробный
            |УПОРЯДОЧИТЬ ПО Номер ВОЗР";
        Запрос.УстановитьПараметр("Заголовок", Заголовок);
        Запрос.УстановитьПараметр("Подробный", Подробный);
        Результат = Запрос.Выполнить();
        Если Результат.Колонки.Количество() <> 2 Или Результат.Колонки.Найти("Номер") = Неопределено
            Или Результат.Колонки.Найти("Состав") = Неопределено Тогда
            ВызватьИсключение "Нужны две точные колонки Номер и Состав";
        КонецЕсли;
        Выборка = Результат.Выбрать();
        Если Выборка.Количество() <> 2 Тогда
            ВызватьИсключение "Нужны ровно две собственные верхние записи";
        КонецЕсли;
        Поля = Новый Массив;
        Поля.Добавить("НомерСтроки");
        Поля.Добавить("Позиция");
        Поля.Добавить("Количество");
        Если Не Выборка.Следующий() Тогда
            ВызватьИсключение "Не получен следующий собственный верхний результат";
        КонецЕсли;
        Если ТипЗнч(Выборка.Номер) <> Тип("Строка") Или Выборка.Номер <> "OWN00001" Тогда
            ВызватьИсключение "Не совпал номер/тип/верхний порядок собственной строки";
        КонецЕсли;
        Вложенный = Выборка.Состав;
        Если ТипЗнч(Вложенный) <> Тип("РезультатЗапроса") Тогда
            ВызватьИсключение "Исходное поле Состав должно иметь тип РезультатЗапроса";
        КонецЕсли;
        Пустой = Вложенный.Пустой();
        Если ТипЗнч(Пустой) <> Тип("Булево") Тогда
            ВызватьИсключение "Признак Пустой должен иметь точный тип Булево";
        КонецЕсли;
        Если Пустой <> Ложь Тогда
            ВызватьИсключение "Не совпала пустота собственного вложенного результата";
        КонецЕсли;
        Ожидания = Новый Массив;
        Ожидания.Добавить(Новый Структура("НомерСтроки,Позиция,Количество", 1, "item-A", 2));
        Ожидания.Добавить(Новый Структура("НомерСтроки,Позиция,Количество", 2, "item-B", 3));
        ПроверитьСвоюПолнуюТаблицу(Вложенный.Выгрузить(), Ожидания, Поля, Ложь);
        Если Не Выборка.Следующий() Тогда
            ВызватьИсключение "Не получен следующий собственный верхний результат";
        КонецЕсли;
        Если ТипЗнч(Выборка.Номер) <> Тип("Строка") Или Выборка.Номер <> "OWN00002" Тогда
            ВызватьИсключение "Не совпал номер/тип/верхний порядок собственной строки";
        КонецЕсли;
        Вложенный = Выборка.Состав;
        Если ТипЗнч(Вложенный) <> Тип("РезультатЗапроса") Тогда
            ВызватьИсключение "Исходное поле Состав должно иметь тип РезультатЗапроса";
        КонецЕсли;
        Пустой = Вложенный.Пустой();
        Если ТипЗнч(Пустой) <> Тип("Булево") Тогда
            ВызватьИсключение "Признак Пустой должен иметь точный тип Булево";
        КонецЕсли;
        Если Пустой <> Истина Тогда
            ВызватьИсключение "Не совпала пустота собственного вложенного результата";
        КонецЕсли;
        Ожидания = Новый Массив;
        ПроверитьСвоюПолнуюТаблицу(Вложенный.Выгрузить(), Ожидания, Поля, Ложь);
        Если Выборка.Следующий() Тогда
            ВызватьИсключение "В собственном верхнем результате не должно быть третьей строки";
        КонецЕсли;
        ПроверитьСвоиИсходныеДокументы(ОжиданияДокументов, ОжиданияСтрок);
        Сообщить("Проекция дала пустой Состав для заголовка; реальная hidden-item/9 в исходнике не изменена");
    Исключение
        Если ТранзакцияАктивна() Тогда
            ОтменитьТранзакцию();
        КонецЕсли;
        ПроверитьОтсутствиеСвоихЗаписей();
        ВызватьИсключение;
    КонецПопытки;
    ОтменитьТранзакцию();
    ПроверитьОтсутствиеСвоихЗаписей();
    Сообщить("Собственный минимальный fixture отменён; все три своих источника пусты");
КонецПроцедуры

Процедура ПроверитьСвойСтендПредставлений()
    Если Метаданные.Справочники.Количество() <> 1 Или Метаданные.Документы.Количество() <> 1 Или Метаданные.Перечисления.Количество() <> 0 Или Метаданные.РегистрыСведений.Количество() <> 0 Или Метаданные.РегистрыНакопления.Количество() <> 0 Или Метаданные.РегистрыБухгалтерии.Количество() <> 0 Или Метаданные.РегистрыРасчета.Количество() <> 0 Или Метаданные.ПланыВидовХарактеристик.Количество() <> 0 Или Метаданные.ПланыВидовРасчета.Количество() <> 0 Или Метаданные.ПланыСчетов.Количество() <> 0 Или Метаданные.ПланыОбмена.Количество() <> 0 Или Метаданные.БизнесПроцессы.Количество() <> 0 Или Метаданные.Задачи.Количество() <> 0 Или Метаданные.Последовательности.Количество() <> 0 Или Метаданные.ОбщиеРеквизиты.Количество() <> 0 Или Метаданные.ПодпискиНаСобытия.Количество() <> 0 Или Метаданные.РегламентныеЗадания.Количество() <> 0 Или Метаданные.ВнешниеИсточникиДанных.Количество() <> 0 Тогда
        ВызватьИсключение "Нужен только новый собственный минимальный стенд УчебныеПредставления85";
    КонецЕсли;
    Если Метаданные.ЕстьИзмененияРасширениямиКонфигурации() Тогда
        ВызватьИсключение "В своём сеансе не должно быть изменений метаданных расширениями";
    КонецЕсли;
    ИсточникиРасширений = Новый Массив;
    ИсточникиРасширений.Добавить(ИсточникРасширенийКонфигурации.БазаДанных);
    ИсточникиРасширений.Добавить(ИсточникРасширенийКонфигурации.СеансАктивные);
    ИсточникиРасширений.Добавить(ИсточникРасширенийКонфигурации.СеансОтключенные);
    Для Каждого ИсточникРасширений Из ИсточникиРасширений Цикл
        Расширения = РасширенияКонфигурации.Получить(, ИсточникРасширений);
        Если ТипЗнч(Расширения) <> Тип("Массив") Тогда
            ВызватьИсключение "Нативный список расширений должен иметь тип Массив";
        КонецЕсли;
        Если Расширения.Количество() <> 0 Тогда
            ВызватьИсключение "В новом собственном стенде нет расширений базы или сеанса";
        КонецЕсли;
    КонецЦикла;
    МетаПодпись = Метаданные.Справочники.Найти("УчебныеПодписи85");
    МетаДокумент = Метаданные.Документы.Найти("УчебныйПакет85");
    Если МетаПодпись = Неопределено Или МетаДокумент = Неопределено Тогда
        ВызватьИсключение "Не найдены свои справочник и документ";
    КонецЕсли;
    Свойства = Метаданные.СвойстваОбъектов;
    Если МетаПодпись.Иерархический Или МетаПодпись.Владельцы.Количество() <> 0
        Или МетаПодпись.ТипКода <> Свойства.ТипКодаСправочника.Строка Или МетаПодпись.ДлинаКода <> 3
        Или МетаПодпись.ДлинаНаименования <> 20 Или МетаПодпись.Автонумерация
        Или МетаПодпись.ОсновноеПредставление <> Свойства.ОсновноеПредставлениеСправочника.ВВидеНаименования
        Или МетаПодпись.Реквизиты.Количество() <> 0 Или МетаПодпись.ТабличныеЧасти.Количество() <> 0 Тогда
        ВызватьИсключение "Не совпала собственная схема подписи и представление по наименованию";
    КонецЕсли;
    Имена = МетаПодпись.ПолучитьИменаПредопределенных();
    Если ТипЗнч(Имена) <> Тип("ФиксированныйМассив") Тогда
        ВызватьИсключение "Нативные имена предопределённых должны иметь тип ФиксированныйМассив";
    КонецЕсли;
    Если Имена.Количество() <> 0 Тогда
        ВызватьИсключение "В своём справочнике не должно быть предопределённых имён";
    КонецЕсли;
    Если МетаДокумент.Проведение <> Свойства.Проведение.Запретить
        Или МетаДокумент.ТипНомера <> Свойства.ТипНомераДокумента.Строка Или МетаДокумент.ДлинаНомера <> 8
        Или МетаДокумент.Автонумерация Или МетаДокумент.Реквизиты.Количество() <> 0
        Или МетаДокумент.Движения.Количество() <> 0 Или МетаДокумент.ТабличныеЧасти.Количество() <> 1 Тогда
        ВызватьИсключение "Не совпала собственная схема документа";
    КонецЕсли;
    МетаСтроки = МетаДокумент.ТабличныеЧасти.Найти("Строки85");
    Если МетаСтроки = Неопределено Тогда
        ВызватьИсключение "Не найдена своя табличная часть Строки85";
    КонецЕсли;
    Если МетаСтроки.Реквизиты.Количество() <> 2 Тогда
        ВызватьИсключение "В своей табличной части нужны ровно два реквизита";
    КонецЕсли;
    МетаПозиция = МетаСтроки.Реквизиты.Найти("Позиция");
    МетаКоличество = МетаСтроки.Реквизиты.Найти("Количество");
    Если МетаПозиция = Неопределено Или МетаКоличество = Неопределено Тогда
        ВызватьИсключение "Не найдены собственные Позиция и Количество";
    КонецЕсли;
    Если МетаПозиция.Тип.Типы().Количество() <> 1 Или Не МетаПозиция.Тип.СодержитТип(Тип("Строка")) Тогда
        ВызватьИсключение "Позиция должна иметь единственный тип Строка";
    КонецЕсли;
    Если МетаПозиция.Тип.КвалификаторыСтроки.Длина <> 20
        Или МетаПозиция.Тип.КвалификаторыСтроки.ДопустимаяДлина <> ДопустимаяДлина.Переменная Тогда
        ВызватьИсключение "Позиция должна иметь переменную длину до 20";
    КонецЕсли;
    Если МетаКоличество.Тип.Типы().Количество() <> 1 Или Не МетаКоличество.Тип.СодержитТип(Тип("Число")) Тогда
        ВызватьИсключение "Количество должно иметь единственный тип Число";
    КонецЕсли;
    Если МетаКоличество.Тип.КвалификаторыЧисла.Разрядность <> 4
        Или МетаКоличество.Тип.КвалификаторыЧисла.РазрядностьДробнойЧасти <> 0
        Или МетаКоличество.Тип.КвалификаторыЧисла.ДопустимыйЗнак <> ДопустимыйЗнак.Неотрицательный Тогда
        ВызватьИсключение "Количество должно быть собственным неотрицательным Число(4,0)";
    КонецЕсли;
    Если МетаПозиция.ПроверкаЗаполнения <> ПроверкаЗаполнения.НеПроверять
        Или МетаКоличество.ПроверкаЗаполнения <> ПроверкаЗаполнения.НеПроверять Тогда
        ВызватьИсключение "Автоматическая проверка собственных полей должна быть выключена";
    КонецЕсли;
КонецПроцедуры

Функция ПолностьюСовпадаетСвояСтрока(СтрокаТаблицы, Ожидаемая, Поля, Колонки)
    Для Каждого ИмяПоля Из Поля Цикл
        Если Не Ожидаемая.Свойство(ИмяПоля, Ожидание) Тогда
            ВызватьИсключение "Не найдено своё ожидаемое поле " + ИмяПоля;
        КонецЕсли;
        Колонка = Колонки.Найти(ИмяПоля);
        Факт = СтрокаТаблицы.Получить(Колонки.Индекс(Колонка));
        Если ТипЗнч(Факт) <> ТипЗнч(Ожидание) Тогда
            Возврат Ложь;
        КонецЕсли;
        Если ТипЗнч(Ожидание) <> Тип("Null") Тогда
            Если Факт <> Ожидание Тогда
                Возврат Ложь;
            КонецЕсли;
        КонецЕсли;
    КонецЦикла;
    Возврат Истина;
КонецФункции

Процедура ПроверитьСвоюПолнуюТаблицу(Таблица, Ожидания, Поля, Упорядоченный)
    Если ТипЗнч(Таблица) <> Тип("ТаблицаЗначений") Тогда
        ВызватьИсключение "Нужна своя выгруженная ТаблицаЗначений";
    КонецЕсли;
    Если Таблица.Колонки.Количество() <> Поля.Количество() Или Таблица.Количество() <> Ожидания.Количество() Тогда
        ВызватьИсключение "Не совпали полные размеры своего результата";
    КонецЕсли;
    Для Каждого ИмяПоля Из Поля Цикл
        Если Таблица.Колонки.Найти(ИмяПоля) = Неопределено Тогда
            ВызватьИсключение "Не найдена своя колонка " + ИмяПоля;
        КонецЕсли;
    КонецЦикла;
    Использованные = Новый Массив;
    Если Ожидания.Количество() > 0 Тогда
        Для Индекс = 0 По Ожидания.Количество() - 1 Цикл
            Если Упорядоченный Тогда
                Если Не ПолностьюСовпадаетСвояСтрока(Таблица[Индекс], Ожидания[Индекс], Поля, Таблица.Колонки) Тогда
                    ВызватьИсключение "Не совпали все поля, типы или порядок своей строки";
                КонецЕсли;
            Иначе
                Найден = -1;
                Для Кандидат = 0 По Ожидания.Количество() - 1 Цикл
                    Если Использованные.Найти(Кандидат) = Неопределено Тогда
                        Если ПолностьюСовпадаетСвояСтрока(Таблица[Индекс], Ожидания[Кандидат], Поля, Таблица.Колонки) Тогда
                            Найден = Кандидат;
                            Прервать;
                        КонецЕсли;
                    КонецЕсли;
                КонецЦикла;
                Если Найден = -1 Тогда
                    ВызватьИсключение "Не совпал полный типизированный состав с учётом повторений";
                КонецЕсли;
                Использованные.Добавить(Найден);
            КонецЕсли;
        КонецЦикла;
    КонецЕсли;
КонецПроцедуры

Процедура ПроверитьОтсутствиеСвоихЗаписей()
    Если ТранзакцияАктивна() Тогда
        ВызватьИсключение "Отсутствие записей проверяется вне активной транзакции";
    КонецЕсли;
    Источники = Новый Массив;
    Источники.Добавить("Справочник.УчебныеПодписи85");
    Источники.Добавить("Документ.УчебныйПакет85");
    Источники.Добавить("Документ.УчебныйПакет85.Строки85");
    Для Каждого ИмяИсточника Из Источники Цикл
        ЗапросПустоты = Новый Запрос;
        ЗапросПустоты.Текст = "ВЫБРАТЬ КОЛИЧЕСТВО(*) КАК Количество ИЗ " + ИмяИсточника;
        Таблица = ЗапросПустоты.Выполнить().Выгрузить();
        Если Таблица.Количество() <> 1 Или Таблица.Колонки.Количество() <> 1
            Или Таблица.Колонки.Найти("Количество") = Неопределено Тогда
            ВызватьИсключение "Не совпала полная форма своего счётчика записей";
        КонецЕсли;
        Если ТипЗнч(Таблица[0].Количество) <> Тип("Число") Тогда
            ВызватьИсключение "Свой счётчик должен иметь тип Число";
        КонецЕсли;
        Если Таблица[0].Количество <> 0 Тогда
            ВызватьИсключение "В новой собственной базе не должно быть записей: " + ИмяИсточника;
        КонецЕсли;
    КонецЦикла;
КонецПроцедуры

Функция СоздатьСвойДокумент(Номер, ДатаДокумента)
    Если ТипЗнч(Номер) <> Тип("Строка") Или ТипЗнч(ДатаДокумента) <> Тип("Дата") Тогда
        ВызватьИсключение "Нужны свои типизированные номер и дата";
    КонецЕсли;
    Если СтрДлина(Номер) <> 8 Тогда
        ВызватьИсключение "Свой номер должен иметь длину 8";
    КонецЕсли;
    Объект = Документы.УчебныйПакет85.СоздатьДокумент();
    Объект.Номер = Номер;
    Объект.Дата = ДатаДокумента;
    Если Объект.Строки85.Количество() <> 0 Тогда
        ВызватьИсключение "Новая собственная табличная часть должна быть пустой";
    КонецЕсли;
    Возврат Объект;
КонецФункции

Функция ЗаписатьСвойДокумент(Объект)
    Если ТипЗнч(Объект) <> Тип("ДокументОбъект.УчебныйПакет85") Тогда
        ВызватьИсключение "Записывается только собственный новый документ";
    КонецЕсли;
    Объект.Записать(РежимЗаписиДокумента.Запись);
    Ссылка = Объект.Ссылка;
    Если ТипЗнч(Ссылка) <> Тип("ДокументСсылка.УчебныйПакет85") Тогда
        ВызватьИсключение "После записи нужна своя ссылка документа";
    КонецЕсли;
    Если Ссылка = Документы.УчебныйПакет85.ПустаяСсылка() Тогда
        ВызватьИсключение "Ссылка своего записанного документа не должна быть пустой";
    КонецЕсли;
    Возврат Ссылка;
КонецФункции

Процедура ПроверитьСвойВводСтроки(Позиция, Количество)
    Если ТипЗнч(Позиция) <> Тип("Строка") Тогда
        ВызватьИсключение "Позиция должна иметь тип Строка";
    КонецЕсли;
    Если СтрДлина(Позиция) < 1 Или СтрДлина(Позиция) > 20 Тогда
        ВызватьИсключение "Длина своей позиции должна быть 1..20";
    КонецЕсли;
    Если ТипЗнч(Количество) <> Тип("Число") Тогда
        ВызватьИсключение "Количество должно иметь тип Число";
    КонецЕсли;
    Если Количество < 0 Или Количество > 9999 Или Количество <> Цел(Количество) Тогда
        ВызватьИсключение "Своё количество должно быть целым 0..9999";
    КонецЕсли;
КонецПроцедуры

Процедура ПроверитьОтклонениеСвоейСтроки(Позиция, Количество)
    Отклонён = Ложь;
    Попытка
        ПроверитьСвойВводСтроки(Позиция, Количество);
    Исключение
        Отклонён = Истина;
        Сообщить("Ручной опыт: свой неверный ввод отклонён. " + ОписаниеОшибки());
    КонецПопытки;
    Если Не Отклонён Тогда
        ВызватьИсключение "Ожидалось отклонение данного собственного ввода";
    КонецЕсли;
КонецПроцедуры

Процедура ДобавитьСвоюСтроку(Объект, НомерСтроки, Позиция, Количество)
    Если ТипЗнч(Объект) <> Тип("ДокументОбъект.УчебныйПакет85") Тогда
        ВызватьИсключение "Табличная часть получается только из собственного объекта документа";
    КонецЕсли;
    ПроверитьСвойВводСтроки(Позиция, Количество);
    СтрокаОбъекта = Объект.Строки85.Добавить();
    СтрокаОбъекта.Позиция = Позиция;
    СтрокаОбъекта.Количество = Количество;
    Если ТипЗнч(СтрокаОбъекта.НомерСтроки) <> Тип("Число") Или СтрокаОбъекта.НомерСтроки <> НомерСтроки
        Или ТипЗнч(СтрокаОбъекта.Позиция) <> Тип("Строка") Или СтрокаОбъекта.Позиция <> Позиция
        Или ТипЗнч(СтрокаОбъекта.Количество) <> Тип("Число") Или СтрокаОбъекта.Количество <> Количество Тогда
        ВызватьИсключение "Не совпали все три собственных поля добавленной строки";
    КонецЕсли;
КонецПроцедуры

Процедура ПроверитьСвоиИсходныеДокументы(ОжиданияДокументов, ОжиданияСтрок)
    Проверка = Новый Запрос;
    Проверка.Текст = "ВЫБРАТЬ Док.Ссылка КАК Ссылка, Док.Номер КАК Номер, Док.Дата КАК Дата
    |ИЗ Документ.УчебныйПакет85 КАК Док УПОРЯДОЧИТЬ ПО Док.Номер ВОЗР";
    Поля = Новый Массив;
    Поля.Добавить("Ссылка");
    Поля.Добавить("Номер");
    Поля.Добавить("Дата");
    ПроверитьСвоюПолнуюТаблицу(Проверка.Выполнить().Выгрузить(), ОжиданияДокументов, Поля, Истина);
    Проверка.Текст = "ВЫБРАТЬ Стр.Ссылка КАК Ссылка, Стр.Ссылка.Номер КАК Номер,
    |Стр.НомерСтроки КАК НомерСтроки, Стр.Позиция КАК Позиция, Стр.Количество КАК Количество
    |ИЗ Документ.УчебныйПакет85.Строки85 КАК Стр
    |УПОРЯДОЧИТЬ ПО Стр.Ссылка.Номер ВОЗР, Стр.НомерСтроки ВОЗР";
    Поля = Новый Массив;
    Поля.Добавить("Ссылка");
    Поля.Добавить("Номер");
    Поля.Добавить("НомерСтроки");
    Поля.Добавить("Позиция");
    Поля.Добавить("Количество");
    ПроверитьСвоюПолнуюТаблицу(Проверка.Выполнить().Выгрузить(), ОжиданияСтрок, Поля, Истина);
КонецПроцедуры

Процедура ПроверитьСвоюПаруДокументов(Подробный, Заголовок, СвоиДокументы)
    Если ТипЗнч(Подробный) <> Тип("ДокументСсылка.УчебныйПакет85") Или ТипЗнч(Заголовок) <> Тип("ДокументСсылка.УчебныйПакет85") Тогда
        ВызватьИсключение "Оба параметра должны иметь свой ссылочный тип документа";
    КонецЕсли;
    Если СвоиДокументы.Найти(Подробный) = Неопределено Или СвоиДокументы.Найти(Заголовок) = Неопределено Или Подробный = Заголовок Тогда
        ВызватьИсключение "Нужны два разных записанных документа данного собственного fixture";
    КонецЕсли;
КонецПроцедуры

Процедура ПроверитьОтклонениеСвоейПары(Подробный, Заголовок, СвоиДокументы)
    Отклонён = Ложь;
    Попытка
        ПроверитьСвоюПаруДокументов(Подробный, Заголовок, СвоиДокументы);
    Исключение
        Отклонён = Истина;
        Сообщить("Ручной опыт: своя неверная пара отклонена. " + ОписаниеОшибки());
    КонецПопытки;
    Если Не Отклонён Тогда
        ВызватьИсключение "Ожидалось отклонение данной собственной пары";
    КонецЕсли;
КонецПроцедуры
УчебныйПускПустогоСостава85.Форма

Собственная новая внешняя обработка УчебныйПускПустогоСостава85 → управляемая Форма → Модуль

Отдельный полный клиентский/серверный модуль формы; серверная процедура без контекста вызывает УчебныйЯзык.ВыполнитьПример(). Не смешивать с общим модулем.

&НаКлиенте
Процедура Выполнить(Команда)
    ВыполнитьНаСервере();
    Сообщить("Собственный опыт проверен; его учебные записи отменены");
КонецПроцедуры

&НаСервереБезКонтекста
Процедура ВыполнитьНаСервере()
    УчебныйЯзык.ВыполнитьПример();
КонецПроцедуры

Разбор по шагам

  1. Подготовить именно новый собственный стенд и полный серверный модуль. Guard до действий проверяет версию8.5.1.1343, имена, состав метаданных и все заданные квалификаторы/представление.

  2. ПолучитьИменаПредопределенных возвращает ФиксированныйМассив имён, заданных в конфигурации; проверить его тип/нулевой размер. Это не чтение или поиск записей рабочего справочника.

  3. Native источники расширений — БазаДанных,СеансАктивные,СеансОтключенные. Получить без отбора возвращает массив соответствующего источника; все три должны быть пусты, изменения метаданных расширениями тоже отсутствуют. Модули вручную подготовлены пустыми: их BSL-свойства всегда Неопределено.

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

  5. До Добавить проверяются свои строки: Позиция Строка длиной1..20, количество целое0..9999. Две положительные границы и восемь негативных опытов не добавляют строк; catch только вокруг валидатора.

  6. Минимальный fixture — ровно два документа: OWN00001 от08.10.2026 12:00 с (1,item-A,2),(2,item-B,3) и OWN00002 от06.10.2026 12:00 с (1,hidden-item,9). Ни подпись справочника, ни третий документ не записываются.

  7. Полный исходный состав проверяется до проекции: все ссылки/номера/даты двух документов и ссылки/номера/номера строк/позиции/количества трёх деталей, с типами и порядком источника.

  8. Своим двум параметрам нужны разные реальные ссылки данного fixture. Отсутствующая, строковая, пустая ссылка и совпавшие параметры отклоняются нашим валидатором до запроса, не считаются native ошибкой ПУСТАЯТАБЛИЦА.

  9. Первая ветвь выбирает OWN00002 и ПУСТАЯТАБЛИЦА.(НомерСтроки,Позиция,Количество). Точка перед скобками и три псевдонима взяты из native. Вторая ветвь выбирает OWN00001 с тремя настоящими вложенными полями того же порядка.

  10. UNION ALL сопоставляет поля по позициям, имена результата задаёт первая ветвь. УПОРЯДОЧИТЬ ПО Номер ВОЗР относится к результату объединения и даёт OWN00001,OWN00002.

  11. Исходная выборка содержит Состав именно РезультатЗапроса. Проверить тип обоих полей и булеву Пустой: для подробного Ложь, для заголовка Истина; только затем выгрузить каждый вложенный результат.

  12. Детали без отдельного условия сортировки сравниваются unordered multiset: полный тип/значение каждого поля и кратность повторений, не множество позиций. Пустой результат проверяется по трём именам колонок и нулю строк, без выдуманных типов отсутствующих ячеек.

  13. Повторно проверить все исходные поля. hidden-item/9 остаётся настоящей строкой второго документа: ПУСТАЯТАБЛИЦА сформировала проекцию, не удалила и не скрыла данные правами.

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

  15. Отдельный будущий ручной аварийный опыт: в своей учебной копии полного модуля после первого вызова ПроверитьСвоиИсходныеДокументы либо ПроверитьСвоюИсходнуюПодпись внутри основной процедуры, уже после записей, временно вставьте ВызватьИсключение "Учебная авария после собственных записей";. Повторный запуск должен завершиться исключением без зелёных сообщений. В отладчике проверьте завершение своей транзакции и три нулевых счётчика после отмены. Затем уберите только вставленную строку. Обычные плохие вводы ловятся локально до записи и не проверяют этот внешний аварийный путь; здесь такой опыт не выполнялся.

  16. Форма запуска полностью дана отдельно от общего модуля. Python моделирует только фиксированные данные и ожидания, не запускает BSL/запросы/откат в 1С.

Как проверить результат

  1. Две верхние строки: OWN00001→РезультатЗапроса с полным набором (1,item-A,2),(2,item-B,3); OWN00002→пустой РезультатЗапроса с именами НомерСтроки,Позиция,Количество.

  2. Номер строго Строка; каждая существующая деталь имеет Число/Строка/Число. Порядок деталей не обещан; верхний порядок явный. Пустой строго Булево.

  3. До/после запроса все поля двух документов и трёх исходных деталей совпадают. Строка hidden-item/9 не исчезает из исходника до собственного rollback.

  4. Четыре неверных пары параметров и восемь плохих строк отклоняются собственными валидаторами. Обе ветви отката завершают транзакцию и проверяют три пустых собственных источника.

Ожидаемый результат

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

На что обратить внимание

  1. МодульОбъекта и МодульМенеджера в native всегда Неопределено: сравнение с ним не доказывает пустоту текста. Пустые модули/формы, единственный сеанс и новая база — обязательные условия ручной подготовки.

  2. Предопределённые имена относятся к конфигурации, не равны списку записанных данных. Расширения проверяются read-only; код не подключает, отключает и не удаляет их.

  3. Запись нужна только в предоставленном будущем BSL собственного нового стенда. Здесь 1С/BSL/SQL не запускались, создание и отмена записей не заявляются.

  4. Python моделирует только фиксированные данные/полный состав и логику подготовки/отката. Direct HBK доказывает текст native, а не фактическое исполнение в 1С.

  5. Если сама ОтменитьТранзакцию или последующая проверка пустоты вызывает исключение, сохранение первоначальной диагностики не гарантируется: наружу может выйти ошибка отмены/проверки. Зелёного завершения нет; сначала разберите эту ошибку и проверьте состояние своего стенда. Урок не обещает успешный rollback при сбое самого механизма отмены.

  6. Только указанная raw-тема является новой привязкой. Поддерживающие UNION/поля вложенного результата/ORDER BY/COUNT уже не прибавляются к покрытию.

  7. ПУСТАЯТАБЛИЦА — не пустая настоящая табчасть, не Null и не массив. У второго документа есть строка9, но именно проекция этого запроса возвращает пустой вложенный результат.

  8. Alias-список задаёт имена пустой вложенной таблицы. Типы несуществующих ячеек не выводятся из отсутствующих данных; существующие поля подробного результата проверяются по фактическим значениям.

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

Не хотите разбираться сами?

Адаптируем это решение под вашу базу. Оценка бесплатно.

Оставить задачу

1С, 1С:Предприятие — товарные знаки ООО «1С». Сайт не является официальным представительством фирмы «1С» и не аффилирован с ней.