Разделение многоуровневного списка по столбцам
В моей практике весьма часто приходится сталкиваться с ситуацией, когда мне в руки попадает большой многоуровневый список с вложенной нумерацией:

Это могут быть сметы с их бесконечными пунктами-подпунктами работ и материалов, списки задач-подзадач по проектам, бухгалтерские выгрузки статей-подстатей из 1С и т.д. - вариантов миллион. И не знаю, как вам, а мне подобные списки сразу хочется разложить на столбцы по уровням нумерации. Готовых встроенных инструментов в Excel для такого нет, но когда это нас с вами останавливало? :)
Давайте рассмотрим несколько способов реализовать подобную трансформацию в Microsoft Excel.
Способ 1. Формулы
Для начала давайте выясним, на каком уровне (1,2,3...) находится каждый элемент. С ходу видно, что это можно вычислить по количеству точек в нумерации. В подобных случаях, когда вам нужно определить сколько именно раз какой-то символ входит в исходный текст, есть простой рецепт: заменить все интересующие символы (точки в нашем случае) на пустоту (т.е. удалить) и сравнить длину получившегося урезанного текста с длиной исходного. Разница в длине и будет количеством вхождений.
Реализовать эту логику можно следующей формулой:

Здесь функция ПОДСТАВИТЬ (SUBSTITUTE) заменяет точки на пустоту, а функция ДЛСТР (LEN) вычисляет длину текста.
Теперь введём над следующими столбцами цифры уровней 1, 2, 3 и т.д. и добавляем к нашей формуле проверку с извлечением элементов на каждый уровень:

Только не забудьте правильно закрепить ссылки на ячейки в этой формуле символом доллара, чтобы можно было скопировать её на весь диапазон.
Ну и, само собой, можно уложить всё это в одну формулу, используя классический принцип построения мегаформул в Excel, когда сначала вся логика расчёта реализуется в отдельных ячейках несколькими простыми формулами, а потом они по очереди копируются и вставляются друг в друга. Т.е. в нашем случае мы прямо в строке формул выделяем и копируем в буфер всё после знака = из ячейки С2 и затем аккуратно вставляем это вместо адреса этой же ячейки $C2 в формулу в ячейку D2 (не забыв добавить доллары где это нужно). Таким образом всё компактно собирается в одну мегаформулу, разобраться в которой, правда, может быть уже сложновато.
Способ 2. Power Query
Иногда подобную задачу бывает проще решить с помощью Power Query - мощной надстройки для импорта и преобразования данных, которая с 2016 версии уже стала неотъемлемой частью Excel.
Для начала давайте конвертируем нашу таблицу в динамическую "умную", используя сочетание клавиш Ctrl+T или команду Главная - Форматировать как таблицу (Home - Format as Table) и загрузим в Power Query - кнопкой Из таблицы/диапазона на вкладке Данные (Data - From table/range).
Важно сразу задать для столбца с номерами текстовый тип данных, т.к. далее мы будем работать с нимм именно как с строками:

Затем добавим вычисляемый столбец с уровнем для каждого элемента, используя команду Настраиваемый столбец на вкладке Добавление столбца (Add column - Custom column).
Дальше, в принципе, можно было бы пойти по той же логике, что и в предыдущем способе - удалить все точки из нумерации и посчитать разницу в длине получившегося и исходного текста. Причем можно будет даже не заменять точку на пустоту, как мы это делали ранее функцией ПОДСТАВИТЬ, а удалить напрямую - в языке М, встроенному в Power Query для этого есть прямая функция Text.Remove. Ну, а для вычисления длины текстовой строки подойдет функция Text.Length.
В итоге наша формула могла бы выглядеть так:

Если в исходной нумерации где-то случайно вместо точки использовалась, например, запятая или подчеркивание, то можно задать сразу все эти символы как разделители - для этого во втором аргументе функции Text.Remove указывается список в фигурных скобках:
=Text.Length([Номер])-Text.Length(Text.Remove([Номер], {".", ",", "_"}))+1
Есть и другой способ, достойный упоминания - разделить нумерацию по точкам и посчитать затем количество получившихся фрагментов (плюс 1). Это можно сделать с помощью формулы:
=List.Count(Text.Split([Номер], "."))+1
Если символов-разделителей несколько, то можно использовать функцию Text.SplitAny, вторым аргументом которой и задать все разделители текстовой строкой (уже не используя список):
=List.Count(Text.SplitAny([Номер], ".,_"))+1
После этого останется выделить получившийся столбец с вычисленными уровнями и разложить элементы по значениям из этого столбца на разные колонки. Это легко сделать с помощью команды Преобразование - Столбец сведения (Transform - Pivot column):

