СГРУППИРОВАНОПО: различить две проекции одной собственной суммы

Учебные задачи

Три собственные строки A/X/2,A/Y/3,B/X/4 группируются отдельно по отделам и ролям. Четыре строки результата получают булевы флаги набора; исключённое поле имеет именно Null, а не пустую строку.

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

Полный серверный общий модуль УчебныйЯзык отдельной учебной базы 8.5.1.1343; собственные типизированные строки и приватная временная таблица. Запросы в 1С здесь не запускались; Python — только модель фиксированных данных.

Подготовка

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

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

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

  4. Все товары, количества и строки придуманы в этом модуле. Запись документов, подключение к чужой базе, файлы, сеть и печать не используются. В рабочие общие модули текст не вставляйте.

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

  6. В новом собственном стенде не нужны прикладные объекты: не создавайте справочники, документы, регистры и подписки ради этого запроса. Все данные, подготовка и ожидания заданы полностью в коде.

  7. Полный отдельный модуль собственной формы УчебныйПускГруппировок85.Форма для описанной команды Выполнить:

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

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

  8. Основной код — полный самостоятельный серверный общий модуль; все помощники должны располагаться на его уровне. Положительные/отрицательные проверки ввода не меняют три строки рабочего источника.

Процедура ВыполнитьПример() Экспорт
    ПроверитьСвойВводГруппы("A", "X", 0);
    ПроверитьСвойВводГруппы("12345678901234567890", "12345678901234567890", 9999);
    ПроверитьОтклонениеСвоегоВводаГруппы(Неопределено, "X", 2);
    ПроверитьОтклонениеСвоегоВводаГруппы("A", 1, 2);
    ПроверитьОтклонениеСвоегоВводаГруппы("", "X", 2);
    ПроверитьОтклонениеСвоегоВводаГруппы("A", "123456789012345678901", 2);
    ПроверитьОтклонениеСвоегоВводаГруппы("A", "X", "2");
    ПроверитьОтклонениеСвоегоВводаГруппы("A", "X", -1);
    ПроверитьОтклонениеСвоегоВводаГруппы("A", "X", 10000);
    ПроверитьОтклонениеСвоегоВводаГруппы("A", "X", 2.5);
    Источник = СоздатьСвойИсточникГрупп();
    ОжиданиеИсточника = Новый Массив;
    ДобавитьСвоюСтрокуГруппы(Источник, "A", "X", 2);
    ОжиданиеИсточника.Добавить(Новый Структура("Отдел,Роль,Количество", "A", "X", 2));
    ДобавитьСвоюСтрокуГруппы(Источник, "A", "Y", 3);
    ОжиданиеИсточника.Добавить(Новый Структура("Отдел,Роль,Количество", "A", "Y", 3));
    ДобавитьСвоюСтрокуГруппы(Источник, "B", "X", 4);
    ОжиданиеИсточника.Добавить(Новый Структура("Отдел,Роль,Количество", "B", "X", 4));
    ПроверитьСвойИсходныйСостав(Источник, ОжиданиеИсточника);
    ОжиданиеРезультата = Новый Массив;
    ОжиданиеРезультата.Добавить(Новый Структура("Отдел,Роль,Количество,ПоОтделу,ПоРоли", "A", Null, 5, Истина, Ложь));
    ОжиданиеРезультата.Добавить(Новый Структура("Отдел,Роль,Количество,ПоОтделу,ПоРоли", "B", Null, 4, Истина, Ложь));
    ОжиданиеРезультата.Добавить(Новый Структура("Отдел,Роль,Количество,ПоОтделу,ПоРоли", Null, "X", 6, Ложь, Истина));
    ОжиданиеРезультата.Добавить(Новый Структура("Отдел,Роль,Количество,ПоОтделу,ПоРоли", Null, "Y", 3, Ложь, Истина));
    Менеджер = Новый МенеджерВременныхТаблиц;
    Попытка
        Создание = Новый Запрос;
        Создание.МенеджерВременныхТаблиц = Менеджер;
        Создание.Текст = "ВЫБРАТЬ Данные.Отдел КАК Отдел, Данные.Роль КАК Роль, Данные.Количество КАК Количество
        |ПОМЕСТИТЬ ВТСобственныеГруппы
        |ИЗ &Источник КАК Данные";
        Создание.УстановитьПараметр("Источник", Источник);
        Создание.Выполнить();
        Запрос = Новый Запрос;
        Запрос.МенеджерВременныхТаблиц = Менеджер;
        Запрос.Текст = "ВЫБРАТЬ
        |    Данные.Отдел КАК Отдел,
        |    Данные.Роль КАК Роль,
        |    СУММА(Данные.Количество) КАК Количество,
        |    СГРУППИРОВАНОПО(Данные.Отдел) КАК ПоОтделу,
        |    СГРУППИРОВАНОПО(Данные.Роль) КАК ПоРоли
        |ИЗ ВТСобственныеГруппы КАК Данные
        |СГРУППИРОВАТЬ ПО ГРУППИРУЮЩИМ НАБОРАМ ((Данные.Отдел), (Данные.Роль))";
        Результат = Запрос.Выполнить().Выгрузить();
        ПроверитьСвойПолныйРезультатГрупп(Результат, ОжиданиеРезультата);
        ПроверитьСвойИсходныйСостав(Источник, ОжиданиеИсточника);
    Исключение
        Менеджер.Закрыть();
        ВызватьИсключение;
    КонецПопытки;
    Менеджер.Закрыть();
