Страницы: 1
RSS
Разделение таблицы в разные книги - можно ли оптимизировать?
 
Alex T., антиспам скрыл Ваше сообщение. Продублируйте его.

Цитата
Alex T. написал:
в раздельные воркбуки
С разными воркшитами?
 
охохо.. попробую написать заново :-) Делал в рамках одной рабочей задачи такую штуку:

Дано: длинная таблица (100-200 тысяч строк) с некоторым количеством колонок и хедером. Чтобы было проще представить - пусть это будет отчет о продажах, где в колонке А расположены уникальные названия магазинов (штук 120), а во всех остальных колонках параметры относящиеся к продаже - номер заказа, дата и так далее.

Задача: разделить эту большую и длинную таблицу на отдельные Excel файлы для каждого магазина. Включая перенос хедера и небольшие косметические операции с каждым файлом - ну типа вставить 4 пустые строки и значение в ячейку дополнительно.

Как сделано:
Код
Option Explicit
Sub split_to_files()

Dim LR As Long, LREMAIL As Long, Itm As Long, MyCount As Long, vCol As Long, sumlr As Long
Dim ws As Worksheet, we As Worksheet, MyArr As Variant, vTitles As String, SvPath As String
Dim docmonth As String, docyear As String, path_f As String, savepath As String, fname As String
Dim StartTime As Double
Dim SecondsElapsed As Double

StartTime = Timer

path_f = ThisWorkbook.Path
savepath = path_f & "\To Send\"

'Sheet with data in it
   Set ws = Sheets("main")

'Range where titles are across top of data, as string, data MUST
'have titles in this row, edit to suit your titles locale
    vTitles = "A1:G2"
   
'Choose column to evaluate from, column A = 1, B = 2, etc.
   'vCol = Application.InputBox("What column to split data by? " & vbLf _
   '     & vbLf & "(A=1, B=2, C=3, etc)", "Which column?", 1, Type:=1)
   'If vCol = 0 Then Exit Sub

    vCol = 1
    
    
'Spot bottom row of data
   LR = ws.Cells(ws.Rows.Count, vCol).End(xlUp).Row
   

'Speed up macro execution

    Application.ScreenUpdating = False
    Application.EnableEvents = False
    Application.AskToUpdateLinks = False
    Application.DisplayAlerts = False
    Application.Calculation = xlAutomatic
    ThisWorkbook.Date1904 = False
    ActiveWindow.View = xlNormalView

'Get a temporary list of unique values from key column
    'ws.Columns(vCol).AdvancedFilter Action:=xlFilterCopy, CopyToRange:=ws.Range("EE1"), Unique:=True
    ws.Range("A1:A" & LR).AdvancedFilter Action:=xlFilterCopy, CopyToRange:=ws.Range("EE1"), Unique:=True

'Sort the temporary list
    ws.Columns("EE:EE").Sort Key1:=ws.Range("EE2"), Order1:=xlAscending, Header:=xlYes, _
       OrderCustom:=1, MatchCase:=False, Orientation:=xlTopToBottom, DataOption1:=xlSortNormal

'Put list into an array for looping (values cannot be the result of formulas, must be constants)
    MyArr = Application.WorksheetFunction.Transpose(ws.Range("EE2:EE" & Rows.Count).SpecialCells(xlCellTypeConstants))

'clear temporary worksheet list
    ws.Range("EE:EE").clear

'Turn on the autofilter, one column only is all that is needed
    ws.Range(vTitles).AutoFilter

'Loop through list one value at a time
    For Itm = 1 To UBound(MyArr)
        ws.Range(vTitles).AutoFilter Field:=vCol, Criteria1:=MyArr(Itm)
        ws.Range("A1:H" & LR).Copy
        Workbooks.Add
        Range("A1").PasteSpecial xlPasteAll
        Rows(1).Insert Shift:=xlShiftDown
        Rows(1).Insert Shift:=xlShiftDown
        Rows(1).Insert Shift:=xlShiftDown
        Rows(1).Insert Shift:=xlShiftDown
        Range("C1").Value = "Additional field:"
        Range("C1").Interior.ColorIndex = 6
        
        Cells.Columns.AutoFit
        
        MyCount = MyCount + Range("A" & Rows.Count).End(xlUp).Row - 5
        
        
        'MyArr(Itm) = Replace(MyArr(Itm), "/", "")
        'MyArr(Itm) = Replace(MyArr(Itm), "\", "")
        'MyArr(Itm) = Replace(MyArr(Itm), ":", "")
        'MyArr(Itm) = Replace(MyArr(Itm), "=", "")
        'MyArr(Itm) = Replace(MyArr(Itm), "*", "")
        'MyArr(Itm) = Replace(MyArr(Itm), ".", "")
        'MyArr(Itm) = Replace(MyArr(Itm), "?", "")
        'MyArr(Itm) = Replace(MyArr(Itm), "{", "")
        'MyArr(Itm) = Replace(MyArr(Itm), "}", "")
        'MyArr(Itm) = Strings.Trim(MyArr(Itm))
     
        If Dir(savepath, vbDirectory) = "" Then
        MkDir savepath
        Else
        End If
        
        ActiveWorkbook.SaveAs savepath & MyArr(Itm) & ".xlsb", 50  '50 is binary format
        ActiveWorkbook.Close False
        
        ws.Range(vTitles).AutoFilter Field:=vCol
    Next Itm

