Речь идет об алгоритме создания документов по свернутым данным из таблицы значений (ТЗ), в которой кроме данных для заполнения табличной части присутствуют также данные для шапки документа.
Традиционный способ:
1. Сортируем ТЗ по этим колонкам с данными для шапки (назовем их ключами)
2. Перебираем ТЗ и по мере смены ключей создаем новый/записываем предыдущий документ для заполнения
3. После перебора проверяем и записываем документ.
ТЗ.Сортировать("Ключ_1,Ключ_2,...,Ключ_N"); ТЗ.ВыбратьСтроки(); Пока ТЗ.ПолучитьСтроку() = 1 Цикл Если ТЗ.НомерСтроки = 1 Тогда // БЛОК 1 Создать новый документ // БЛОК 2 Заполнить заголовок документа ИначеЕсли (ТЗ.Ключ_1 <> ПредЗначение_Ключ_1) ИЛИ (ТЗ.Ключ_2 <> ПредЗначение_Ключ_2) ИЛИ ... (ТЗ.Ключ_N <> ПредЗначение_Ключ_N) Тогда // сменились ключи // БЛОК 3 Заполнить подвал документа // БЛОК 4 Записать / вывести документ // БЛОК 1 Создать новый документ !дублируем код! // БЛОК 2 Заполнить заголовок документа !дублируем код! КонецЕсли; // БЛОК 5 Заполнение строки спецификации документа ПредЗначение_Ключ_1 = ТЗ.Ключ_1; ПредЗначение_Ключ_2 = ТЗ.Ключ_2; ... ПредЗначение_Ключ_N = ТЗ.Ключ_N; КонецЦикла; // БЛОК 3 Заполнить подвал документа !дублируем код! // БЛОК 4 Записать / вывести документ !дублируем код!
Тот же алгоритм применяется при группировке и выводе данных из ТЗ в печатную форму.
"Копипасте" кода - вешь нехорошая, неэстетичная, и даже бы сказал отбивающая желание заниматься этим делом вообще, а если еще этот код будут читать другие...
Можно, конечно, все это вывести в процедуры, но тогда в них надо передать массу параметров, что не совсем удобно.
Первое, и самое простое, что можно сделать - добавить и перестроить условия в цикле ( комментарии (6) Tarasenkov и (10) Orefkov ) :
ПредЗначение_Ключ_1 = ПолучитьПустоеЗначение(); ПредЗначение_Ключ_2 = ПолучитьПустоеЗначение(); ... ПредЗначение_Ключ_N = ПолучитьПустоеЗначение(); ТЗ.Сортировать("Ключ_1,Ключ_2,...,Ключ_N"); ТЗ.НоваяСтрока(); ТЗ.ВыбратьСтроки(); Пока ТЗ.ПолучитьСтроку() = 1 Цикл Если (ТЗ.Ключ_1 <> ПредЗначение_Ключ_1) ИЛИ (ТЗ.Ключ_2 <> ПредЗначение_Ключ_2) ИЛИ ... (ТЗ.Ключ_N <> ПредЗначение_Ключ_N) ИЛИ (ТЗ.НомерСтроки = 1) Тогда Если ТЗ.НомерСтроки > 1 Тогда // БЛОК 3 Заполнить подвал документа // БЛОК 4 Записать / вывести документ КонецЕсли; Если ТЗ.НомерСтроки = ТЗ.КоличествоСтрок() Тогда Прервать; КонецЕсли; // БЛОК 1 Создать новый документ // БЛОК 2 Заполнить заголовок документа КонецЕсли; // БЛОК 5 Заполнение строки спецификации документа ПредЗначение_Ключ_1 = ТЗ.Ключ_1; ПредЗначение_Ключ_2 = ТЗ.Ключ_2; ... ПредЗначение_Ключ_N = ТЗ.Ключ_N; КонецЦикла; ТЗ.УдалитьСтроку(ТЗ.КоличествоСтрок());
Вариант poppy (комментарий (23)) c избавлением от нагромождения условий и самым простым кодом: достигается вынесением в функцию чтения и сравнения ключей двух соседних записей:
// ПроверитьСтрокуТаблицы(<ТЗ>,<СписокКлючей>) // // функция проверяет достижение текущей строки ТЗ ее последней записи, // или несовпадение значений по списку колонок в текущей и следующей записи, возвращает: // 1 в случае выполнения условий, 0 - иначе. // Параметры: // <ТЗ> - таблица значений // <СписокКлючей> - список значений - идентификаторов сравниваемых колонок Функция ПроверитьСтрокуТаблицы(ТЗ, СписокКлючей) // (отредактировано автором) Перем НомСтр; Перем М1; Перем Колонка; НомСтр = ТЗ.НомерСтроки; Если НомСтр >= ТЗ.КоличествоСтрок() Тогда Возврат 1; КонецЕсли; Для М1 = -СписокКлючей.РазмерСписка() По -1 Цикл Колонка = СписокКлючей.ПолучитьЗначение(-М1); Если ТЗ.ПолучитьЗначение(НомСтр , Колонка) <> ТЗ.ПолучитьЗначение(НомСтр + 1, Колонка) Тогда Возврат 1; КонецЕсли; КонецЦикла; Возврат 0; КонецФункции
Применение функции:
СтрокаКолонок = "Ключ_1,Ключ_2,...,Ключ_N"; СписокКлючей = СоздатьОбъект("СписокЗначений"); СписокКлючей.ИзСтрокиСРазделителями("""" + СтрЗаменить(СтрокаКолонок, ",", """,""") + """"); Флаг = 1; ТЗ.Сортировать(СтрокаКолонок); ТЗ.ВыбратьСтроки(); Пока ТЗ.ПолучитьСтроку() = 1 Цикл Если Флаг = 1 Тогда // БЛОК 1 Создать новый документ // БЛОК 2 Заполнить заголовок документа КонецЕсли; // БЛОК 5 Заполнение строки спецификации документа Флаг = ПроверитьСтрокуТаблицы(ТЗ, СписокКлючей); Если Флаг = 1 Тогда // БЛОК 3 Заполнить подвал документа // БЛОК 4 Записать / вывести документ КонецЕсли; КонецЦикла;
Просто и понятно.
Метод автора, на момент появления статьи описывался здесь в ед. числе.
Он заключается в предварительном прогоне таблицы с проверкой условий и заполнением двух новых колонок в ТЗ с значениями-индикаторами о создании и записи документа.
// ИзмененияКлючейТЗ(<ТЗ>,<СтрокаКолонок>,<ИмяКолонкиНач>,<ИмяКонКолонкиКон>) // // Процедура выполняет сравнение значений в указанных колонках ТЗ в соседних записях и // записывает результат сравнения в две добавляемые колонки // // ТЗ - таблица значений // СтрокаКолонок - Строка колонок (ключей) через запятую, по которым сравниваются значения между соседними записями // ИмяКолонкиНач - Имя добавляемой в ТЗ первой колонки, необязательное // (если не задано, обращение к ней возможно по номеру "1") . // В данную колонку заносится число, результат сравнения текущей и предыдущей записи, // а именно, ближайший номер колонки (по порядку из <СтрокаКолонок>), по которой // значения в указанных записях не совпадают; 0 если несовпадений нет. // ИмяКолонкиКон - Имя добавляемой в ТЗ второй колонки, необязательное // (если не задано, обращение к ней возможно по номеру "1") // В данную колонку заносится число, результат сравнения текущей и следующей записи, // а именно, ближайший номер колонки (по порядку из <СтрокаКолонок>), по которой // значения в указанных записях не совпадают; 0 если несовпадений нет. Процедура ИзмененияКлючейТЗ(ТЗ,Знач СтрокаКолонок,Знач ИмяКолонкиНач = "",Знач ИмяКолонкиКон = "") Перем ИДКолонок[10]; // идентификаторы колонок-ключей Перем Ключи[10]; // значения ключей Перем КолКлючей; // количество ключей Перем М1,М2; Перем КолСтрокТЗ; Перем ПромЗнач; КолСтрокТЗ = ТЗ.КоличествоСтрок(); Если КолСтрокТЗ > 0 Тогда СтрокаКолонок = СтрЗаменить(СтрокаКолонок,",",РазделительСтрок); КолКлючей = СтрКоличествоСтрок(СтрокаКолонок); // получить идентификаторы колонок Для М1 = 1 По КолКлючей Цикл ИДКолонок[М1] = СтрПолучитьСтроку(СтрокаКолонок,М1); КонецЦикла; ТЗ.ВставитьКолонку(ИмяКолонкиНач,1); ТЗ.ВставитьКолонку(ИмяКолонкиКон,2); ТЗ.Заполнить(0,,,"1,2"); ТЗ.УстановитьЗначение(1,1,1); // начало в 1-й записи ТЗ.УстановитьЗначение(КолСтрокТЗ,2,1); // конец в последней // заполнить ключи по первой записи Для М1 = 1 По КолКлючей Цикл Ключи[М1] = ТЗ.ПолучитьЗначение(1,ИДКолонок[М1]); КонецЦикла; // начинаем сравнение со второй по последнюю Для М2 = 2 По КолСтрокТЗ Цикл М1 = 1; Пока М1 <=КолКлючей Цикл ПромЗнач = ТЗ.ПолучитьЗначение(М2,ИДКолонок[М1]); Если ПромЗнач <> Ключи[М1] Тогда ТЗ.УстановитьЗначение(М2 - 1,2,М1); // конец старой группировки в пред записи ТЗ.УстановитьЗначение(М2 ,1,М1); // начало новой группировки в текущей записи // обновим оставшиеся ключи Ключи[М1] = ПромЗнач; М1 = М1 + 1; Пока М1 <= КолКлючей Цикл Ключи[М1] = ТЗ.ПолучитьЗначение(М2,ИДКолонок[М1]); М1 = М1 + 1; КонецЦикла; Прервать; КонецЕсли; М1 = М1 + 1; КонецЦикла; КонецЦикла; КонецЕсли; КонецПроцедуры
Процедура отрабатывает сравнение ключей полностью, с указанием номера несовпадающей колонки-ключа.
Получилось не совсем кратко, вследствие оптимизации, зато использование очень компактное:
СтрокаКолонок = "Ключ_1,Ключ_2,...,Ключ_N"; ТЗ.Сортировать(СтрокаКолонок); ИзмененияКлючейТЗ(ТЗ,СтрокаКолонок,"Нач","Кон"); ТЗ.ВыбратьСтроки(); Пока ТЗ.ПолучитьСтроку() = 1 Цикл Если ТЗ.Нач > 0 Тогда // БЛОК 1 Создать новый документ // БЛОК 2 Заполнить заголовок документа КонецЕсли; // БЛОК 5 Заполнение строки спецификации документа Если ТЗ.Кон > 0 Тогда // БЛОК 3 Заполнить подвал документа // БЛОК 4 Записать документ КонецЕсли; КонецЦикла;
Оригинальный способ указан int3 (комментарий (3)). Он заключается в создании вспомогательной таблицы индексов, которая представляет из себя исходную ТЗ, свернутую по колонкам-ключам с суммированием количества записей. (изменено оформление).
// СоздатьИндексТЗ(<ТЗ>,<СтрокаКолонок>,<ИмяИндексКолонки>) // функция возвращает свернутую таблицу значений по указанным колонкам // с суммированием количества записей в добавляемой колонке // параметры: // ТЗ - таблица значений для свертки // СтрокаКолонок - строка колонок, разделенных запятыми, для свертки. // ИмяИндексКолонки - добавляемая колонка №1, в которой суммируется количество записей. Функция СоздатьИндексТЗ(ТЗ,СтрокаКолонок,Знач ИмяИндексКолонки = "") Перем ТЗИндекс; ТЗИндекс = СоздатьОбъект("ТаблицаЗначений"); Если ТЗ.КоличествоСтрок() > 0 Тогда ТЗ.Выгрузить(ТЗиндекс,,,СтрокаКолонок); ТЗИндекс.ВставитьКолонку(ИмяИндексКолонки,1); ТЗИндекс.Заполнить(1,,,"1"); ТЗИндекс.Свернуть(СтрокаКолонок,"1"); ТЗИндекс.Сортировать(СтрокаКолонок,1); КонецЕсли; Возврат ТЗИндекс; КонецФункции
Применение таблицы значений - "индекса":
СтрокаКолонок = "Ключ_1,Ключ_2,...,Ключ_N"; ТЗ.Сортировать(СтрокаКолонок); ТЗИндекс = СоздатьИндексТЗ(ТЗ,СтрокаКолонок,"Кол"); ТЗИндекс.ВыбратьСтроки(); ТЗ.ВыбратьСтроки(); Пока ТЗИндекс.ПолучитьСтроку() = 1 Цикл // БЛОК 1 Создать новый документ // БЛОК 2 Заполнить заголовок документа Для М1 = 1 По ТЗИндекс.Кол Цикл ТЗ.ПолучитьСтроку(); // БЛОК 5 Заполнение строки спецификации документа КонецЦикла; // БЛОК 3 Заполнить подвал документа // БЛОК 4 Записать документ КонецЦикла;
Автор выражает благодарность Poppy, Int3, Tarasenkov, Orefkov за активное участие в статье.
P.S. О быстродействии.
Самый быстрый, - это метод Int3. Он выигрывает у традиционного при средней длине "спецификации" от 50-ти строк ("2% шапок"), или по 1 строке в 98% случаях.
Метод автора дает к традиционному от +50 до +130%.
Но даже сотня процентов дает очень незначительный прирост по времени, - "голые" циклы и условия (сравнивалось на них) на 100т записях прогоняются за секунды, так что красота, удобочитаемость и универсальность кода того ст'оит
// ОбщийКлюч(<ТЗ>,<СписокКлючей>) // // функция формирует и возвращает общий строковый ключ по значениям из текущей строки ТЗ, // по переданному списку колонок // Параметры: // <ТЗ> - таблица значений // <СписокКлючей> - список значений - идентификаторов колонок Функция ОбщийКлюч(ТЗ, СписокКлючей) Перем НомСтр; Перем М1; Перем ТекКлюч; НомСтр = ТЗ.НомерСтроки; ТекКлюч = ЗначениеВСтрокуВнутр( ТЗ.ПолучитьЗначение(НомСтр,
СписокКлючей.ПолучитьЗначение(1))); Для М1 = 2 По СписокКлючей.РазмерСписка() Цикл ТекКлюч = ТекКлюч +
ЗначениеВСтрокуВнутр( ТЗ.ПолучитьЗначение(НомСтр ,
СписокКлючей.ПолучитьЗначение(М1))); КонецЦикла; Возврат 0; КонецФункции
Применение функции:
СтрокаКолонок = "Ключ_1,Ключ_2,...,Ключ_N"; СписокКлючей = СоздатьОбъект("СписокЗначений"); СписокНовДок = СоздатьОбъект("СписокЗначений"); СписокКлючей.ИзСтрокиСРазделителями("""" + СтрЗаменить(СтрокаКолонок, ",", """,""") + """"); ТЗ.ВыбратьСтроки(); Пока ТЗ.ПолучитьСтроку() = 1 Цикл ТекКлюч = ОбщийКлюч(ТЗ, СписокКлючей); НовДок = СписокНовДок.Получить(ТекКлюч); Если ТипЗначенияСтр(НовДок) = "Документ" Тогда // БЛОК 1 Создать новый документ в НовДок с использованием СоздатьОбъект() // БЛОК 2 Заполнить заголовок документа НовДок СписокНовДок.Установить(ТекКлюч, НовДок); КонецЕсли; // БЛОК 5 Заполнение строки спецификации документа НовДок КонецЦикла;
Для М1 = 1 По СписокНовДок.РазмерСписка() Цикл
НовДок = СписокНовДок.ПолучитьЗначение(М1);
// БЛОК 3 Заполнить подвал документа НовДок
// БЛОК 4 Записать / вывести документ НовДок
КонецЦикла;
К минусам этого метода можно отнести возможную громоздкость "общего ключа",
нахождение в памяти одновременно всех создаваемых документов, к плюсам -
таблицу значений предварительно сортировать не нужно.