понедельник, 23 апреля 2012 г.

Обход запроса по нескольким группировкам

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

Если в запросе указать итоги по складу, номенклатуре и характеристике номенклатуры и написать следующий код:
то обход будет производиться следующим образом:
Т.е. по первой группировке "Склад" внешний цикл (голубой) совершит одну итерацию, по второй группировке "Номенклатура" ("розовый" цикл) четыре итерации, в каждой из итераций "розового" цикла будет разное количество итераций "зеленого" цикла по группировке "ХарактеристикаНоменклатуры", и в конечном итоге будут выбраны все детальные записи ("серый" цикл), которые на рисунке я отмечать не стал, ибо лениво.

Но иногда хочется выбрать записи вот таким образом:
Т.е. так, чтобы внешний (голубой) цикл выбирал, как и положено, по группировке "Склад", а внутренний (зеленый) выбирал по некой "агрегатной" группировке "Номенклатура+ХарактеристикаНоменклатуры". Ну и дальше по необходимости детальные записи. Это было бы удобно, если бы вы, например, создавали документы по группировке "Склад", а второй "метагруппировкой" заполняли табличную часть каким-либо образом. Конечно, код, который приведен выше, справляется с этой задачей, но лично моя печаль в том, что там есть один вложенный цикл (розовый), который делается совершенно ненужным с точки зрения "изящества кода" как минимум. К тому же, когда таких группировок становится больше, например девять, мы видим совершенно "потрясающую" картину из девяти вложенных циклов.
Еще можно просто пропустить группировку "Номенклатура" и обходить результат запроса по группировкам "Склад - ХарактеристикаНоменклатуры - ДетальныеЗаписи". Но вот беда, в этом случае на уровне характеристики нет самой номенклатуры. Смотрите сами:

Получается, что на уровне выборки по складу номенклатуры еще нет, а на уровне выборки по характеристике ее... все еще нет. Бида.

Я точно знаю, что я не один такой, но у меня и у других коллег по несчастью как-то сам собой напрашивается вот такой код:

Код прекрасен всем, кроме того... что он не работает. Если когда-нибудь разгадаю это тайное послание фирмы 1С о "списке группировок", я обязательно с вами поделюсь. Сейчас же я знаю только то, что этот код мне не удалось заставить работать ни под каким соусом.

Но мы не ждем милости от природы и от 1С. А берем и сами делаем. Вот такой код работает нормально вполне:

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

РоднаяВыборка1С = МетаВыборка["Выборка"];

Естественно, дальше родную выборку 1С можно хоть снова перебирать этими функциями, хоть выбирать штатными средствами.

Также не могу не сказать, что, в принципе, в функциях нет ничего нового и я тут не претендую на оригинальность или что-нибудь в таком духе. Думаю, что многие писали такие же функции, просто хотелось поделиться с уважаемым сообществом.
Они также не убирают вложенные циклы, просто немного иначе их организуют и скрывают. Т.е. в действительности все "лишние" циклы есть, но из кода это не очевидно.
Еще вполне понятно, что их использование будет менее производительным в смысле быстродействия, нежели использование вороха вложенных циклов. Я, конечно, старался написать пооптимальнее, но сами понимаете, накладные расходы будут.
Да, одна функция рекурсивная. По идее глубина рекурсии будет небольшой и равна количеству пропускаемых группировок плюс один, однако рекурсия есть рекурсия.
Ну и конечно, не исключен вариант, что я где-то накосячил. Если обнаружите - пишите, поправим.

Наконец сами функции:

Функция зфВыбратьПоГруппировкам(Выборка, Группировки, СИерархией = Ложь)

       МетаВыборка = Новый Соответствие;

       врОбходРезультата = ОбходРезультатаЗапроса.ПоГруппировкам;
       Если СИерархией Тогда
               врОбходРезультата = ОбходРезультатаЗапроса.ПоГруппировкамСИерархией;
       КонецЕсли;
       МетаВыборка.Вставить("ОбходРезультата", врОбходРезультата);

       МассивГруппировок = Новый Массив;
       врСтрГруппировки = Группировки;
       Пока Истина Цикл
               Поз = Найти( врСтрГруппировки, "," );
               Если Поз = 0 Тогда
                       МассивГруппировок.Добавить(СокрЛП(врСтрГруппировки));
                       Прервать;
               КонецЕсли;
               МассивГруппировок.Добавить( СокрЛП( Лев(врСтрГруппировки,Поз-1) ) );
               врСтрГруппировки = Сред( врСтрГруппировки, Поз+1 );
       КонецЦикла;

       МетаВыборка.Вставить("Группировки", МассивГруппировок);

       врВыборка = Выборка;
       Для пц=0 По МассивГруппировок.Количество()-2 Цикл
               врВыборкаУровня = врВыборка.Выбрать(врОбходРезультата, МассивГруппировок[пц]);
               МетаВыборка.Вставить("_Выборка"+Строка(пц), врВыборкаУровня);
               Если не врВыборкаУровня.Следующий() Тогда
                       Прервать;
               КонецЕсли;
               врВыборка = врВыборкаУровня;
       КонецЦикла;
       врВыборкаУровня = врВыборка.Выбрать(врОбходРезультата, МассивГруппировок[пц]);
       МетаВыборка.Вставить("Выборка", врВыборкаУровня);
       МетаВыборка.Вставить("_Выборка"+Строка(пц), врВыборкаУровня);

       Возврат МетаВыборка;

КонецФункции // зфВыбратьПоГруппировкам

Функция зфСледующийПоГруппировкам(МетаВыборка, Уровень = Неопределено)

       Если Уровень = Неопределено Тогда
               Уровень = МетаВыборка["Группировки"].Количество()-1;
       КонецЕсли;

       Если Уровень < 0 Тогда
               Возврат Ложь;
       КонецЕсли;

       врВыборка = МетаВыборка["_Выборка"+Строка(Уровень)];

       Если врВыборка.Следующий() Тогда
               Возврат Истина;
       КонецЕсли;

       Если зфСледующийПоГруппировкам(МетаВыборка, Уровень-1) Тогда
               МассивГруппировок = МетаВыборка["Группировки"];
               врВыборкаРодитель = МетаВыборка["_Выборка"+Строка(Уровень-1)];
               врВыборка = врВыборкаРодитель.Выбрать(МетаВыборка["ОбходРезультата"],МассивГруппировок[Уровень]);
               МетаВыборка["_Выборка"+Строка(Уровень)] = врВыборка;
               Если Уровень = МассивГруппировок.Количество()-1 Тогда
                       МетаВыборка["Выборка"] = врВыборка;
               КонецЕсли;
               Возврат зфСледующийПоГруппировкам(МетаВыборка, Уровень);
       Иначе
               Возврат Ложь;
       КонецЕсли;

КонецФункции // зфСледующийПоГруппировкам

Спасибо за внимание, а я желаю вам хорошего дня и хорошего кода.

Публикация на Инфостарт

4 комментария:

byte.mdfab комментирует...

Можно еще в запросе в секции итогов указать МАКСИМУМ(Номенклатура) - тогда она должна появиться при обходе группировки по характеристикам.

Green FiLin комментирует...

О, да! Спасибо, действительно можно так.

Олег Мусин комментирует...

Без ИТОГИ ПО нет и не будет обхода по группировкам, сударь. Вот и вся загадка не работающего кода.

Green FiLin комментирует...

Вы совершенно не поняли суть описываемого в статье вопроса. Код работает.

Отправить комментарий