Мне нужно сделать макрос, который будет собирать номера деталей из столбца А и вставлять их на другой лист через каждые 8 пробелов. Загвоздка в том, что мне нужно сделать это на основе кодов заказов: A11, A21, A31, B11, B21, B31, C11, C21, C31, C12, C22, C32, C13, C23, C33 (находится в столбце B) на лист, Есть 5 листов, которые сгруппированы следующим образом: Лист «A##» содержит все коды, начинающиеся с «A». Лист «B##» содержит все коды с «B». Лист «C#1» содержит все коды, начинающиеся с C и заканчивающиеся на 1 и так далее. Это необходимо сделать примерно для 12000 деталей. Из того небольшого знания, что у меня есть в Excel VBA, я считаю, что массив — это самый быстрый способ сделать это.
Примером того, как выглядит код заказа, может быть «A11», «A12», «A13» для 3 кодов, которые необходимо отправить на другой лист. Я использовал подстановочные знаки, чтобы ограничить фильтрацию (например, «A**» для обозначения «A13», «A23» и т. д.).
Ниже приведен код, который я сейчас использую для выполнения этой задачи, а с другими макросами и всеми циклами первый запуск макроса занял у меня 1 час 5 минут. Однако этот макрос нужно будет запускать раз в месяц и с той же рабочей книгой, поэтому я запустил второй раз, чтобы «обновить» данные, и это заняло 3,5 часа. Теперь он больше не будет работать, поэтому мне пришлось искать другие способы его ускорения.
В следующем коде wb = активная рабочая книга, а Sht — это лист, на котором я хочу использовать коды. Я написал это так, потому что делаю это надстройкой Excel, а не просто модулем в рабочей книге.
Public Sub SetupSheetA()
Set wb = ActiveWorkbook
Set Sht = wb.Worksheets("A##")
Code = "A**"
'Grab endRow value for specific sheet designated by the order code
With wb.Worksheets("SO Hits Data Single Row")
endRow = 1 + 8 * Application.WorksheetFunction.CountIf(.Range("B4:B999999"), Code)
End With
Sht.Cells.Clear 'Clear sheet contents
'Macros
Call PartInfo
'Other macros not relevant to this question
End Sub
Public Sub PartInfo()
'***********************************************************************************************************
'Collect Part #, order code, vendor info, and WH Info
'***********************************************************************************************************
Dim j As Long, i As Long
j = Application.WorksheetFunction.CountA(wb.Sheets("SO Hits Data Single Row").Range("A1:A999999"))
With Sht
'Part #
CurrentPartRow = 2
For i = 4 To j
If Sheets("SO Hits Data Single Row").Range(Cells(i, 2).Address) Like Code Then
.Range(Cells(CurrentPartRow, 1).Address).Value = "='SO Hits Data Single Row'!" & Cells(i, 1).Address
CurrentPartRow = CurrentPartRow + 8
End If
Next i
'Order code
.Range("A3").Value = "=VLOOKUP(A2,'SO Hits Data Single Row'!$A:$B,2,FALSE)"
'Copy to Next Row
For CurrentPartRow = 10 To endRow - 7 Step 8
'Order code CopyPaste
.Range("A3").Copy Destination:=.Range(Cells(CurrentPartRow + 1, 1).Address
Next CurrentPartRow
End With
End Sub
Я попытался ускорить процесс, сохранив книгу в формате .xlbs, что уменьшило размер файла с 240 МБ до 193 МБ. Затем я удалил все данные, которые мне сошли с рук, и удалил все ненужное форматирование, которое еще больше уменьшило размер файла до 163 МБ, а затем удаление листов, на которые макрос вставляет данные, уменьшило размер файла до 73 МБ. Даже с этим гораздо меньшим файлом макрос все равно будет зависать и не отвечать, несмотря на то, что он работал в течение всех выходных.
Я также попытался отфильтровать массив, используя этот код:
Dim arr1 As Variant, arr2 As Variant, i As Long, code As String
code = "A**" 'For any order codes containing A11, A12, A13, A21, A22, _
A23, etc
Lastrow = Sheets("SO Hits Data Single Row").Cells(Rows.Count, _
1).End(xlUp).Row
arr1 = Sheets("SO Hits Data Single Row").Range("B4:B" & Lastrow).Value
arr2 = Filter(arr1, code)
Sheets("A##").Range("a1") = arr2
Но это просто выдает ошибку несоответствия.
Ниже приведен пример результата, которого мне нужно достичь.
Хорошо, я отредактировал это для уточнения.
Какая часть кода требует времени и зависает? Вы установили Application.Calculation
на xlCalculationManual
, чтобы приостановить вычисления во время выполнения кода? Кроме того, есть ли какие-либо данные между выходными номерами деталей или это «значение1, 7 пустых строк, значение2, 7 пустых строк…»? Изображение вывода поможет сделать это минимальный воспроизводимый пример
Извините, все еще не понял. Вы опубликовали этот снимок экрана с 13 парами значений. Не могли бы вы поделиться названием рабочего листа, именами рабочих листов и адресами ячеек, куда нужно скопировать эти 13 пар значений?
Пытаясь упростить ситуацию, я сделал это неясным. Мои извинения. Я обновил результат, которого пытаюсь достичь. Я справился с этим с помощью очень грубого макроса, который зависает, когда я собираю номера деталей для каждого листа (лист «A##», показанный на последнем скриншоте, который я добавил). Есть 5 листов: «A##», «B##», «C#1», «C#2», «C#3», которые берут номера деталей из «SO Hits Data Single Row». Что касается того, какие коды помещаются на какой лист, символы «#» в имени листа в основном являются подстановочными знаками и могут быть любым числом с постоянными другими цифрами (например, «C# 3» должен захватывать коды C13, C23 и C33.
Если у вас Excel 2019 или Excel 365, вы можете использовать встроенные функции SORT
и FILTER
, чтобы значительно упростить задачу:
Public Function PartsToSheet(OrderPrefix AS String) AS Boolean
PartsToSheet = False
On Error GoTo FuncErr 'Return False if there is an error
Dim calcTMP As xlCalculation
calcTMP = Application.Calculation
'Only Calculate Formulae when we explicitly say to
Application.Calculation = xlCalculationManual
Dim wsSource AS Worksheet, wsDestination AS Worksheet
Dim lParts AS Long, lRecords AS Long
Dim adTable AS String, adOrders AS String
Set wsSource = ThisWorkbook.Worksheets("SO Hits Data Single Row")
Set wsDestination = ThisWorkbook.Worksheets(OrderPrefix & "##")
'Prepare the Destination
With wsDestination
'Deleting Rows & Columns frees up the Used Range, freeing more memory than Clear does
.Range(.Cells(1, 1), .Range(.Rows.Count, 1)).EntireRow.Delete
.Range(.Cells(1, 1), .Range(1, .Columns.Count)).EntireColumn.Delete
End With
lParts = Application.CountA(wsSource.Columns(1))
lRecords = Application.CountIf(wsSource.Columns(2), OrderPrefix & "*")
adTable = wsSource.Range(wsSource.Cells(1, 1),wsSource.Cells(lParts, 2)).Address(True, True, xlA1, True)
adOrders = wsSource.Range(wsSource.Cells(1, 2),wsSource.Cells(lParts, 2)).Address(True, True, xlA1, True)
If lRecords > 0 Then 'If there are Order Codes for this Sheet
wsDestination.Range(wsDestination.Cells(2, 1), wsDestination.Cells(8 * lRecords - 6)).Formula = _
"=IF(MOD(ROW()+6,8)>0, """", INDEX(SORT(" & _
"FILTER(" & adTable & ", LEFT(" & adOrders & ", 1) = """ & OrderPrefix & """)" & _
", 2), (ROW()+6)/8, 1))"
wsDestination.Columns(1).Calculate 'Explicitly calculate formulae
wsDestination.Range(wsDestination.Cells(2, 1), wsDestination.Cells(8 * lRecords - 6)).Value = _
wsDestination.Range(wsDestination.Cells(2, 1), wsDestination.Cells(8 * lRecords - 6)).Value
End If
PartsToSheet = True 'Success!
FuncErr:
On Error GoTo -1 'Clear any errors in the handler
Application.Calculation = calcTMP
End Function
По сути, мы заполняем первый столбец целевого листа функцией, которая будет пустой для 7 строк (IF(MOD(ROW()+6,8)>0,
), затем предоставляем следующую запись (INDEX(.., (ROW()+6)/8, 1)
) в массиве, который мы получаем, FILTER
для префикса и SORT
для код заказа.
Затем мы «выравниваем» результат, преобразуя его из динамических формул в статические значения.
В Excel 2019 нет SORT
и FILTER
.
Я пытался использовать это с входами «A», «A##» и «A**», но я не получил ничего, кроме FALSE. Также, чтобы уточнить, это Excel 365.
Итак, я обнаружил, что массив на самом деле был лучшим способом приблизиться к этому. Тем не менее, размер файла явно был серьезной проблемой, и я обнаружил, что это связано с тем, что пустые ячейки были включены в текущий выбор. Однажды я исправил, что макрос работал быстрее, но все равно занимал слишком много времени. В итоге я написал код, чтобы сохранить данные в массив, а затем отфильтровать их позже, как показано ниже.
Sub Example()
Dim arr1 As Variant, arr2(10000) As Variant, i As Long, j As Long, k As Long, Filter As String
Application.ScreenUpdating = False 'Freeze screen while macro runs
Application.EnableEvents = False 'Disable popups
Application.Calculation = xlManual 'Disable Sheet calcs
Filter = "A**"
arr1 = ActiveWorkbook.Worksheets("Sheet1").Range("A4:B12000").Value
j= Application.WorksheetFunction.CountA(wb.Sheets("SO Hits Data Single Row").Range("A1:A20000"))
For i = 1 To j
If arr1(i, 2) Like Filter Then
arr2(k) = arr1(i, 1)
arr2(k + 1) = ""
arr2(k + 2) = ""
arr2(k + 3) = ""
arr2(k + 4) = ""
arr2(k + 5) = ""
arr2(k + 6) = ""
arr2(k + 7) = ""
k = k + 8 'This was so I could adjust for the blank spaces I needed between each value in the array
End If
Next i
Application.ScreenUpdating = True 'Unfreeze screen
Application.Calculation = xlAutomatic 'Enable Sheet calcs
Application.EnableEvents = True 'Enable popups
End Sub
Приведенный выше код немного более специфичен для моей ситуации, но ниже приведена более общая форма для будущих зрителей.
Sub Example()
Dim arr1 As Variant, arr2(10000) As Variant, i As Long, j As Long, k As Long, Filter As String
Application.ScreenUpdating = False 'Freeze screen while macro runs
Application.EnableEvents = False 'Disable popups
Application.Calculation = xlManual 'Disable Sheet calcs
Filter = "A**" 'This is where you would put your filter instead of "A**"
arr1 = ActiveWorkbook.Worksheets("Sheet1").Range("A4:B12000").Value
j= Application.WorksheetFunction.CountA(wb.Sheets("SO Hits Data Single Row").Range("A1:A20000"))
For i = 1 To j
If arr1(i, 2) Like Filter Then
arr2(k) = arr1(i, 1)
End If
Next i
Application.ScreenUpdating = True 'Unfreeze screen
Application.Calculation = xlAutomatic 'Enable Sheet calcs
Application.EnableEvents = True 'Enable popups
End Sub
Не могли бы вы подробнее рассказать об этом
9 of 45 codes
(поделитесь списком) и об этомSet Sht = wb.Worksheets("A##"): Code = "A**"
? Также опишите, на каких листах это происходит, что делает формулаVLOOKUP
и т. д. Будьте более конкретными. Вы можете отредактируй свой пост в любое время.