Хочу поделиться с вами небольшой «зарисовкой», которая помогает мне обходить выборку запроса, как бы пропуская некоторые группировки.
Что я имею в виду. Предположим, у нас есть запрос, который выбирает из регистра движения по номенклатуре в разрезе склада, номенклатуры, характеристики номенклатуры и документа движения. Вот как выглядит «плоская» выгрузка результата этого запроса:
Если в запросе указать итоги по складу, номенклатуре и характеристике номенклатуры и написать следующий код:
то обход будет производиться следующим образом:
Т.е. по первой группировке «Склад» внешний цикл (голубой) совершит одну итерацию, по второй группировке «Номенклатура» («розовый» цикл) четыре итерации, в каждой из итераций «розового» цикла будет разное количество итераций «зеленого» цикла по группировке «ХарактеристикаНоменклатуры», и в конечном итоге будут выбраны все детальные записи («серый» цикл), которые на рисунке я отмечать не стал, ибо лениво.
Но иногда хочется выбрать записи вот таким образом:
Т.е. так, чтобы внешний (голубой) цикл выбирал, как и положено, по группировке «Склад», а внутренний (зеленый) выбирал по некой «агрегатной» группировке «Номенклатура+ХарактеристикаНоменклатуры». Ну и дальше по необходимости детальные записи. Это было бы удобно, если бы вы, например, создавали документы по группировке «Склад», а второй «метагруппировкой» заполняли табличную часть каким-либо образом. Конечно, код, который приведен выше, справляется с этой задачей, но лично моя печаль в том, что там есть один вложенный цикл (розовый), который делается совершенно ненужным с точки зрения «изящества кода» как минимум. К тому же, когда таких группировок становится больше, например девять, мы видим совершенно «потрясающую» картину из девяти вложенных циклов.
Еще можно просто пропустить группировку «Номенклатура» и обходить результат запроса по группировкам «Склад — ХарактеристикаНоменклатуры — ДетальныеЗаписи». Но вот беда, в этом случае на уровне характеристики нет самой номенклатуры. Смотрите сами:
Получается, что на уровне выборки по складу номенклатуры еще нет, а на уровне выборки по характеристике ее… все еще нет. Бида.
Я точно знаю, что я не один такой, но у меня и у других коллег по несчастью как-то сам собой напрашивается вот такой код:
Код прекрасен всем, кроме того… что он не работает. Если когда-нибудь разгадаю это тайное послание фирмы 1С о «списке группировок», я обязательно с вами поделюсь. Сейчас же я знаю только то, что этот код мне не удалось заставить работать ни под каким соусом.
Но мы не ждем милости от природы и от 1С. А берем и сами делаем. Вот такой код работает нормально вполне:
Пара слов о функциях.
Функция «зфВыбратьПоГруппировкам» применяется вместо «ВыборкаЛалала.Выбрать()». Ей передается выборка, из которой нужно выбирать, и перечень группировок через запятую. При этом она возвращает некую «метавыборку». Ничего военного, просто соответствие с необходимыми данными.
Функция «зфСледующийПоГруппировкам» применяется вместо «ВыборкаОлоло.Следующий()». Ей передается та самая, открытая на предыдущем шаге «метавыборка» и возвращает она истину или ложь, как и штатный метод «Следующий».
Да. Внутри цикла вы можете смело получать родную 1С-овскую выборку нижнего уровня группировок, обратившись к элементу соответствия «Выборка». Вот так:
РоднаяВыборка1С = МетаВыборка[«Выборка»];
Естественно, дальше родную выборку 1С можно хоть снова перебирать этими функциями, хоть выбирать штатными средствами.
Также не могу не сказать, что, в принципе, в функциях нет ничего нового и я тут не претендую на оригинальность или что-нибудь в таком духе. Думаю, что многие писали такие же функции, просто хотелось поделиться с уважаемым сообществом.
Они также не убирают вложенные циклы, просто немного иначе их организуют и скрывают. Т.е. в действительности все «лишние» циклы есть, но из кода это не очевидно.
Еще вполне понятно, что их использование будет менее производительным в смысле быстродействия, нежели использование вороха вложенных циклов. Я, конечно, старался написать пооптимальнее, но сами понимаете, накладные расходы будут.
Да, одна функция рекурсивная. По идее глубина рекурсии будет небольшой и равна количеству пропускаемых группировок плюс один, однако рекурсия есть рекурсия.
Ну и конечно, не исключен вариант, что я где-то накосячил. Если обнаружите — пишите, поправим.
Наконец сами функции:
Функция зфВыбратьПоГруппировкам(Выборка, Группировки, СИерархией = Ложь)
МетаВыборка = Новый Соответствие;
врОбходРезультата = ОбходРезультатаЗапроса.ПоГруппировкам;
Если СИерархией Тогда
врОбходРезультата = ОбходРезультатаЗапроса.ПоГруппировкамСИерархией;
КонецЕсли;
МетаВыборка.Вставить(«ОбходРезультата», врОбходРезультата);
МассивГруппировок = Новый Массив;
врСтрГруппировки = Группировки;
Пока Истина Цикл
Поз = Найти( врСтрГруппировки, «,» );
Если Поз = 0 Тогда
МассивГруппировок.Добавить(СокрЛП(врСтрГруппировки));
Прервать;
КонецЕсли;
МассивГруппировок.Добавить( СокрЛП( Лев(врСтрГруппировки,Поз—1) ) );
врСтрГруппировки = Сред( врСтрГруппировки, Поз+1 );
КонецЦикла;
МетаВыборка.Вставить(«Группировки», МассивГруппировок);
врВыборка = Выборка;
Для пц=0 По МассивГруппировок.Количество()-2 Цикл
врВыборкаУровня = врВыборка.Выбрать(врОбходРезультата, МассивГруппировок[пц]);
МетаВыборка.Вставить(«_Выборка»+Строка(пц), врВыборкаУровня);
Если не врВыборкаУровня.Следующий() Тогда
Прервать;
КонецЕсли;
врВыборка = врВыборкаУровня;
КонецЦикла;
врВыборкаУровня = врВыборка.Выбрать(врОбходРезультата, МассивГруппировок[пц]);
МетаВыборка.Вставить(«Выборка», врВыборкаУровня);
МетаВыборка.Вставить(«_Выборка»+Строка(пц), врВыборкаУровня);
Возврат МетаВыборка;
КонецФункции // зфВыбратьПоГруппировкам
Функция зфСледующийПоГруппировкам(МетаВыборка, Уровень = Неопределено)
Если Уровень = Неопределено Тогда
Уровень = МетаВыборка[«Группировки»].Количество()-1;
КонецЕсли;
Если Уровень < 0 Тогда
Возврат Ложь;
КонецЕсли;
врВыборка = МетаВыборка[«_Выборка»+Строка(Уровень)];
Если врВыборка.Следующий() Тогда
Возврат Истина;
КонецЕсли;
Если зфСледующийПоГруппировкам(МетаВыборка, Уровень—1) Тогда
МассивГруппировок = МетаВыборка[«Группировки»];
врВыборкаРодитель = МетаВыборка[«_Выборка»+Строка(Уровень—1)];
врВыборка = врВыборкаРодитель.Выбрать(МетаВыборка[«ОбходРезультата»],МассивГруппировок[Уровень]);
МетаВыборка[«_Выборка»+Строка(Уровень)] = врВыборка;
Если Уровень = МассивГруппировок.Количество()-1 Тогда
МетаВыборка[«Выборка»] = врВыборка;
КонецЕсли;
Возврат зфСледующийПоГруппировкам(МетаВыборка, Уровень);
Иначе
Возврат Ложь;
КонецЕсли;
КонецФункции // зфСледующийПоГруппировкам
Спасибо за внимание, а я желаю вам хорошего дня и хорошего кода.
В основном блогеколлега советует для данного примера применить к полю «Номенклатура» агрегатную функцию МАКСИМУМ.
Не пробовал, но почему ему не работать?
Непонятно мне, а почему нельзя подготовить текст запроса таким образом чтобы получить простую выборку для простого обхода вот без этих всех квадратных треуголок? )
(2) Новиков, Да, почему ж нельзя? Можно, конечно! Более того, это было бы даже лучше и быстрее.
Но иногда приходится и с треуголками. =)
А предложите свой вариант для такого обхода?
(3) я признаться, уже забыл, когда руками обходил результат запроса с группировками. Я обычно делаю базовую схему компоновки, и при помощи настроек компоновки (двигаю группировки вверх и вниз) получаю нужный резалт. И уже его как-то обрабатываю. Т.е. даже текст запроса никакого не нужно собирать. А Ваш вариант — он наверное крут, я не спорю. И наверное даже где-то находит применение. Вот и хотелось бы узнать — где! :%)
(4) Новиков, А! СКД. Да, я тоже люблю скормить данные СКД, а потом получить их в нужно виде и по ним ходить. Но, вот недавно решил выбрать просто запросом и просто ходить по группировкам без СКД. Как-раз документы создавал. Так и применял.
Глупо было бы утверждать, что это единственный и уникальный способ и я сейчас выдам прям такую исключительную ситуацию, где запрос применить можно, а СКД нельзя.
С другой стороны какое-то внутреннее стремление к простоте протестует против того, чтобы «городить ЦЕЛУЮ СКД», когда надо просто обойти группировки простого запроса. Если бы это было возможно реализовать штатными средствами (как в не работающем примере), это был бы win. А поскольку сами функции получились достаточно «мозгачными», то это, конечно не win, а так — посокрушаться.
Хм, никогда не пользуюсь указанным обходом. Работаю с плоской таблицей значений, можно как угодно выкручивать, сортировать и обходить, безо всяких доп.функций и страшных вложений циклов. В самом хитром случае учреждаются доп.колонки для управления обходом (прям в запросе, или одним обходом таблицы), а дальше рулёжка уже по ним.
Да, и СКД рулит, конечно. Чего её «городить», дело-то простое, а выигрыш иной раз нехилый.
Круто, но применение непонятно.
И мне почему то удобней не выборками пользоваться, а ТаблицаЗначений = Запрос.Выполнить().Выгрузить();
И мне кажется это к тому же просто быстрее. Как раз применимо для случая, когда надо что-то чем то заполнить, нет разве ?
Решение конечно для конкретной задачи, такой подход возможно имеет смысл в ситуации когда таблица имеет очень большой объем.И выбирать ее несколько раз для каждой выборки в рамках одной процедуры очень долго.Может быть конечно так разъяснено что не совсем понятно цель.В общем все от ситуации.
(8) Вопрос знатокам — выборка, полученная из результата запроса методом «Выбрать», или сам результат, кэшируется где-либо или болтается в оперативке? Если да, то в клиентской или где?
Друзья, но когда мне нужно обойти плоскую таблицу, мне же нужно понимать момент, когда у меня появляется другой склад в первой колонке, чтобы, если взять уже приведенный пример, создать новый документ с ДРУГИМ складом. Как же это реализовать в плоской выборке?
Т.е. тот случай, когда на разных «уровнях группировок» реализуется разное поведение, разный код.
Т.е. я сейчас не защищаю свои функции, но интересен сам принцип.
я последнее продолжительное время возвращаю запросом дерево, да, с составным ключом
циклы упрощаются до невозможности
и самое главное масштабирование кода также упрощается в разы! Достаточно изменить состав ключа в запросе — и всё, никакой 1С код в цикле менять не приходится
особенно помогла в этом метода «конвертации» даты в строку прям в запросе для успешного склеивания
(12) romansun, напишите пожалуйста публикацию! Хотя бы пару строк с примером 🙂
(12) romansun, Хм. Насколько я понимаю, в запросе можно создать составной ключ склеиванием строк. Но поддерживается ли уникальность при конвертации ссылочных типов в строку? Т.е. вы получаете представление ссылки или просто представление, как наименование, код, НомерДок и т.д?
Если нет пустых характеристик, можно сделать запрос без группирповки итогов и воспользоваться методом Выборка.СледующийПоЗначениюПоля()
Описание метода наИТС
либо как предложил Новиков в (2) прямой запрос, без группировок остортировать по списку полей шапки:
Показать
(1) Максимум при группировке по Характеристики не поможет, если есть пустые характеристики.
(6) Yashazz,
Согласен с вами, но при большом объеме иногда удобнее прогуливаться по иерархии.
Как раз на прошлой неделе столкнулся с такой проблемой, делал перенос с 7.7 на 8.2 (да знаю, что есть куча обработок и Конвертация данных, которую пока не освоил к сожалению), каждый раз делать запросы или сортировать/обходить таблицу со всей номенклатурой, контрагентами и т.д. накладно (хотя какая разница, один раз перенос будет).
Автору + — еще одно интересное решение в копилку, думаю пригодится, хотя использовать буду редко.
(12) romansun,
Спасибо за наводку.
(15) i132, За «СледующийПоЗначениюПоля()» спасибо, нужно будет посмотреть.
А по поводу кода. Мне кажется или у вас там первая строка проглатывается? Когда вы в выборке дважды в подряд «Следующий()» вызываете?
Ну в целом, после первого «Следующий» выборку можно и на начало сбросить, так что принцип понятен.
Но вот именно от такого кода и хочется избавиться в первую очередь. Собственно, эти процедуры я и «затеял», чтобы уйти от «мониторинга момента смены ключа».
Чем мне не нравится такой подход, это во-первых тем, что логика алгоритма несколько «размазывается» по коду. Мы имеем заполнение внутри цикла, создание и запись документа там же, а еще такие же блоки за пределами цикла. Причем, когда мы записываем документ внутри цикла, имеется документ относящийся к предыдущей итерации выборки, а данные в выборке соответствуют уже новому. Все это, что ни говори, повышает сложность кода. Да, что я говорю, думаю все отписавшиеся не раз писали код, который мониторил бы смену одного из полей и в зависимости от этого что-то происходил.
А еще мне очень не нравится то, что код дублируется. Хорошо, когда это одна строка «ЗаполнитьЗначенияСвойств», но когда по смене ключа предусматриваются более обширные действия, блок в начале (до начала цикла) и блок в цикле нужно не забывать обновлять одновременно. Так же как и «Записать()», если нужна не просто запись, а еще какие-то финализирующие действия.
А хочется стройной конструкции, где на одном уровне вложенности документ создавался бы, затем как-то заполнялся (следующая вложенность) и потом на том же уровне и записывался. Без разделения и дублирования.
(18) >первая строка проглатывается
упс, спасибо, не заметил действительно обработку первой строки пропустил. старый пост (15) переписал.
Достаточно интересная статья, написано доходчиво и наглядно, сразу чувствуется высокий уровень автора! Предложенная идея больше смахивает на «лабораторную» раскопку 1С.
хорошая статья,спасибо за инфу.
Огромное спасибо автору!!
Как раз сегодня мучился с подобной задачей. С кодом, приведенным автором, у меня все заработало!!
Респект и уважуха!!!
Хорошая статья. Тоже сижу разбираюсь с обходом группировок в запросе, а тут как раз в тему.
Действительно, актуально!
Спасибо, хорошая статья. И в обсуждении как всегда много важного!
А если переделать запрос та вот так:
Показать
Верхняя группировка будет по складу, а нижняя — по номенклатуре + характеристике.
Или я не верно понял?
В таком простом случае, да сработает. Но это все же немного не то.
А если две составных группировки?
Например склад+контрагент и товар+характеристика.
Вот вам применение: есть документы, там 13 реквизитов, надо выявить одинаковые документы, если их два и более то один провести, остальные пометить на удаление. Методом группировок мы отсеиваем одинаковые, оставляем только одинаковые два и более документа, потом группируем по всем 13 реквизитам и на выходе имеем на самом нижнем уровне одинаковые записи двух и более документов. При первом входе проводим, при втором и последующих — метим на удаление.
Полезная статья по группировкам! Мне оказалась полезной!
«Бида.»….тут опечатка или так задумано? =)
(30) Так и задумано. =)
Саша, спасибо 🙂 Быстро загуглилось и пригодилось!
(32) О, приветствую! Ну, вот, хорошо что пригодилось. Я когда-то полголовы себе об это сломал. Значит не зря.
Посмотрите как это реализовано на диске ИТС
https://its.1c.ru/db/pubqlang#content:64:hdoc
&НаСервереБезКонтекста
Процедура ВыполнитьЗапрос()
Запрос = Новый Запрос;
Запрос.Текст =
«ВЫБРАТЬ
| ПриходнаяНакладнаяСостав.Товар КАК Товар,
| ПриходнаяНакладнаяСостав.Количество КАК Количество
|ИЗ
| Документ.ПриходнаяНакладная.Состав КАК ПриходнаяНакладнаяСостав
|УПОРЯДОЧИТЬ ПО
| Товар
|ИТОГИ
| СУММА(Количество)
|ПО
| Товар ИЕРАРХИЯ»;
РезультатЗапроса = Запрос.Выполнить();
СпособВыборки = ОбходРезультатаЗапроса.ПоГруппировкамСИерархией;
ВыборкаЗапроса = РезультатЗапроса.Выбрать(СпособВыборки);
ВыдатьВсеВложения(ВыборкаЗапроса);
КонецПроцедуры
&НаСервереБезКонтекста
Процедура ВыдатьВсеВложения(ИерархическаяВыборка)
Сообщение = Новый СообщениеПользователю;
Пока ИерархическаяВыборка.Следующий() Цикл
Сообщение.Текст = «Товар: » + ИерархическаяВыборка.Товар.Наименование +
» Количество: » + ИерархическаяВыборка.Количество() +
» Тип записи: » + ИерархическаяВыборка.ТипЗаписи() +
» Уровень: » + ИерархическаяВыборка.Уровень() +
» Группировка: » + ИерархическаяВыборка.Группировка();
Сообщение.Сообщить();
// Продолжим выборку подчиненных записей
СпособВыборки = ОбходРезультатаЗапроса.ПоГруппировкамСИерархией;
Если ИерархическаяВыборка.ТипЗаписи() = ТипЗаписиЗапроса.ИтогПоИерархии Тогда
ДочерняяВыборка = ИерархическаяВыборка.Выбрать(СпособВыборки, ИерархическаяВыборка.Группировка());
Иначе
ДочерняяВыборка = ИерархическаяВыборка.Выбрать(СпособВыборки);
КонецЕсли;
ВыдатьВсеВложения(ДочерняяВыборка);
КонецЦикла;
КонецПроцедуры
Спасибо огромное! Решил задачу над которой мучался несколько часов за 2 минуты!