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

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

Т.е. так, чтобы внешний (голубой) цикл выбирал, как и положено, по группировке «Склад», а внутренний (зеленый) выбирал по некой «агрегатной» группировке «Номенклатура+ХарактеристикаНоменклатуры». Ну и дальше по необходимости детальные записи. Это было бы удобно, если бы вы, например, создавали документы по группировке «Склад», а второй «метагруппировкой» заполняли табличную часть каким-либо образом. Конечно, код, который приведен выше, справляется с этой задачей, но лично моя печаль в том, что там есть один вложенный цикл (розовый), который делается совершенно ненужным с точки зрения «изящества кода» как минимум. К тому же, когда таких группировок становится больше, например девять, мы видим совершенно «потрясающую» картину из девяти вложенных циклов.
Еще можно просто пропустить группировку «Номенклатура» и обходить результат запроса по группировкам «Склад — ХарактеристикаНоменклатуры — ДетальныеЗаписи». Но вот беда, в этом случае на уровне характеристики нет самой номенклатуры. Смотрите сами:
Получается, что на уровне выборки по складу номенклатуры еще нет, а на уровне выборки по характеристике ее… все еще нет. Бида.
Я точно знаю, что я не один такой, но у меня и у других коллег по несчастью как-то сам собой напрашивается вот такой код:
Код прекрасен всем, кроме того… что он не работает. Если когда-нибудь разгадаю это тайное послание фирмы 1С о «списке группировок», я обязательно с вами поделюсь. Сейчас же я знаю только то, что этот код мне не удалось заставить работать ни под каким соусом.
Но мы не ждем милости от природы и от 1С. А берем и сами делаем. Вот такой код работает нормально вполне:
Пара слов о функциях.
Функция «зфВыбратьПоГруппировкам» применяется вместо «ВыборкаЛалала.Выбрать()». Ей передается выборка, из которой нужно выбирать, и перечень группировок через запятую. При этом она возвращает некую «метавыборку». Ничего военного, просто соответствие с необходимыми данными.
Функция «зфСледующийПоГруппировкам» применяется вместо «ВыборкаОлоло.Следующий()». Ей передается та самая, открытая на предыдущем шаге «метавыборка» и возвращает она истину или ложь, как и штатный метод «Следующий».
Да. Внутри цикла вы можете смело получать родную 1С-овскую выборку нижнего уровня группировок, обратившись к элементу соответствия «Выборка». Вот так:
РоднаяВыборка1С = МетаВыборка[«Выборка»];
Естественно, дальше родную выборку 1С можно хоть снова перебирать этими функциями, хоть выбирать штатными средствами.
Также не могу не сказать, что, в принципе, в функциях нет ничего нового и я тут не претендую на оригинальность или что-нибудь в таком духе. Думаю, что многие писали такие же функции, просто хотелось поделиться с уважаемым сообществом.
Они также не убирают вложенные циклы, просто немного иначе их организуют и скрывают. Т.е. в действительности все «лишние» циклы есть, но из кода это не очевидно.
Еще вполне понятно, что их использование будет менее производительным в смысле быстродействия, нежели использование вороха вложенных циклов. Я, конечно, старался написать пооптимальнее, но сами понимаете, накладные расходы будут.
Да, одна функция рекурсивная. По идее глубина рекурсии будет небольшой и равна количеству пропускаемых группировок плюс один, однако рекурсия есть рекурсия.
Ну и конечно, не исключен вариант, что я где-то накосячил. Если обнаружите — пишите, поправим.
Наконец сами функции:
Функция зфВыбратьПоГруппировкам(Выборка, Группировки, СИерархией = Ложь)
МетаВыборка = Новый Соответствие;
врОбходРезультата = ОбходРезультатаЗапроса.ПоГруппировкам;
Если СИерархией Тогда
врОбходРезультата = ОбходРезультатаЗапроса.ПоГруппировкамСИерархией;
КонецЕсли;
МетаВыборка.Вставить(«ОбходРезультата», врОбходРезультата);
МассивГруппировок = Новый Массив;
врСтрГруппировки = Группировки;
Пока Истина Цикл
Поз = Найти( врСтрГруппировки, «,» );
Если Поз = 0 Тогда
МассивГруппировок.Добавить(СокрЛП(врСтрГруппировки));
Прервать;
КонецЕсли;
МассивГруппировок.Добавить( СокрЛП( Лев(врСтрГруппировки,Поз—1) ) );
врСтрГруппировки = Сред( врСтрГруппировки, Поз+1 );
КонецЦикла;
МетаВыборка.Вставить(«Группировки», МассивГруппировок);
врВыборка = Выборка;
Для пц=0 По МассивГруппировок.Количество()-2 Цикл
врВыборкаУровня = врВыборка.Выбрать(врОбходРезультата, МассивГруппировок[пц]);
МетаВыборка.Вставить(«_Выборка»+Строка(пц), врВыборкаУровня);
Если не врВыборкаУровня.Следующий() Тогда
Прервать;
КонецЕсли;
врВыборка = врВыборкаУровня;
КонецЦикла;
врВыборкаУровня = врВыборка.Выбрать(врОбходРезультата, МассивГруппировок[пц]);
МетаВыборка.Вставить(«Выборка», врВыборкаУровня);
МетаВыборка.Вставить(«_Выборка»+Строка(пц), врВыборкаУровня);
Возврат МетаВыборка;
КонецФункции // зфВыбратьПоГруппировкам
Функция зфСледующийПоГруппировкам(МетаВыборка, Уровень = Неопределено)
Если Уровень = Неопределено Тогда
Уровень = МетаВыборка[«Группировки»].Количество()-1;
КонецЕсли;
Если Уровень < 0 Тогда
Возврат Ложь;
КонецЕсли;
врВыборка = МетаВыборка[«_Выборка»+Строка(Уровень)];
Если врВыборка.Следующий() Тогда
Возврат Истина;
КонецЕсли;
Если зфСледующийПоГруппировкам(МетаВыборка, Уровень—1) Тогда
МассивГруппировок = МетаВыборка[«Группировки»];
врВыборкаРодитель = МетаВыборка[«_Выборка»+Строка(Уровень—1)];
врВыборка = врВыборкаРодитель.Выбрать(МетаВыборка[«ОбходРезультата»],МассивГруппировок[Уровень]);
МетаВыборка[«_Выборка»+Строка(Уровень)] = врВыборка;
Если Уровень = МассивГруппировок.Количество()-1 Тогда
МетаВыборка[«Выборка»] = врВыборка;
КонецЕсли;
Возврат зфСледующийПоГруппировкам(МетаВыборка, Уровень);
Иначе
Возврат Ложь;
КонецЕсли;
КонецФункции // зфСледующийПоГруппировкам
Спасибо за внимание, а я желаю вам хорошего дня и хорошего кода.