В открывшемся окне выбираем столбец данных в качестве результата, а в расширенных параметрах не забываем отключить агрегацию (Don't aggregate) - и задача решена:

Большой плюс такого подхода в том, что при изменении исходных данных (добавлении новых строк, корректировки уровней и т.д.) достаточно будет лишь обновить наш запрос - кнопкой Обновить всё на вкладке Данные (Data - Refresh all).
Минус же в том, что если в столбце с нумерацией вдруг почему-то окажутся дубликаты, то на этих строчках наша процедура сведения даст ошибку. Подстраховаться от такого можно, добавив в наш запрос перед шагом Сведенный столбец (Pivoted column) пару дополнительных шагов, а именно:
- Добавить столбец индекса на вкладке Добавление столбца - Столбец индекса (Add column - Index column).
- Перенести его за шапку в начало таблицы
Способ 3. Макрос на VBA
Ну и, наконец, можно решить нашу задачу с помощью коротенького макроса на Visual Basic. Чтобы добавить его в текущую книгу, нажмите сочетание клавиш Alt+F11 или кнопку Visual Basic на вкладке Разработчик (Developer). Затем вставьте новый пустой модуль через меню Insert - Module и введите туда следующий код:
Sub Multilevel()
dim n as Integer, cell as Range
For Each cell In Selection
n = UBound(Split(cell, "."))
If n > 0 Then cell.Offset(0, 1).Resize(1, n).Insert xlShiftToRight
Next cell
End Sub
Логика тут простая:
- Проходим в цикле For Each ... Next по выделенным ячейкам (это будут ячейки с нумерацией) и ссылку на очередную ячейку сохраняем в переменную cell.
- Разделяем полученный номер по точкам на фрагменты с помощью VBA-функции Split и помещаем их в массив.
- Подсчитываем размер получившегося массива функцией Ubound - это и будет уровень очередного элемента, который кладём в переменную n.
- Сдвигаемся на ячейку вправо относительно текущей с помощью Offiset(0,1) и выделяем с помощью метода Resize n ячеек.
- Вставляем n пустых ячеек со сдвигом вправо, отодвигая, таким образом, очередной элемент на его уровень.
Если теперь выделить ячейки с нумерацией и запустить наш макрос, используя сочетание клавиш Alt+F8 или кнопку Макросы на вкладке Разработчик (Developer - Macros), то наш список моментально разложится по столбцам-уровням.
Минус у такого подхода в том, что после запуска макросов в Excel традиционно, к сожалению, перестаёт работать отмена последнего действия (Undo). Ну, и сохранять наш файл теперь придётся в формате с поддержкой макросов, само собой.
Зато можно добавить этот код в Личную книгу макросов (вашу персональную библиотеку макросов, о которой я уже писал) - и пользоваться им потом многократно, когда это вам потребуется.
Ссылки по теме
- Использование функции ПОДСТАВИТЬ (SUBSTITUTE) в Excel
- Делим слипшийся текст на части
- Как использовать Личную книгу макросов в Excel
Подсчитать сколько разделителей через List.Count, добавить перед данными ";" и разделить полученный столбец на части
#"Добавлен пользовательский объект" = Table.AddColumn(#"Измененный тип", "combine", each Text.Repeat(";",List.Count(Text.SplitAny([Номер],".,_"))-1)&[Данные]), #"Разделить столбец по разделителю" = Table.SplitColumn(#"Добавлен пользовательский объект", "combine", Splitter.SplitTextByDelimiter(";", QuoteStyle.Csv), {"combine.1", "combine.2", "combine.3"}),Я, к сожалению, небольшой спец в Excel, но пытаюсь понять, где можно применить в работах предложенное разнесение по столбцам?
Прошу не винить меня строго ни автора статьи, ни комментаторов.
Весь смысл этой статьи и видео как раз в том, чтобы имея первый столбец с многоуровневой нумерацией разложить соседний справа столбец на несколько колонок по соответствующим уровням. Без первого столбца это выполнить в принципе невозможно.
Посмотрите еще раз внимательно видео от начала и до конца - должно помочь
подскажите, а можно ли в настройку Plex добавить макрос, который будет вытаскивать информацию по автоотступам из выделенного диапазона? писал с помощью ИИ
у меня есть рыба, посмотрите, пожалуйста. таким инструментов пользуюсь часто из-за выгрузок из системы 1С и прочих с портала рабочего.
Sub SmartCopyByIndent() Dim cell As Range Dim sourceColumn As Long Dim ws As Worksheet Dim indentLevels As Collection Dim uniqueIndents() As Long Dim indentMap As Object Dim indentLevel As Long Dim targetColumn As Long Dim i As Long, j As Long Dim maxColumns As Long Application.ScreenUpdating = False Application.Calculation = xlCalculationManual Application.EnableEvents = False On Error GoTo ErrorHandler Set ws = ActiveSheet ' Проверка выделения If Selection.Count = 0 Then MsgBox "Выделите ячейки для обработки", vbExclamation Exit Sub End If ' Проверка что все ячейки в одном столбце sourceColumn = Selection.Column For Each cell In Selection If cell.Column <> sourceColumn Then MsgBox "Все выделенные ячейки должны находиться в одном столбце!", vbCritical Exit Sub End If Next cell ' Собираем уникальные уровни отступов Set indentLevels = New Collection On Error Resume Next ' Для обработки дубликатов For Each cell In Selection If Not IsEmpty(cell) Then indentLevel = cell.IndentLevel indentLevels.Add indentLevel, CStr(indentLevel) End If Next cell On Error GoTo ErrorHandler ' Если нет отступов - выход If indentLevels.Count = 0 Then MsgBox "Не найдено ячеек с отступами для обработки", vbInformation Exit Sub End If ' Сортируем уровни отступов ReDim uniqueIndents(1 To indentLevels.Count) For i = 1 To indentLevels.Count uniqueIndents(i) = indentLevels(i) Next i ' Простая сортировка пузырьком For i = 1 To UBound(uniqueIndents) - 1 For j = i + 1 To UBound(uniqueIndents) If uniqueIndents(i) > uniqueIndents(j) Then Dim temp As Long temp = uniqueIndents(i) uniqueIndents(i) = uniqueIndents(j) uniqueIndents(j) = temp End If Next j Next i ' Создаем сопоставление уровень отступа -> индекс столбца Set indentMap = CreateObject("Scripting.Dictionary";) For i = 1 To UBound(uniqueIndents) indentMap(uniqueIndents(i)) = i - 1 ' Нумерация с 0 Next i ' Количество столбцов для вставки = количество уникальных отступов maxColumns = indentLevels.Count ' Добавляем столбцы СПРАВА от исходного ws.Columns(sourceColumn + 1).Resize(, maxColumns).Insert _ Shift:=xlToRight, _ CopyOrigin:=xlFormatFromLeftOrAbove ' Заголовки для новых столбцов (без заливки и отступов) For i = 0 To maxColumns - 1 targetColumn = sourceColumn + 1 + i ws.Cells(1, targetColumn).Value = "Уровень " & uniqueIndents(i + 1) With ws.Cells(1, targetColumn) .Font.Bold = True .Borders.Weight = xlThin .HorizontalAlignment = xlCenter .IndentLevel = 0 ' Убираем отступ из заголовка End With Next i ' Копируем данные с умным сопоставлением (без отступов в новых ячейках) For Each cell In Selection If Not IsEmpty(cell) Then indentLevel = cell.IndentLevel If indentMap.Exists(indentLevel) Then targetColumn = sourceColumn + 1 + indentMap(indentLevel) ' Копируем значение ws.Cells(cell.Row, targetColumn).Value = cell.Value ' Убираем отступ из новой ячейки ws.Cells(cell.Row, targetColumn).IndentLevel = 0 ' Дополнительно: убираем отступы из пробелов в начале текста If Not IsNull(ws.Cells(cell.Row, targetColumn).Value) Then ws.Cells(cell.Row, targetColumn).Value = Trim(ws.Cells(cell.Row, targetColumn).Value) End If End If End If Next cell Cleanup: Application.ScreenUpdating = True Application.Calculation = xlCalculationAutomatic Application.EnableEvents = True ' Автоподбор ширины ws.Columns(sourceColumn + 1).Resize(, maxColumns).AutoFit ' Формируем детальный отчет Dim report As String report = "Уникальные уровни отступов:" & vbCrLf For i = 1 To UBound(uniqueIndents) report = report & " • Уровень " & uniqueIndents(i) & " → Столбец " & _ Split(ws.Cells(1, sourceColumn + 1 + i - 1).Address, "$";)(1) & vbCrLf Next i MsgBox "Данные успешно скопированы!" & vbCrLf & _ "Исходный столбец сохранен без изменений" & vbCrLf & vbCrLf & _ report & vbCrLf & _ "Добавлено столбцов: " & maxColumns & vbCrLf & _ "Во всех новых ячейках удалены отступы", vbInformation Exit Sub ErrorHandler: MsgBox "Ошибка: " & Err.Description & vbCrLf & _ "Номер ошибки: " & Err.Number, vbCritical Resume Cleanup End SubВ PLEX давно есть функция IndentLevel как раз для этого случая;)
Sub SmartCopyByIndent() Dim cell As Range Dim sourceColumn As Long Dim ws As Worksheet Dim indentLevels As Collection Dim uniqueIndents() As Long Dim indentMap As Object Dim indentLevel As Long Dim targetColumn As Long Dim i As Long, j As Long Dim maxColumns As Long Application.ScreenUpdating = False Application.Calculation = xlCalculationManual Application.EnableEvents = False On Error GoTo ErrorHandler Set ws = ActiveSheet ' Проверка выделения If Selection.Count = 0 Then MsgBox "Выделите ячейки для обработки", vbExclamation Exit Sub End If ' Проверка что все ячейки в одном столбце sourceColumn = Selection.Column For Each cell In Selection If cell.Column <> sourceColumn Then MsgBox "Все выделенные ячейки должны находиться в одном столбце!", vbCritical Exit Sub End If Next cell ' Собираем уникальные уровни отступов Set indentLevels = New Collection On Error Resume Next ' Для обработки дубликатов For Each cell In Selection If Not IsEmpty(cell) Then indentLevel = cell.IndentLevel indentLevels.Add indentLevel, CStr(indentLevel) End If Next cell On Error GoTo ErrorHandler ' Если нет отступов - выход If indentLevels.Count = 0 Then MsgBox "Не найдено ячеек с отступами для обработки", vbInformation Exit Sub End If ' Сортируем уровни отступов ReDim uniqueIndents(1 To indentLevels.Count) For i = 1 To indentLevels.Count uniqueIndents(i) = indentLevels(i) Next i ' Простая сортировка пузырьком For i = 1 To UBound(uniqueIndents) - 1 For j = i + 1 To UBound(uniqueIndents) If uniqueIndents(i) > uniqueIndents(j) Then Dim temp As Long temp = uniqueIndents(i) uniqueIndents(i) = uniqueIndents(j) uniqueIndents(j) = temp End If Next j Next i ' Создаем сопоставление уровень отступа -> индекс столбца Set indentMap = CreateObject("Scripting.Dictionary";) For i = 1 To UBound(uniqueIndents) indentMap(uniqueIndents(i)) = i - 1 ' Нумерация с 0 Next i ' Количество столбцов для вставки = количество уникальных отступов maxColumns = indentLevels.Count ' Добавляем столбцы СПРАВА от исходного ws.Columns(sourceColumn + 1).Resize(, maxColumns).Insert _ Shift:=xlToRight, _ CopyOrigin:=xlFormatFromLeftOrAbove ' Заголовки для новых столбцов (без заливки и отступов) For i = 0 To maxColumns - 1 targetColumn = sourceColumn + 1 + i ws.Cells(1, targetColumn).Value = "Уровень " & uniqueIndents(i + 1) With ws.Cells(1, targetColumn) .Font.Bold = True .Borders.Weight = xlThin .HorizontalAlignment = xlCenter .IndentLevel = 0 ' Убираем отступ из заголовка End With Next i ' Копируем данные с умным сопоставлением (без отступов в новых ячейках) For Each cell In Selection If Not IsEmpty(cell) Then indentLevel = cell.IndentLevel If indentMap.Exists(indentLevel) Then targetColumn = sourceColumn + 1 + indentMap(indentLevel) ' Копируем значение ws.Cells(cell.Row, targetColumn).Value = cell.Value ' Убираем отступ из новой ячейки ws.Cells(cell.Row, targetColumn).IndentLevel = 0 ' Дополнительно: убираем отступы из пробелов в начале текста If Not IsNull(ws.Cells(cell.Row, targetColumn).Value) Then ws.Cells(cell.Row, targetColumn).Value = Trim(ws.Cells(cell.Row, targetColumn).Value) End If End If End If Next cell Cleanup: Application.ScreenUpdating = True Application.Calculation = xlCalculationAutomatic Application.EnableEvents = True ' Автоподбор ширины ws.Columns(sourceColumn + 1).Resize(, maxColumns).AutoFit ' Формируем детальный отчет Dim report As String report = "Уникальные уровни отступов:" & vbCrLf For i = 1 To UBound(uniqueIndents) report = report & " • Уровень " & uniqueIndents(i) & " → Столбец " & _ Split(ws.Cells(1, sourceColumn + 1 + i - 1).Address, "$";)(1) & vbCrLf Next i MsgBox "Данные успешно скопированы!" & vbCrLf & _ "Исходный столбец сохранен без изменений" & vbCrLf & vbCrLf & _ report & vbCrLf & _ "Добавлено столбцов: " & maxColumns & vbCrLf & _ "Во всех новых ячейках удалены отступы", vbInformation Exit Sub ErrorHandler: MsgBox "Ошибка: " & Err.Description & vbCrLf & _ "Номер ошибки: " & Err.Number, vbCritical Resume Cleanup End SubПопробуйте сходить к вашему 1С-нику - пусть разделит эту иерархию на отдельные столбцы в плоской таблице.
В любом случае, спасибо за ответ 🙏🏻