КонецПроцедуры

Функция СоздатьСвойИсточникГрупп()
    Таблица = Новый ТаблицаЗначений;
    ТипПодписи = Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(20, ДопустимаяДлина.Переменная));
    Таблица.Колонки.Добавить("Отдел", ТипПодписи);
    Таблица.Колонки.Добавить("Роль", ТипПодписи);
    Таблица.Колонки.Добавить("Количество", Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(4, 0, ДопустимыйЗнак.Неотрицательный)));
    Возврат Таблица;
КонецФункции

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

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

Процедура ДобавитьСвоюСтрокуГруппы(Таблица, Отдел, Роль, Количество)
    ПроверитьСвойВводГруппы(Отдел, Роль, Количество);
    СтрокаИсточника = Таблица.Добавить();
    СтрокаИсточника.Отдел = Отдел;
    СтрокаИсточника.Роль = Роль;
    СтрокаИсточника.Количество = Количество;
КонецПроцедуры

Функция СовпадаетСвоёЗначение(Факт, Ожидание)
    Если ТипЗнч(Ожидание) = Тип("Null") Тогда
        Возврат ТипЗнч(Факт) = Тип("Null");
    КонецЕсли;
    Если ТипЗнч(Факт) <> ТипЗнч(Ожидание) Тогда
        Возврат Ложь;
    КонецЕсли;
    Возврат Факт = Ожидание;
КонецФункции

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

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

Процедура ПроверитьСвойПолныйРезультатГрупп(Таблица, Ожидание)
    Если Таблица.Колонки.Количество() <> 5 Или Таблица.Количество() <> 4 Или Ожидание.Количество() <> 4 Тогда
        ВызватьИсключение "Должны быть пять колонок и четыре собственные строки группирующих наборов";
    КонецЕсли;
    Если Таблица.Колонки.Найти("Отдел") = Неопределено Или Таблица.Колонки.Найти("Роль") = Неопределено
        Или Таблица.Колонки.Найти("Количество") = Неопределено Или Таблица.Колонки.Найти("ПоОтделу") = Неопределено
        Или Таблица.Колонки.Найти("ПоРоли") = Неопределено Тогда
        ВызватьИсключение "Не совпали имена всех пяти полей результата";
    КонецЕсли;
    Найденные = Новый Массив;
    СуммаПоОтделам = 0;
    СуммаПоРолям = 0;
    Для Каждого СтрокаРезультата Из Таблица Цикл
        ИндексОжидания = НайтиПолнуюОжидаемуюСтроку(СтрокаРезультата, Ожидание);
        Если Найденные.Найти(ИндексОжидания) <> Неопределено Тогда
            ВызватьИсключение "Полная ожидаемая строка результата повторилась";
        КонецЕсли;
        Найденные.Добавить(ИндексОжидания);
        Если СтрокаРезультата.ПоОтделу Тогда
            СуммаПоОтделам = СуммаПоОтделам + СтрокаРезультата.Количество;
        Иначе
            СуммаПоРолям = СуммаПоРолям + СтрокаРезультата.Количество;
        КонецЕсли;
        Сообщить("Полная строка проверена: номер собственного ожидания=" + Строка(ИндексОжидания));
    КонецЦикла;
    Если Найденные.Количество() <> 4 Или СуммаПоОтделам <> 9 Или СуммаПоРолям <> 9 Тогда
        ВызватьИсключение "Полный состав и две отдельные проекции одной собственной суммы не совпали";
    КонецЕсли;
    Сообщить("Отделы A/5,B/4 и роли X/6,Y/3 проверены полностью. Каждая проекция даёт 9, проекции не складываются");
