Страницы: 1 2 3 След.
RSS
Оптимизация выполнения цикла в VBA
 
Добрый день!
Прошу помочь в оптимизации кода. У меня только базовые знания VBA, поэтому буду признателен за помощь.
Суть проблемы (gопробую объяснить словами):
Имеется таблица с данными на листе AllData.
Количество строк данных - порядка 100 тыс. и обновляется, количество столбцов - 35. Начало таблицы со второй строки листа.
В ячейках А1 и B1 имеются текстовые значения. Необходимо каждую строку таблицы (по столбцам А и В) сравнить на соответствие условиям: An=A1 или Bn=A1  или An=B1  или Bn=B1, где n - номер строки,  и после этого скопировать строки, которые удовлетворяют условиям, на другой лист Sort.
На данный момент в макросе используется цикл вида:
Код
For i=2 To Last (номер последней строки)
    if An=A1 or Bn=A1 or An=B1 or Bn=B1 Then
          копирование строки на новый лист
    End If
Next i
Проблема в том что цикл в таком виде занимает достаточно много времени и хотелось бы его уменьшить в несколько раз.
Заранее признателен за помощь.
 
цикл возможно и нормальный, а вот изменения приводят к реакции приложения и их следует отключать на время
Код
Application.ScreenUpdating=False
Application.EnableEvents=False
Application.Calculation=xlCalculationManual

потом не забыть вернуть.
По вопросам из тем форума, личку не читаю.
 
Кнопка ЦИТИРОВАНИЯ не для КОПИРОВАНИЯ [МОДЕРАТОР]

Application.ScreenUpdading отключаю в начале работы программы. Events попробую отключить. Calculation тоже влияет даже если в книге нет формул?
Изменено: vikttur - 10.10.2021 19:50:53
 
Цитата
Hero_win написал:
Calculation тоже влияет даже если в книге нет формул?
нет. Но тогда EVENTS отдельно не сильно поможет. Оптимизировать нужно не цикл а метод копирования.
По вопросам из тем форума, личку не читаю.
 
я бы советовал не копировать каждую строку на новый лист, а сперва записать всё в переменную, а потом уже один раз скопировать все запомненные строки на нужный лист.
1. Нам не нужен ваш рабочий файл
2. сделайте небольшой пример с любыми данными
3. Нам не нужен ваш рабочий файл
Изменено: New - 10.10.2021 18:53:02
 
Цитата
New написал: записать всё в переменную, а потом уже один раз скопировать все запомненные строки на нужный лист
Буду признателен если напишите код для такого копирования сначала в переменную, а потом на лист
Пример данных во вложении.
Копирование строк сейчас осуществляется так:
Код
        Workbooks("пример.xlsx").Sheets("AllData").Range(Cells(i, 1).Address(), Cells(i, 35).Address()).Copy
        Workbooks("пример.xlsx").Sheets("Sort").Cells(Nall + 1, 1).PasteSpecial Paste:=xlPasteValues
 
Самое эффективное и простое (на мой взгляд) - выставить соответствующий автофильтр и одним махом все скопировать.
Изменено: sokol92 - 10.10.2021 19:19:48
Владимир
 
Hero_win, я пока не очень понял, что именно нужно копировать, но попробуйте этот вариант. Если не так, то опишите какие именно строки нужно копировать на лист Sort (по какому условию)

Код
Sub Макрос1()
    Dim Rng As Range, FilteredRng As Range, LastRow As Long

    With Worksheets("AllData")
        If .AutoFilterMode = False Then
            .Rows(1).AutoFilter
        Else
            If .FilterMode = True Then .ShowAllData
        End If
        
        Worksheets("Sort").Cells.Clear
        
        .UsedRange.AutoFilter Field:=1, Criteria1:="=" & .Range("A1"), Operator:=xlOr, Criteria2:="=" & .Range("B1")
        With .AutoFilter.Range
            If .Resize(.Rows.Count, 1).SpecialCells(xlCellTypeVisible).Count > 1 Then
                Set FilteredRng = .Rows.SpecialCells(xlCellTypeVisible)
                FilteredRng.Copy Worksheets("Sort").Cells(1, 1)
            End If
        End With
        .ShowAllData
        .UsedRange.AutoFilter Field:=2, Criteria1:="=" & .Range("A1"), Operator:=xlOr, Criteria2:="=" & .Range("B1")
        With .AutoFilter.Range
            If .Resize(.Rows.Count, 1).SpecialCells(xlCellTypeVisible).Count > 1 Then
                Set FilteredRng = .Offset(1, 0).Resize(.Rows.Count - 1, .Columns.Count).SpecialCells(xlCellTypeVisible)
                With Worksheets("Sort")
                    LastRow = .Cells(.Rows.Count, 1).End(xlUp).Row
                    FilteredRng.Copy Worksheets("Sort").Cells(LastRow + 1, 1)
                End With
            End If
        End With
        .ShowAllData
        Range("A1").Select
    End With
    MsgBox "Данные на лист Sort скопированы!", vbInformation, "Конец"