'Cleanup
    ws.AutoFilterMode = False
    SecondsElapsed = Round(Timer - StartTime, 2)
    MsgBox SecondsElapsed & " Secs for processing"
    Application.ScreenUpdating = True
    Application.EnableEvents = True
    Application.AskToUpdateLinks = True
    Application.DisplayAlerts = True
    Application.Calculation = xlAutomatic
    ThisWorkbook.Date1904 = False
    Application.StatusBar = False
End Sub


Работает вполне ок, все разделяет, все копирует, нема проблема.

Вопрос: можно ли этот процесс ускорить? Сейчас обработка 1-го файла занимает 1-2 секунд, что в принципе не составляет никаких проблем, интерес чисто академический. Вдруг кто-то сталкивался.

Пример в приложенном файле - там три макроса, один из них чистит документ, второй наполняет его рендомными данными (в колонке А будет 120 уникальных значений), третий собственно делит таблицу на отдельные файлы.

Изменено: Alex T. - 25.01.2021 23:41:55
 
После упорядочения (например, как показано ниже) вашего кода, время выполнения (для меня) составляет 41 с/120 файлов (0,34 с/1 файл)

Код
Option Explicit

Sub splitti_fitti()
    
    Dim StartTime As Single
    StartTime = Timer
    
    Dim Itm As Long, vCol As Long ', MyCount As Long
    Dim savepath As String ', path_f As String
    Dim ws As Worksheet
    Dim MyArr
    
    vCol = 1
    With ThisWorkbook
        'path_f = .Path
        savepath = .Path & "\To Send"
        Set ws = .Sheets("main")
    End With
    'savepath = path_f & "\To Send"
    
    If Dir(savepath, vbDirectory) = "" Then MkDir savepath
    
    With Application
        .ScreenUpdating = False: .EnableEvents = False
        .Calculation = xlManual: .DisplayAlerts = False
    End With
    ActiveWindow.View = xlNormalView
    
    With ws
        .Range("A1:A" & .Cells(.Rows.Count, vCol).End(xlUp).Row).AdvancedFilter _
                    Action:=xlFilterCopy, CopyToRange:=.Range("EE1"), Unique:=True
        .Range("EE1").CurrentRegion.Sort Key1:=.Range("EE2"), Order1:=xlAscending, Header:=xlYes
        MyArr = Application.Transpose(.Range("EE2:EE" & .Cells(.Rows.Count, "EE").End(xlUp).Row).Value)
        .Range("EE1").CurrentRegion.Clear
        '.Range("A1").CurrentRegion.AutoFilter
    End With
    
    For Itm = 1 To UBound(MyArr)
        Workbooks.Add
        
        With ws.Range("A1").CurrentRegion
            .AutoFilter Field:=vCol, Criteria1:=MyArr(Itm)
            .SpecialCells(xlVisible).Copy
        End With
        
        With ActiveWorkbook
            With ActiveSheet
                With .Range("A1")
                    .PasteSpecial xlPasteAll
                    Application.CutCopyMode = False
                    .Select
                End With
                .Rows("1:4").Insert Shift:=xlShiftDown
                With .Range("C1")
                    .Value = "Additional field:"
                    .Interior.ColorIndex = 6
                End With
                .Columns("A:G").AutoFit
                'MyCount = MyCount + .Range("A" & .Rows.Count).End(xlUp).Row - 5 '?
            End With
            .SaveAs savepath & ("\" & MyArr(Itm) & ".xlsb"), 50
            .Close False
        End With
    Next
    
    ws.AutoFilterMode = False
    Set ws = Nothing
    
    With Application
        .DisplayAlerts = True: .Calculation = xlAutomatic
        .EnableEvents = True: .ScreenUpdating = True
    End With
    
    Debug.Print Round(Timer - StartTime, 3) & " Secs for processing"
    
End Sub
 
у меня вопрос - на листе много формул? Если да, то вот эта строка в начале макроса (в блоке 'Speed up macro execution)
Application.Calculation = xlAutomatic
должна выглядеть вот так
Application.Calculation =xlCalculationManual

Объясню. Допустим у вас на листе куча формул. Вы в начале макроса ставите режим Автоматический пересчёт формул (xlAutomatic) - в этот момент включается пересчёт всех формул в книге (1 раз!). А так же когда вы ставите фильтр и когда вы снимаете фильтр (а это 120 раз поставили и 120 раз сняли, то есть 240 РАЗ!) - все формулы на всех листах пересчитываются - будет задержка времени. Всегда при работе с большим объёмом данных в начале макроса отключают Автоматический пересчёт на ручной, а в конце макроса ставят Автоматический - на этой строке сразу происходит пересчёт всех формул в книге.

P.S. Если формул на листе нет, то можно всё это организовать на массивах - все данные с листа берёте в двумерный массив, а дальше бегаете циклами по нему (как у вас реализовано вот это For Itm = 1 To UBound(MyArr), и сравниваете если значение в столбце 1 общего массива равно MyArr(i), тогда перекладываете все 7 столбцов в новый массив, который будете выгружать на лист в новый файл.

P.P.S. Вот в этой строке вы всегда копируете 200.000 строк ws.Range("A1:H" & LR).Copy, но т.к. массив отфильтрован, то строк тут будет всегда меньше, возможно надо в цикле For Itm = 1 To UBound(MyArr) находить последнюю строку (как у вас LR = ws.Cells(ws.Rows.Count, vCol).End(xlUp).Row) и использовать новую переменную с последней заполненной строкой для копирования данных

PPP.S Вот эти строки
       Rows(1).Insert Shift:=xlShiftDown
       Rows(1).Insert Shift:=xlShiftDown
       Rows(1).Insert Shift:=xlShiftDown
       Rows(1).Insert Shift:=xlShiftDown

думаю можно заменить одной строкой Rows("1:4").Insert Shift:=xlShiftDown - если у вас включен Автоматический пересчёт формул и формул много - после каждого добавления строки будет идти пересчёт всех формул, т.е. к вышеуказанным 1+240 разам добавятся ещё +4 раза пересчёт формул

PPPP.S.
- эту строку Cells.Columns.AutoFit - лучше написать Columns("A:G").AutoFit, чтобы не было обращения ко всем столбцам на листе
- снятие автофильтра ws.Range(vTitles).AutoFilter Field:=vCol можно (и так более правильнее) написать так ws.ShowAllData

PPPPP.S.
- вот эту строку в конце макроса (в блоке 'Cleanup) Application.ScreenUpdating = True - лучше писать самой последней (по крайней мере обязательно ПОСЛЕ включения пересчёта Автоматического пересчёта формул)
Изменено: New - 26.01.2021 02:58:00
 
Ух, какое лютое шаманство. действительно 41 секунда приблизительно. Учиться, учиться и учиться, как завещали..
Буду изучать.
Огромное спасибо за науку.
 
я там ещё добавил внизу PPPP.S.в моём сообщение выше и добавьте строку ActiveSheet.DisplayPageBreaks = False в блок    'Speed up macro execution. Включать её в True в конце макроса не стоит
Изменено: New - 26.01.2021 03:01:21
 
Всем большое спасибо за советы, это было действительно интересно, подчерпнул для себя новое - так что профит есть 100% :)
Изначально не планировал ничего менять, просто интерес был, но посмотрев примеры, понял насколько у меня убогий код и переписал его согласно вашим рекоммендациям на продакшен документе. Пользователю наверное без разницы, 30 секунд или минута, они вообще это руками делали днями :) Но мне лично - приятно.

PS. артефакты в коде которые под вопросом остались, типа MyCount  - это я просто основной функционал выдрал, где-то мог забыть что-то. В оригинале документ посимпатичнее, с кнопками всякими, обеспечивает импорт данных выгружаемых из сервиса, его приведение в надлежащий вид, разделение на файлы, отправку по почте каждого файла и так далее - короче автоматизирует в 4 нажатия все то, что пользователь делал вручную.
Страницы: 1
Наверх