КонецПроцедуры
УчебныйПускГруппировок85.Форма

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

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

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

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

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

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

  2. Свой внешний источник содержит два единственных строковых типа Строка(20), переменная длина, и единственное неотрицательное Число(4,0). Точные строки: A/X/2,A/Y/3,B/X/4, исходная сумма 9.

  3. Собственный ввод проверяет типы до длины/диапазона и до добавления строки. Подписи длиной 1..20, количество целое 0..9999 — наши дополнительные правила, не ограничения функции СГРУППИРОВАНОПО.

  4. Без добавления данных проверить две разрешённые границы и восемь плохих входов: отсутствующий отдел, число вместо роли, пустая/слишком длинная подпись, строковое, отрицательное, слишком большое и дробное количество. Catch охватывает только свой валидатор.

  5. Поместить три строки в ВТСобственныеГруппы своего менеджера простым запросом без группировки внешнего источника. Native INTO требует явных типов и ограничивает сложные конструкции такого подготовительного запроса; группировка идёт уже над своей временной таблицей.

  6. ГРУППИРУЮЩИМ НАБОРАМ ((Отдел),(Роль)) задаёт две раздельные группы: по отделу и по роли. Это не единый набор (Отдел,Роль), который дал бы три комбинации исходных строк.

  7. Выбрать оба исходных поля, сумму количества и две СГРУППИРОВАНОПО. Аргумент каждой функции — отдельный столбец, участвующий в СГРУППИРОВАТЬ ПО; выражение из двух полей разных групп здесь не составляется.

  8. Native GROUPING возвращает ИСТИНА, если строка получена для группировки по заданному полю, и ЛОЖЬ иначе. Это именно Булево в 1С, не числовые 0/1 другой системы запросов. Типы обоих флагов проверяются явно.

  9. Проекция отделов содержит A/5 и B/4; флаги ПоОтделу=Истина,ПоРоли=Ложь. Роль исключена из текущего набора и имеет Null по native GROUPStatement.

  10. Проекция ролей содержит X/6 и Y/3; флаги ПоОтделу=Ложь,ПоРоли=Истина. Отдел исключён и имеет Null. Проверить точные значения сумм, оба флага и все типы каждого поля каждой строки.

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

  12. Для ожидаемого Null проверяется ТипЗнч, а не сравнение Null с текстом. Для остальных четырёх значений строки сначала сравниваются типы, затем значения; пустая строка и Неопределено не принимаются вместо исключённого поля.

  13. Сумма проекции отделов равна 9, сумма проекции ролей тоже 9. Это две проекции одной исходной суммы: складывать их в общий бизнес-результат нельзя, иначе одни исходные данные будут учтены дважды.

  14. Повторно проверить полный состав и типы исходных трёх строк. Закрыть только свой менеджер при успехе и ошибке; неожиданная ошибка подготовки, запроса или проверки после закрытия возбуждается повторно.

  15. Полный отдельный модуль формы запуска предоставлен в preparation/supportingModules. Он вызывает серверную экспортную точку; его директивы не вставляются внутрь общего модуля.

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

  1. Полные строки результата (Отдел,Роль,Количество,ПоОтделу,ПоРоли): (A,Null,5,Истина,Ложь),(B,Null,4,Истина,Ложь),(Null,X,6,Ложь,Истина),(Null,Y,3,Ложь,Истина).

  2. Все пять колонок и четыре строки проверены независимо от порядка. Подпись активной группы имеет тип Строка, исключённое поле именно Null, количество Число, оба флага Булево.

  3. Каждая проекция отдельно даёт 9. В коде нет бизнес-суммы, складывающей обе проекции, и нет предложения ИТОГИ.

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

  5. Исходный полный состав A/X/2,A/Y/3,B/X/4 сохраняется с теми же типами. Свой менеджер закрывается при успехе и неожиданной ошибке.

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

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

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

  1. СГРУППИРОВАНОПО относится к группирующим наборам, а не к обходу строк ИТОГИ, ТипЗаписи или уровню выборки. Здесь никакие итоговые записи и дополнительные детали не создаются.

  2. ИСТИНА означает, что поле участвовало в текущем наборе группировки; это не универсальный признак заполненности поля. Native не определяет функцию как проверку пустой строки или Null.

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

  4. Native GROUPStatement поддерживает группирующие наборы только для таблиц верхнего уровня. Наш источник — верхнеуровневая собственная временная таблица, не вложенная табличная часть.

  5. Native запрещает выражения списка выборки с двумя и более полями, относящимися к разным группам. Здесь каждый аргумент СГРУППИРОВАНОПО — одно своё поле; объединённый текст Отдел+Роль не используется.

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

  7. Python моделирует только фиксированные данные, полный ожидаемый набор и отдельные суммы проекций; сверка установленного HBK подтверждает текст контрактов. Это не исполнение SQL или BSL в движке 1С.

  8. GROUPStatement, SUM и INTO — поддерживающие нативные страницы, а не новые привязки этого урока. Единственная заявленная новая тема — GROUPING.

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

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

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

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

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