End Sub
Изменено: New - 10.10.2021 19:42:07
 
Hero_win,
предлагаю начать не с оптимизации кода, а с описания условий задачи
так что решаете?
Изменено: Ігор Гончаренко - 10.10.2021 19:54:38
Программисты - это люди, решающие проблемы, о существовании которых Вы не подозревали, методами, которых Вы не понимаете!
 
New, спасибо большое за совет! Об использовании автофильтра почему-то совсем не подумал, он многократно ускоряет процесс.
Ваш код практически то что нужно. Единственное, в Вашем коде используется две последовательные фильтрации и вставка результатов фильтрации на лист Sort последовательно, в связи с чем получается два момента:
1. отфильтрованные строки могут дублироваться в двух разных результатах фильтрации. Возможно ли убрать дублирующийся строки, а оставить только одно значение?
2. Я этого не упомянул, но важно сохранить последовательность строк. Пример обновил (добавилась нумерация в первом столбце). Важно чтобы по результатам фильтра строки сохраняли изначальную последовательность (например из 1,2,3,4,5 строк отфильтрованы строки 2 и 4),, т.е.  если сверху вниз шло 1,2,3,4,5, то в фильтре сверху вниз шли 2 и 4.
 
Ігор Гончаренко, поясню:
Имея в качестве исходных данных таблицу на листе AllData необходимо по имеющимся значениям в ячейках А1 и В1 выполнить выборку строк (с 1 по 35 столбец) и сохранить эту выборку на отдельном листе Sort в той последовательности в которой изначально данные строки были расположены. Условие для выборки: в столбцах А и В должны совпадать данные по условию An=A1 или Bn=A1  или An=B1  или Bn=B1. Если хотя бы одно из этих условий выполняется то вся строка должна быть скопирована на лист Sort.
 
а зачем автофильтр если есть расширенный, который и уникальность и .... все сам, сам
Код
Sub Macro5()
    With Sheets("Sort")
        .Cells.ClearContents
        .Range("Ak1:AL1") = Sheets("AllData").Range("b1:c1").Value
        .Range("Ak2:ak3") = Application.Transpose(.Range("Ak1:al1").Value)
        .Range("AL4:AL5") = Application.Transpose(.Range("Ak1:al1").Value)
    Sheets("AllData").Columns("A:AJ").AdvancedFilter Action:=xlFilterCopy, _
        CriteriaRange:=.Range("AK1:AL5"), CopyToRange:=.Range("A1"), _
        Unique:=True
        .Range("B1:C1,AK1:Al5").ClearContents
    End With
End Sub


Ну конечно есть еще ADO
или просто MSQuery.

только вот к оптимизации цикла это отношения не имеет.
Изменено: БМВ - 10.10.2021 21:29:18
По вопросам из тем форума, личку не читаю.
 
Цитата
New написал:
P.S. Если есть номер по порядку, то на листе Sort можно сделать сортировку по столбцу А. У меня задвоенных строк нет
Да, верно! Можно будет сделать на листе Sort отдельную сортировку, спасибо!
А по поводу задвоенных строк, насколько я понимаю, будет задвоение в примере когда в столбах А  и В будет одновременно Владивосток и Москва, либо Москва и Владивосток. Получается в этом случае строки попадают в оба фильтра.
 
Цитата
БМВ написал:
а зачем автофильтр если есть расширенный, который и уникальность и .... все сам, сам
Можете пояснить что Вы имеете ввиду? Можете показать пример кода?
 
Вот вам с сортировкой по вашему 2-му примеру

Код
Sub Макрос1()
    Dim Rng As Range, FilteredRng As Range, LastRow As Long
 
    With Worksheets("AllData")
        If .AutoFilterMode = False Then
            .Rows(1).AutoFilter
        Else
            If .FilterMode = True Then .ShowAllData
        End If
         
        Worksheets("Sort").Cells.Clear
         
        .UsedRange.AutoFilter Field:=2, Criteria1:="=" & .Range("B1"), Operator:=xlOr, Criteria2:="=" & .Range("C1")
        With .AutoFilter.Range
            If .Resize(.Rows.Count, 1).SpecialCells(xlCellTypeVisible).Count > 1 Then
                Set FilteredRng = .Rows.SpecialCells(xlCellTypeVisible)
                FilteredRng.Copy Worksheets("Sort").Cells(1, 1)
            End If
        End With
        .ShowAllData
        .UsedRange.AutoFilter Field:=3, Criteria1:="=" & .Range("B1"), Operator:=xlOr, Criteria2:="=" & .Range("C1")
        With .AutoFilter.Range
            If .Resize(.Rows.Count, 1).SpecialCells(xlCellTypeVisible).Count > 1 Then
                Set FilteredRng = .Offset(1, 0).Resize(.Rows.Count - 1, .Columns.Count).SpecialCells(xlCellTypeVisible)
                With Worksheets("Sort")
                    LastRow = .Cells(.Rows.Count, 1).End(xlUp).Row
                    FilteredRng.Copy Worksheets("Sort").Cells(LastRow + 1, 1)
                End With
            End If
        End With
        .ShowAllData
        Range("A1").Select
    End With
       
    With Worksheets("Sort")
        'удаляем дубликаты
        .UsedRange.RemoveDuplicates Columns:=Array(1, 2, 3, 4, 5, 6, _
            7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, _
            34, 35, 36), Header:=xlYes
        
        'сортируем
        LastRow = .Cells(.Rows.Count, 1).End(xlUp).Row
        .Sort.SortFields.Clear
        .Sort.SortFields.Add Key:=Range("A2:A" & LastRow), SortOn:=xlSortOnValues, Order:=xlAscending, DataOption:=xlSortNormal
        With .Sort
            .SetRange .Parent.UsedRange
            .Header = xlYes
            .MatchCase = False
            .Orientation = xlTopToBottom
            .SortMethod = xlPinYin
            .Apply
        End With
    End With
    MsgBox "Данные на лист Sort скопированы!", vbInformation, "Конец"
End Sub
Изменено: New - 11.10.2021 06:41:20
 
Если копируются только значения, то можно считать в массив и выбрав нужные строки в новый массив вывалить его куда надо
 
Цитата
Hero_win написал:
Можете показать пример кода?
выше добавил.
По вопросам из тем форума, личку не читаю.
 
Вот код по логике, которую предложил Александр Моторин,

Код
Sub Макрос2()
    Dim iVal1 As String, iVal2 As String, arrData, arrResult, i As Long, iRow As Long, iCol As Long

    With Worksheets("AllData")
        arrData = .Range("A1").CurrentRegion
        iVal1 = .Range("B1")
        iVal2 = .Range("C1")
    End With
    
    ReDim arrResult(1 To UBound(arrData), 1 To UBound(arrData, 2))
    With Worksheets("Sort")
        For i = 2 To UBound(arrData)
            If arrData(i, 2) = iVal1 Or arrData(i, 2) = iVal2 Or arrData(i, 3) = iVal1 Or arrData(i, 3) = iVal2 Then
                iRow = iRow + 1
                For iCol = 1 To UBound(arrData, 2)
                    arrResult(iRow, iCol) = arrData(i, iCol)
                Next iCol
            End If
        Next i
        .Range("A2").Resize(iRow, UBound(arrResult, 2)).Value = arrResult
        Worksheets("AllData").Rows(1).Copy .Cells(1, 1)
    End With
End Sub
Изменено: New - 10.10.2021 21:27:58
 
БМВ, New, Александр Моторин, всем большое спасибо за помощь!!! Завтра попробую протестировать разные варианты уже на полном объеме данных. Посмотрю что получится по скорости обработки.

New, почему-то при выполнении кода из сообщения попадают не все строки. Всего по результатам фильтрации должны попасть строки с данными: 5, 9, 13, 28, 35, 41, 46, 47, 51, 54, 64, 65, 72, 79, 91, 95, 104, 108, 114, 122 (всего 20), а здесь получается 5, 35, 46, 47, 51, 54, 65, 79, 91, 95, 122
 
Hero_win, вот если бы вы сказали, что из сообщения №15 (Макрос1) не все строки попадают на лист Sort, я бы согласился с вами. А вот при макросе из сообщение №18 (Макрос2) у меня все 20 строк попадают в лист Sort
Посмотрите приложенную картинку. Скачайте приложенный файл и нажмите на кнопку
Изменено: New - 10.10.2021 22:03:07
 
New, да, все верно. Макрос в сообщении №18 сортирует верно и в полном объеме все 20 строк. Спасибо за помощь! Просто обратил внимание что макрос из сообщения №15 не в полном объеме сортирует.
 
Цитата
Hero_win написал:
обратил внимание что макрос из сообщения №15 не в полном объеме сортирует.
вы во втором примере добавили столбец "№" в столбец А, и соответственно нужно было в коде изменить номера столбцов, где мы ставим Автофильтр с А и В, на В и С. Я забыл это сделать, сейчас исправил своё сообщение #15. Теперь все макросы из этой темы выдают 20 строк на листе Sort
Изменено: New - 11.10.2021 06:43:38
 
Цитата
New написал:
Теперь все макросы из этой темы
не все :-)
Код
Sub MacroAdo()
Dim oRecordSet As Object, oConn As Object, C1 As String, C2 As String, F, i As Integer
Sheets("Sort").Cells.ClearContents
C1 = Sheets("AllData").Range("b1")
C2 = Sheets("AllData").Range("c1")
    Set oRecordSet = CreateObject("ADODB.Recordset")
    Set oConn = CreateObject("ADODB.Connection")
    sSQL_text = "SELECT * FROM `Alldata$` WHERE `" & C1 & "`='" & C1 & "' OR `" & C1 & "`='" & C2 & "' OR `" & _
    C2 & "`='" & C1 & "' OR `" & C2 & "`='" & C2 & "'"
    sConnStr = "Provider=Microsoft.ACE.OLEDB.12.0;Mode=Read;Data Source=" & ThisWorkbook.FullName & ";Extended Properties=""Excel 12.0;HDR=Yes"";"
    oConn.Open sConnStr
    oRecordSet.Open sSQL_text, oConn
    ActiveWorkbook.Sheets("Sort").Range("A2").CopyFromRecordset oRecordSet
    For Each F In oRecordSet.Fields
        i = i + 1
        If F.Name <> C1 And F.Name <> C2 Then ActiveWorkbook.Sheets("Sort").Cells(1, i) = F.Name
    Next
End Sub

Теперь - все  :D
По вопросам из тем форума, личку не читаю.
 
А ТС просил оптимизировать цикл) а мы ему тут 4 разных подхода нарисовали
1. Автофильтр
2. Расширенный фильтр
3. Массивы
4. ADO
Изменено: New - 11.10.2021 08:37:30
 
Цитата
New написал:
оптимизировать цикл
так я к этому и привел
Скрытый текст
 :D
По вопросам из тем форума, личку не читаю.
 
Цитата
New: ТС просил оптимизировать цикл) а мы ему тут 4 разных подхода нарисовали
ну по сути -то понятно, что ТСу просто надо ускорить процесс

Мне больше всего нравится следующий вариант:
    • взять таблицу массив
    • офильтровать в нём в цикле только нужные данные
    • выгрузить куда надо

Это быстро, прозрачно, гибко и понятно
Возможно, самый быстрый вариант и уж точно самый понятный (фильтры - спорно понятны, на мой взгляд, ну а ADO с его НЕочевидностью и НЕнадёжность вообще атас  :) )
Изменено: Jack Famous - 11.10.2021 14:10:21
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄
 
Jack Famous, да, это макрос из сообщения #18. Но я думаю, что расширенный фильтр будет быстрее на 100.000 и +
 
Цитата
New: я думаю, что расширенный фильтр будет быстрее
сомневаюсь  :D
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄
 
Ну не быстрее на малых объемах, но компактнее - однозначно. А на 100к строк  (16000 результатов) в два раза расширенный фильтр быстрее массива
По вопросам из тем форума, личку не читаю.
 
Цитата
БМВ: на 100к строк  (16000 результатов) в два раза расширенный фильтр быстрее массива
Погнали тестировать
Во всех делах очень полезно периодически ставить знак вопроса к тому, что вы с давних пор считали не требующим доказательств (Бертран Рассел) ►Благодарности сюда◄
Страницы: 1 2 3 След.
Наверх