Sort column into rows of eight cells - excel

I don't know if it's possible to do it,
I need to copy data from sheet 2 from column B with a variable range,
selecting 8 lines at a time from sheet 2,
pasting with transposition in sheet 1 starting from row 9 onwards? thank you
Sub copy()
Sheets(2).Range("B1:B8").Copy
With Sheets(1).Range("B9:I9")
.PasteSpecial Paste:=xlPasteValues, Transpose:=True
End With
Sheets(2).Range("B9:B16").Copy
With Sheets(1).Range("B10:I10")
.PasteSpecial Paste:=xlPasteValues, Transpose:=True
End With
Sheets(2).Range("B17:B24").Copy
With Sheets(1).Range("B11:I11")
.PasteSpecial Paste:=xlPasteValues, Transpose:=True
End With
Sheets(2).Range("B25:B32").Copy
With Sheets(1).Range("B12:I12")
.PasteSpecial Paste:=xlPasteValues, Transpose:=True
End With
Application.CutCopyMode = True
End Sub ```

The code assumes that the range we want to copy and paste is always the same and always 8 rows.
I assume the original data looks like this:
Then we can copy and transpose the range to this:
by using this code:
Sub Copy_paste_transpose()
Dim lrow_copy As Long
Dim i As Long, j As Long
Dim rows_to_copy As Long
lrow_copy = Sheets(2).Cells(Rows.Count, "B").End(xlUp).Row 'Find last row in Sheet2
i = 9 'Start pasting at row 9
rows_to_copy = 7 'always "remove" one row.
For j = 1 To lrow_copy Step 8 'Loop through range and "jump" 8 rows at each looping.
Sheets(2).Range(Sheets(2).Cells(j, "B"), Sheets(2).Cells(j + rows_to_copy, "B")).Copy 'Copy range
Sheets(1).Range(Sheets(1).Cells(i, 2), Sheets(1).Cells(i, 2 + rows_to_copy)).PasteSpecial Paste:=xlPasteValues, Transpose:=True 'Paste range and transpose the copied range
i = i + 1 'add one row after each paste
Next j
Application.CutCopyMode = False 'Deselect last copy selection
End Sub

Try this code:
Sub copy()
Const PERIOD = 8, PASTE_FROM_ROW = 9
Dim last_row As Long, i As Long
last_row = Sheets(2).Cells(Rows.Count, "B").End(xlUp).Row
For i = 0 To last_row \ PERIOD - 1
Sheets(1).Cells(PASTE_FROM_ROW + i, "B").Resize(, PERIOD) = _
WorksheetFunction.Transpose(Sheets(2).Cells(i * PERIOD + 1, "B").Resize(PERIOD))
Next
End Sub

Transpose a Column
Option Explicit
Sub TransposeColumn()
Const ProcName As String = "TransposeColumn"
On Error GoTo ClearError
Const sID As Variant = 2 ' or "Sheet2"
Const sFirst As String = "B1"
Const dID As Variant = 1 ' or "Sheet1"
Const dFirst As String = "B9"
Const dcCount As Long = 8
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
' Create a reference to the Source First Cell Range.
Dim sws As Worksheet: Set sws = wb.Worksheets(sID)
Dim sfCell As Range: Set sfCell = sws.Range(sFirst)
' Using the 'GetTransposedColumn' function, write the transposed data
' to the Destination Array.
Dim dData As Variant: dData = GetTransposedColumn(sfCell, dcCount)
If IsEmpty(dData) Then Exit Sub
' Create a reference to the Destination First Cell Range.
Dim dws As Worksheet: Set dws = wb.Worksheets(dID)
Dim dfCell As Range: Set dfCell = dws.Range(dFirst)
' Check if the values from the Destination Array fit
' into the Destination Worksheet.
Dim drCount As Long: drCount = UBound(dData, 1)
If drCount > dws.Rows.Count - dfCell.Row + 1 Then Exit Sub
If dcCount > dws.Columns.Count - dfCell.Column + 1 Then Exit Sub
' Create a reference to the Destination Range.
Dim drg As Range: Set drg = dfCell.Resize(drCount, dcCount)
' Write the values from the Destination Array to the Destination Range.
drg.Value = dData
' Clear the contents below the Destination Range.
With drg
Dim crCount As Long: crCount = .Worksheet.Rows.Count - .Row + 1
If crCount > drCount Then
.Resize(crCount - drCount).Offset(drCount).ClearContents
End If
End With
MsgBox "Data transposed.", vbInformation, ProcName
ProcExit:
Exit Sub
ClearError:
Debug.Print "'" & ProcName & "': Unexpected Error!" & vbLf _
& " " & "Run-time error '" & Err.Number & "':" & vbLf _
& " " & Err.Description
Resume ProcExit
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Returns the transposed values of a one-column range
' in a 2D one-based array.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function GetTransposedColumn( _
ByVal FirstCell As Range, _
Optional ByVal ColumnsCount As Long = 1) _
As Variant
Const ProcName As String = "GetTransposedColumn"
On Error GoTo ClearError
If FirstCell Is Nothing Then Exit Function
If ColumnsCount < 1 Then Exit Function
Dim srg As Range
Dim srCount As Long
With FirstCell.Cells(1)
Dim lCell As Range
Set lCell = .Resize(.Worksheet.Rows.Count - .Row + 1) _
.Find("*", , xlFormulas, , , xlPrevious)
If lCell Is Nothing Then Exit Function
srCount = lCell.Row - .Row + 1
Set srg = .Resize(srCount)
End With
Dim sData As Variant
If srCount = 1 Then
ReDim sData(1 To 1, 1 To 1): sData(1, 1) = srg.Value
Else
sData = srg.Value
End If
Dim dcrCount As Long: dcrCount = Int(srCount / ColumnsCount)
Dim dcRem As Long: dcRem = srCount Mod ColumnsCount
Dim drCount As Long
If dcRem = 0 Then
drCount = dcrCount
Else
drCount = dcrCount + 1
End If
Dim dData As Variant: ReDim dData(1 To drCount, 1 To ColumnsCount)
Dim r As Long
Dim c As Long
Dim n As Long
If dcrCount > 0 Then
For r = 1 To dcrCount
For c = 1 To ColumnsCount
n = n + 1
dData(r, c) = sData(n, 1)
Next c
Next r
Else
r = 1
End If
If dcRem > 0 Then
For c = 1 To dcRem
n = n + 1
dData(r, c) = sData(n, 1)
Next c
End If
GetTransposedColumn = dData
ProcExit:
Exit Function
ClearError:
Debug.Print "'" & ProcName & "': Unexpected Error!" & vbLf _
& " " & "Run-time error '" & Err.Number & "':" & vbLf _
& " " & Err.Description
Resume ProcExit
End Function

Related

Advanced filters is returning a single duplicate name at both the beginning and end of the created list?

I am attempting to combine four separate list of name into a single list without showing any duplicates. The code below uses the advanced filters to first filter for unique names from each of the four list and then combine them into a single name list. It then again uses advanced filters on the newly created consolidated name list to double check for duplicates and then writes the final list of unique names.
My issue is that the final name list is showing a single duplicate name that appears at both the beginning and at the end list.
Option Explicit
Sub CreateUniqueList()
Dim lastrow As Long
ActiveSheet.Range("d:d").Clear
ActiveSheet.Range("x:x").Clear
ActiveSheet.Range("g13:g36").AdvancedFilter xlFilterCopy, , ActiveSheet.Range("D2"), True
lastrow = Cells(Rows.Count, "d").End(xlUp).Row + 1
ActiveSheet.Range("i13:i36").AdvancedFilter xlFilterCopy, , ActiveSheet.Range("d" & lastrow), True
lastrow = Cells(Rows.Count, "d").End(xlUp).Row + 1
ActiveSheet.Range("k13:k36").AdvancedFilter xlFilterCopy, , ActiveSheet.Range("d" & lastrow), True
lastrow = Cells(Rows.Count, "d").End(xlUp).Row + 1
ActiveSheet.Range("m13:m36").AdvancedFilter xlFilterCopy, , ActiveSheet.Range("d" & lastrow), True
lastrow = Cells(Rows.Count, "d").End(xlUp).Row
ActiveSheet.Range("d2:d" & lastrow).AdvancedFilter xlFilterCopy, , ActiveSheet.Range("x2"), True
ActiveSheet.Range("d:d").Clear
End Sub
I'm sure it is a simple mistake but for the life of me I can't figure it out.
Copy Unique Values From Columns
AdvancedFilter will copy the headers, so if the first row is 1, and 1 is found somewhere below, it will remain a duplicate. An idea would be to copy the range from column D to X right before your last AdvancedFilter action and apply a RemoveDuplicates instead.
But I've opted for a faster solution using data structures i.e. writing the whole source range to an array, writing the unique values from the designated columns of the source range to a dictionary, writing the values from the dictionary to another array, and finally, writing the values from the array to the destination range. Also, there is no need for a helper column.
Option Explicit
Sub CreateUniqueList()
' Source
Const sName As String = "Sheet1"
Const srgAddress As String = "G13:M36"
Dim sCols As Variant: sCols = Array(1, 3, 5, 7)
' Destination
Const dName As String = "Sheet1"
Const dfCellAddress As String = "X2"
' Workbook
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
' Return the values from the source range ('srg')
' in the 2D one-based source array ('sData').
Dim sws As Worksheet: Set sws = wb.Worksheets(sName)
Dim srg As Range: Set srg = sws.Range(srgAddress)
Dim sData As Variant: sData = srg.Value
' Return the unique values from the designated columns ('sCols')
' of the source array in a dictionary ('dict')
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
dict.CompareMode = vbTextCompare
Dim c As Long
For c = LBound(sCols) To UBound(sCols)
DictAddColumn dict, sData, sCols(c)
Next c
Erase sData
' Return the values from the dictionary
' in the 2D one-based one-column destination array ('dData').
Dim dData As Variant: dData = GetColumnDictKeys(dict)
Set dict = Nothing
Dim drCount As Long: drCount = UBound(dData, 1)
Dim dws As Worksheet: Set dws = wb.Worksheets(dName)
With dws.Range(dfCellAddress)
' Write the result.
.Resize(drCount).Value = dData
' Clear below.
.Resize(dws.Rows.Count - .Row - drCount + 1) _
.Offset(drCount).ClearContents
End With
MsgBox "Unique list created.", vbInformation
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Adds the unique values from a column ('sColumnIndex')
' of a 2D array ('sData') to an existing dictionary ('dDict').
' Remarks: Error values and blanks are excluded.
' Remarks: 'ByRef' indicates that the dictionary in the calling procedure
' will be modified.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Sub DictAddColumn( _
ByRef dDict As Object, _
ByVal sData As Variant, _
Optional ByVal sColumnIndex As Variant, _
Optional ByVal DoCount As Boolean = False)
Const ProcName As String = "DictAddColumn"
On Error GoTo ClearError
Dim sKey As Variant
Dim sr As Long
For sr = LBound(sData, 1) To UBound(sData, 1)
sKey = sData(sr, sColumnIndex)
If Not IsError(sKey) Then
If Len(CStr(sKey)) > 0 Then
If DoCount Then
dDict(sKey) = dDict(sKey) + 1
Else
dDict(sKey) = Empty
End If
End If
End If
Next sr
ProcExit:
Exit Sub
ClearError:
Debug.Print "'" & ProcName & "' Run-time error '" _
& Err.Number & "':" & vbLf & " " & Err.Description
Resume ProcExit
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Returns the keys from a dictionary ('sDict')
' in a 2D one-based one-column array.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function GetColumnDictKeys( _
ByVal sDict As Object) _
As Variant
Const ProcName As String = "GetColumnDictKeys"
On Error GoTo ClearError
Dim dData As Variant: ReDim dData(1 To sDict.Count, 1 To 1)
Dim sKey As Variant
Dim dr As Long
For Each sKey In sDict.Keys
dr = dr + 1
dData(dr, 1) = sKey
Next sKey
GetColumnDictKeys = dData
ProcExit:
Exit Function
ClearError:
Debug.Print "'" & ProcName & "' Run-time error '" _
& Err.Number & "':" & vbLf & " " & Err.Description
Resume ProcExit
End Function
EDIT
This solution copies the complete ranges' values and applies RemoveDuplicates.
Sub CreateUniqueListCopyByAssignment()
' without helper column
Const cCount As Long = 4
Dim ws As Worksheet: Set ws = ActiveSheet ' improve!
Dim srg As Range: Set srg = ws.Range("G13:G36")
Dim rCount As Long: rCount = srg.Rows.Count
Dim drg As Range: Set drg = ws.Range("X2").Resize(rCount)
Application.ScreenUpdating = False
ws.Range("X2:X" & ws.Rows.Count).Clear
Dim c As Long
For c = 0 To cCount - 1
drg.Offset(c * rCount).Value = srg.Offset(, c * 2).Value
Next c
drg.Resize(rCount * cCount).RemoveDuplicates 1, xlNo
Application.ScreenUpdating = True
End Sub
This solution is similar to yours, but it applies RemoveDuplicates near the end, mentioned at the top of this post. I think these ranges are too small to harvest the power of AdvancedFilter.
Sub CreateUniqueListQuickFix()
' with helper column
Application.ScreenUpdating = False
With ActiveSheet
Dim rCount As Long: rCount = .Rows.Count
Dim lr As Long
.Range("X2:X" & rCount).Clear
.Range("g13:g36").AdvancedFilter xlFilterCopy, , .Range("D2"), True
lr = Cells(rCount, "D").End(xlUp).Row + 1
.Range("i13:i36").AdvancedFilter xlFilterCopy, , .Range("D" & lr), True
lr = Cells(rCount, "D").End(xlUp).Row + 1
.Range("k13:k36").AdvancedFilter xlFilterCopy, , .Range("D" & lr), True
lr = Cells(rCount, "D").End(xlUp).Row + 1
.Range("m13:m36").AdvancedFilter xlFilterCopy, , .Range("D" & lr), True
lr = Cells(rCount, "D").End(xlUp).Row
.Range("D2:D" & lr).RemoveDuplicates 1, xlNo
lr = Cells(rCount, "D").End(xlUp).Row
.Range("D2:D" & lr).Copy .Range("X2")
.Range("D2:D" & lr).Clear
End With
Application.ScreenUpdating = True
End Sub

Excel VBA Reverse Selected Cells

I have the following bit in a macro in my workbook. It selects the last n=10 rows to set as the data source.
.lstDbase.RowSource = "Stencils!A" & iRow - 10 & ":R" & iRow
Am I able to reverse this selection without actually saving the reversed data?
Get Range Rows Reverse
Option Explicit
Sub PopulateRangeRowsReverse() ' ???
Const fRow As Long = 2 ' ???
Const rMaxOffset As Long = 10
'Const iRow As Long = 11 ' ???
'With ???
Dim lrCount As Long: lrCount = iRow - fRow + 1
If lrCount < 1 Then Exit Sub ' no data
If lrCount > rMaxOffset Then lrCount = rMaxOffset
Dim rg As Range
Set rg = ThisWorkbook.Worksheets("Stencils") _
.Rows(iRow - lrCount + 1).Columns("A:R").Resize(lrCount)
Dim Data As Variant: Data = GetRangeRowsReverse(rg)
With .lstDbase
.Clear
.ColumnCount = rg.Columns.Count
.List = Data
End With
'End With
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Returns the reversed rows of a range in a 2D one-based array.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function GetRangeRowsReverse( _
ByVal rg As Range) _
As Variant
Const ProcName As String = "GetRangeRowsReverse"
On Error GoTo ClearError
Dim sData As Variant
Dim rCount As Long
Dim cCount As Long
With rg
rCount = .Rows.Count
cCount = .Columns.Count
If rCount + cCount = 2 Then
ReDim sData(1 To 1, 1 To 1): sData(1, 1) = .Value
Else
sData = .Value
End If
End With
Dim dData As Variant: ReDim dData(1 To rCount, 1 To cCount)
Dim r As Long
Dim c As Long
For r = 1 To rCount
For c = 1 To cCount
dData(r, c) = sData(rCount, c)
Next c
rCount = rCount - 1
Next r
GetRangeRowsReverse = dData
ProcExit:
Exit Function
ClearError:
Debug.Print "'" & ProcName & "' Run-time error '" _
& Err.Number & "':" & vbLf & " " & Err.Description
Resume ProcExit
End Function

Combine data from multiple worksheets to one sheet on key word from column

im sorry for making similar question but im run into a problem, bcs i don t know very good VBA coding...
I found many similar questions, and i found a code that i can apply to my needs.
I found code here But i don't know how to edit that code so that he can work in my Workbook. I have workbook with 35 worksheets, all with same format, values are in columns "A:F", in column "E" i have text "On Stock" and "Sent", i want all rows from all worksheets that have "On Stock" value in column "E" to be copied into one worksheet named "Blanko List". I tried to edit code myself, but it can t run, nothing happens. Thanks in advance.
Edited code
Sub CommandButton4_Click()
Dim wM As Worksheet, ws As Worksheet
Dim r As Long, lr As Long, nr As Long, y As Long
Dim c As Range, firstaddress As String
Application.ScreenUpdating = False
Set wM = Sheets("Blanko List")
lr = wM.Cells.Find("*", , xlValues, xlWhole, xlByRows, xlPrevious, False).Row
If lr > 1 Then wM.Range("A2:G" & lr).ClearContents
For Each ws In ThisWorkbook.Worksheets
If ws.Name <> "Blanko List" Then
y = 0
On Error Resume Next
y = Application.CountIf(ws.Columns(7), "On Stock")
On Error GoTo 0
If y > 1 Then
firstaddress = ""
With ws.Columns(7)
Set c = .Find("On Stock", LookIn:=xlValues, LookAt:=xlWhole)
If Not c Is Nothing Then
firstaddress = c.Address
Do
nr = wM.Range("G" & Rows.Count).End(xlUp).Offset(1).Row
ws.Range("A" & c.Row & ":G" & c.Row).Copy wM.Range("A" & nr)
Application.CutCopyMode = False
Set c = .FindNext(c)
Loop While Not c Is Nothing And c.Address <> firstaddress
End If
End With
End If
End If
Next ws
wM.Activate
Application.ScreenUpdating = True
''''
Original code:
Option Explicit
Sub GetYes()
Dim wM As Worksheet, ws As Worksheet
Dim r As Long, lr As Long, nr As Long, y As Long
Dim c As Range, firstaddress As String
Application.ScreenUpdating = False
Set wM = Sheets("Master")
lr = wM.Cells.Find("*", , xlValues, xlWhole, xlByRows, xlPrevious, False).Row
If lr > 1 Then wM.Range("A2:G" & lr).ClearContents
For Each ws In ThisWorkbook.Worksheets
If ws.Name <> "Master" Then
y = 0
On Error Resume Next
y = Application.CountIf(ws.Columns(7), "Yes")
On Error GoTo 0
If y > 1 Then
firstaddress = ""
With ws.Columns(7)
Set c = .Find("Yes", LookIn:=xlValues, LookAt:=xlWhole)
If Not c Is Nothing Then
firstaddress = c.Address
Do
nr = wM.Range("G" & Rows.Count).End(xlUp).Offset(1).Row
ws.Range("A" & c.Row & ":G" & c.Row).Copy wM.Range("A" & nr)
Application.CutCopyMode = False
Set c = .FindNext(c)
Loop While Not c Is Nothing And c.Address <> firstaddress
End If
End With
End If
End If
Next ws
wM.Activate
Application.ScreenUpdating = True
End Sub
Copy Criteria Rows
Option Explicit
Sub CopyCriteriaRows()
' Source
Const sCols As String = "A:F"
Const sfRow As Long = 2
Const scCol As Long = 5
Const sCriteria As String = "On Stock"
' Destination
Const dName As String = "Blanco List"
Const dFirst As String = "A2"
' Exceptions
Const ExceptionsList As String = "Blanco List" ' add more
Const ListSeparator As String = ","
' Workbook
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
' Write the names of the worksheets to be 'processed' to an array.
Dim wsNames As Variant
wsNames = ArrWorksheetNames(wb, ExceptionsList, ListSeparator)
If IsEmpty(wsNames) Then Exit Sub ' no worksheet found
' Create a reference to the first destination row range.
' Note that the number of columns is equal in source and destination.
Dim dws As Worksheet: Set dws = wb.Worksheets(dName)
Dim cCount As Long: cCount = dws.Columns(sCols).Columns.Count
Dim drrg As Range: Set drrg = dws.Range(dFirst).Resize(, cCount)
Dim sws As Worksheet ' Source Worksheet
Dim srg As Range ' Source Range
Dim sfrrg As Range ' Source First Row Range
Dim drg As Range ' Destination Range
Dim Data As Variant ' Data Array
Dim cValue As Variant ' Current Value
Dim dr As Long ' Destination Row Counter
Dim sr As Long ' Source Row Counter
Dim c As Long ' Column Counter
For Each sws In wb.Worksheets(wsNames)
' Create a reference to the current Source First Row Range.
Set sfrrg = sws.Columns(sCols).Rows(sfRow)
Set srg = Nothing
' Create a reference to the current Source Range.
Set srg = RefColumns(sfrrg)
If Not srg Is Nothing Then ' the current Source Range is not empty
' Write the values from the current Source Range to the Data Array.
Data = GetRange(srg)
' Write the matches to the top of the Data Array. The size
' of the array stays the same but 'dr' is used: to track
' the number of, to move, and finally, to write (to the worksheet)
' the 'destination' values.
dr = 0
For sr = 1 To UBound(Data, 1)
cValue = Data(sr, scCol)
If StrComp(CStr(cValue), sCriteria, vbTextCompare) = 0 Then
dr = dr + 1
For c = 1 To cCount
Data(dr, c) = Data(sr, c)
Next c
End If
Next sr
If dr > 0 Then ' there have been matches
' Create a reference to the Destination Range.
Set drg = drrg.Resize(dr)
' Write only the 'destination' values (dr) from
' the Data Array to the Destination Range.
drg.Value = Data
' Create a reference to the next Destination First Row Range.
Set drrg = drrg.Offset(dr)
End If
End If
Next sws
' The 'Clear Range' is the range spanning
' from the last 'Destination First Row Range'
' (which was referenced, but was not written to)
' to the bottom-most row range of the worksheet.
Dim crg As Range
Set crg = drrg.Resize(dws.Rows.Count - drrg.Row + 1)
crg.ClearContents
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Returns the names of the worksheets of a workbook ('wb'),
' that are not included in a list ('ExceptionsList'),
' in an array.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function ArrWorksheetNames( _
ByVal wb As Workbook, _
Optional ByVal ExceptionsList As String = "", _
Optional ByVal ListSeparator As String = ",", _
Optional ByVal FirstIndex As Long = 0) _
As Variant
If wb Is Nothing Then Exit Function
Dim wsCount As Long: wsCount = wb.Worksheets.Count
If wsCount = 0 Then Exit Function ' There could e.g. only be charts.
Dim IndexDiff As Long: IndexDiff = FirstIndex - 1
Dim LastIndex As Long: LastIndex = wsCount + IndexDiff
Dim Arr() As String: ReDim Arr(FirstIndex To LastIndex)
Dim n As Long: n = IndexDiff
Dim ws As Worksheet
If Len(ExceptionsList) = 0 Then
For Each ws In wb.Worksheets
n = n + 1
Arr(n) = ws.Name
Next ws
Else
Dim Exceptions() As String
Exceptions = Split(ExceptionsList, ListSeparator)
For Each ws In wb.Worksheets
If IsError(Application.Match(ws.Name, Exceptions, 0)) Then
n = n + 1
Arr(n) = ws.Name
End If
Next ws
End If
Select Case n
Case IndexDiff
Exit Function
Case Is < LastIndex
ReDim Preserve Arr(FirstIndex To n)
End Select
ArrWorksheetNames = Arr
End Function
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Creates a reference to the range spanning from the first row
' of a given range ('rg') to the row containing the bottom-most
' non-empty cell of the given range's columns.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function RefColumns( _
ByVal rg As Range) _
As Range
If rg Is Nothing Then Exit Function
With rg.Rows(1)
Dim lCell As Range
Set lCell = .Resize(.Worksheet.Rows.Count - .Row + 1) _
.Find("*", , xlFormulas, , xlByRows, xlPrevious)
If lCell Is Nothing Then Exit Function ' empty range
Set RefColumns = .Resize(lCell.Row - .Row + 1)
End With
End Function
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Returns the values of a range in a 2D one-based array.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function GetRange( _
ByVal rg As Range) _
As Variant
If rg Is Nothing Then Exit Function
Dim rData As Variant
If rg.Rows.Count + rg.Columns.Count = 2 Then ' one cell only
ReDim rData(1 To 1, 1 To 1): rData(1, 1) = rg.Value
Else
rData = rg.Value
End If
GetRange = rData
End Function
' Irrelevant to the Question,
' but for a better understanding of `ArrWorksheetNames`.
Sub ArrWorksheetNamesTEST()
Const ExceptionsList As String = "Sheet1,Sheet2,Sheet3,Sheet4"
Const ListSeparator As String = ","
Const FirstIndex As Long = 4
Dim wb As Workbook: Set wb = ThisWorkbook
Dim wsNames As Variant
wsNames = ArrWorksheetNames(wb, ExceptionsList, ListSeparator, FirstIndex)
If IsEmpty(wsNames) Then
Debug.Print "No worksheets."
Else
Debug.Print "[" & LBound(wsNames) & "," & UBound(wsNames) & "]" _
& vbLf & Join(wsNames, vbLf)
End If
End Sub
You can use this to develop an array of values and then dump them into some collection sheet.
Sub grabAllSheets()
Const exclude_Sheet = "Result" ' name of sheet to drop data
Const tangoText = "On Stock"
Dim ws As Worksheet, aCell As Range
ReDim allvalues(1 To 6, 1 To 1)
Dim i As Long, c As Long
For Each ws In ThisWorkbook.Worksheets
If ws.Name <> exclude_Sheet Then
For Each aCell In Intersect(ws.Range("E:E"), ws.UsedRange).Cells
If aCell.Value = tangoText Then
i = i + 1
ReDim Preserve allvalues(1 To 6, 1 To i)
For c = 1 To Range("F:F").Column
allvalues(c, i) = ws.Cells(aCell.Row, c).Value
Next c
End If
Next aCell
End If
Next ws
Dim theRow As Long
With Sheets(exclude_Sheet)
theRow = .Cells(.Rows.Count, 1).End(xlUp).Row
.Cells(IIf(theRow = 1, 1, theRow + 1), 1).Resize(i, 6).Value = _
Application.WorksheetFunction.Transpose(allvalues)
End With
End Sub

How to speed data copy from sheet1 to Other sheets by using Arrays, Excel vba?

I have workbook with three sheets.
I copy data from sheet1 to sheet2 & sheet3 depend on specific condition on sheet1, value = "Yes" on columns T or U.
The below code works fine using for Loop, but it is slow.
Now I transferred all data of sheet1 to array .
MyArray = Sheet1.Range("A3:U" & LastRow).Value2
is it possible to copy data from this array (by condition if specific value on it) to the other sheets .
I am new to vba , so any help will be appreciated .
Sub Copy_Data_On_Condition()
Application.Calculation = xlCalculationManual
Application.ScreenUpdating = False
Dim LastRow As Long
Dim ris_column As Range
Dim cell As Object
Dim DestRng As Range
Dim MyArray() As Variant
LastRow = Sheet1.Cells(Rows.count, 1).End(xlUp).Row
MyArray = Sheet1.Range("A3:U" & LastRow).Value2
Set ris_column = Sheet1.Range("T3:T" & LastRow)
For Each cell In ris_column
If cell.value = "Yes" Then
Set DestRng = Sheet2.Range("A" & Rows.count).End(xlUp).Offset(1, 0)
cell.EntireRow.Copy DestRng
End If
Next cell
Set ris_column = Sheet1.Range("U3:U" & LastRow)
For Each cell In ris_column
If cell.value = "Yes" Then
Set DestRng = Sheet3.Range("A" & Rows.count).End(xlUp).Offset(1, 0)
cell.EntireRow.Copy DestRng
End If
Next cell
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
End Sub
Update: Both two answers works perfectly , I tested on a sheet with total 2180 rows and copied rows about 1200. "FaneDure" Code takes about 4 second to finish and "Super Symmetry" code takes 0.07 of second which is significantly faster .
Please, try the next code:
Sub Copy_Data_On_Condition()
Dim sh1 As Worksheet, sh2 As Worksheet, sh3 As Worksheet, LastRow As Long
Dim arr_column, rngT As Range, rngU As Range, i As Long, lastCol As Long
Set sh1 = Sheet1: Set sh2 = Sheet2: Set sh3 = Sheet3 'only to make the code more compact
LastRow = sh1.cells(rows.count, 1).End(xlUp).row 'last row in A:A column
lastCol = sh1.UsedRange.Columns.count 'last column of Sheet1, to avoid copying the whole row
arr_column = sh1.Range("T3:U" & LastRow).Value2 'put in an array the columns to be processed against "Yes" string
'process both columns in the same iteration to make code faster
For i = 1 To UBound(arr_column) 'iterate between the array rows and process the columns values
If arr_column(i, 1) = "Yes" Then 'finding a match in column T:T:
If rngT Is Nothing Then 'if the rngT keeping the range to be copied is not Set (yet)
Set rngT = sh1.Range(sh1.cells(i + 2, 1), sh1.cells(i + 2, lastCol)) 'the range is Set by the used range suitable row
Else
Set rngT = Union(rngT, sh1.Range(sh1.cells(i + 2, 1), sh1.cells(i + 2, lastCol))) 'add the suitable row to the existing range
End If
End If
If arr_column(i, 2) = "Yes" Then 'finding a match in column U:U:
If rngU Is Nothing Then 'if the rngU keeping the range to be copied is not Set (yet)
Set rngU = sh1.Range(sh1.cells(i + 2, 1), sh1.cells(i + 2, lastCol)) 'the range is Set by the used range suitable row
Else
Set rngU = Union(rngU, sh1.Range(sh1.cells(i + 2, 1), sh1.cells(i + 2, lastCol))) 'add the suitable row to the existing range
End If
End If
Next i
If Not rngT Is Nothing Then 'if rngT has been set (it contains at least a row), copy it in Sheet2
rngT.Copy Destination:=sh2.Range("A" & sh2.rows.count).End(xlUp).Offset(1) 'copy the range at once
End If
If Not rngU Is Nothing Then 'if rngU has been set (it contains at least a row), copy it in Sheet3
rngU.Copy Destination:=sh3.Range("A" & sh3.rows.count).End(xlUp).Offset(1) 'copy the range at once
End If
End Sub
Because a direct autofilter is not an option, processing the array in memory should give you the fastest result as it minimises the interaction of VBA with the excel application. I believe the following should make your code significantly faster:
Sub Copy_Data_On_Condition()
Dim dStart As Double: dStart = Timer
Application.Calculation = xlCalculationManual
Application.ScreenUpdating = False
Dim srcData As Variant
Dim sht2Data() As Variant
Dim sht2Rows As Long
Dim sht2CriteriaCol As Long: sht2CriteriaCol = 20 'T
Dim sht3Data() As Variant
Dim sht3Rows As Long
Dim sht3CriteriaCol As Long: sht3CriteriaCol = 21 'U
Dim outputCols As Long
Dim i As Long, j As Long
With Sheet1
srcData = .Range("A3:U" & .Cells(Rows.Count, 1).End(xlUp).Row).Value
End With
outputCols = UBound(srcData, 2)
For i = LBound(srcData) To UBound(srcData)
If srcData(i, sht2CriteriaCol) = "Yes" Then
sht2Rows = sht2Rows + 1
ReDim Preserve sht2Data(1 To outputCols, 1 To sht2Rows)
For j = 1 To outputCols
sht2Data(j, sht2Rows) = srcData(i, j)
Next j
End If
If srcData(i, sht3CriteriaCol) = "Yes" Then
sht3Rows = sht3Rows + 1
ReDim Preserve sht3Data(1 To outputCols, 1 To sht3Rows)
For j = 1 To outputCols
sht3Data(j, sht3Rows) = srcData(i, j)
Next j
End If
Next i
If sht2Rows > 0 Then
Sheet2.Cells(Rows.Count, "A").End(xlUp).Offset(1, 0).Resize(sht2Rows, outputCols).Value = WorksheetFunction.Transpose(sht2Data)
End If
If sht3Rows > 0 Then
Sheet3.Cells(Rows.Count, "A").End(xlUp).Offset(1, 0).Resize(sht3Rows, outputCols).Value = WorksheetFunction.Transpose(sht3Data)
End If
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
MsgBox "Time taken: " & Format(Timer - dStart, "0.000s")
End Sub
Another fast option is to add a dummy sheet (if possible), use autofilter then delete the dummy worksheet. This is very fast and the code is very simple:
Sub Copy_Data_On_Condition2()
Dim dStart As Double: dStart = Timer
Application.Calculation = xlCalculationManual
Application.ScreenUpdating = False
Application.DisplayAlerts = False
Sheet1.Copy After:=Sheet1
With ActiveSheet
With .Range("A3:U" & .Cells(Rows.Count, 1).End(xlUp).Row)
.Rows(1).Offset(-1, 0).AutoFilter Field:=20, Criteria1:="Yes"
.Copy Destination:=Sheet2.Range("A" & Rows.Count).End(xlUp).Offset(1, 0)
.Rows(1).Offset(-1, 0).AutoFilter Field:=20
.Rows(1).Offset(-1, 0).AutoFilter Field:=21, Criteria1:="Yes"
.Copy Destination:=Sheet3.Range("A" & Rows.Count).End(xlUp).Offset(1, 0)
.AutoFilter
End With
.Delete
End With
Application.DisplayAlerts = True
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
MsgBox Format(Timer - dStart, "0.000")
End Sub
Edit: (following comment and file share)
Your worksheet is protected but without password. Therefore, you can actually do autfilter in place without having to add a new dummy sheet. Your autfilter becomes:
Sub Copy_Data_On_Condition2()
Dim dStart As Double: dStart = Timer
Application.Calculation = xlCalculationManual
Application.ScreenUpdating = False
Application.DisplayAlerts = False
' Check first if there's autfilter
If Sheet1.AutoFilterMode Then Sheet1.AutoFilter.ShowAllData
With Sheet2
If .AutoFilterMode Then .AutoFilter.ShowAllData
.Rows("4:" & .Rows.Count).ClearContents
End With
With Sheet3
If .AutoFilterMode Then .AutoFilter.ShowAllData
.Rows("4:" & .Rows.Count).ClearContents
End With
'=========== Super Symmetry Code _ Auto Filter
With Sheet1
.Unprotect
With .Range("A3:U" & .Cells(Rows.Count, 1).End(xlUp).Row)
.Rows(1).Offset(-1, 0).AutoFilter Field:=20, Criteria1:="Yes"
.Copy Destination:=Sheet2.Range("A" & Rows.Count).End(xlUp).Offset(1, 0)
.Rows(1).Offset(-1, 0).AutoFilter Field:=20
.Rows(1).Offset(-1, 0).AutoFilter Field:=21, Criteria1:="Yes"
.Copy Destination:=Sheet3.Range("A" & Rows.Count).End(xlUp).Offset(1, 0)
End With
.Protect
End With
Application.DisplayAlerts = True
Application.Calculation = xlCalculationAutomatic
Application.ScreenUpdating = True
MsgBox Format(Timer - dStart, "0.000")
End Sub
Autofilter is your best friend here if and when your data grows.
Copy Filtered Data
In this solution, it is assumed that you always want to start your resulting data in a given cell (dFirst) removing the previous contents.
Option Explicit
Sub CopyData()
Const sFirst As String = "A3"
Dim sCols As Variant: sCols = Array(20, 21)
Dim sCriteria As Variant: sCriteria = Array("Yes", "Yes")
Dim dFirst As Variant: dFirst = Array("A3", "A3")
Dim AutoFitColumns As Variant: AutoFitColumns = Array(True, True)
Dim sws As Worksheet: Set sws = Sheet1
Dim dws As Variant: dws = Array(Sheet2, Sheet3)
Dim srg As Range: Set srg = RefRange(sws.Range(sFirst))
If srg Is Nothing Then Exit Sub
Dim dData As Variant
Dim n As Long
For n = LBound(dws) To UBound(dws)
dData = GetCriteriaRows(srg, sCriteria(n), sCols(n))
If Not IsEmpty(dData) Then
WriteData dData, dws(n).Range(dFirst(n)), AutoFitColumns(n)
End If
Next n
End Sub
' Creates a reference to the range from a given first cell (range)
' to the cell at the intersection of the last non-empty row
' and the last non-empty column.
Function RefRange( _
ByVal FirstCellRange As Range) _
As Range
If FirstCellRange Is Nothing Then Exit Function
With FirstCellRange.Cells(1)
Dim rg As Range
Set rg = .Resize(.Worksheet.Rows.Count - .Row + 1, _
.Worksheet.Columns.Count - .Column + 1)
Dim lCell As Range
Set lCell = rg.Find("*", , xlFormulas, , xlByRows, xlPrevious)
If lCell Is Nothing Then Exit Function
Dim lRow As Long: lRow = lCell.Row
Set lCell = rg.Find("*", , , , xlByColumns, xlPrevious)
Set RefRange = .Resize(lRow - .Row + 1, lCell.Column - .Column + 1)
End With
End Function
' Returns a 2D one-based array containing the rows with matching criteria
' in a given column.
Function GetCriteriaRows( _
ByVal srg As Range, _
ByVal CriteriaString As String, _
Optional ByVal CriteriaColumn As Long = 1) _
As Variant
If srg Is Nothing Then Exit Function
If Len(CriteriaString) = 0 Then Exit Function
If CriteriaColumn < 0 Then Exit Function
Dim drCount As Long: drCount = Application.CountIf(srg, CriteriaString)
If drCount = 0 Then Exit Function
Dim srCount As Long: srCount = srg.Rows.Count
Dim cCount As Long: cCount = srg.Columns.Count
If CriteriaColumn > cCount Then Exit Function
Dim sData As Variant
If srCount + cCount = 2 Then
ReDim sData(1 To 1, 1 To 1): sData(1, 1) = srg.Value
Else
sData = srg.Value
End If
Dim dData As Variant: ReDim dData(1 To drCount, 1 To cCount)
Dim cValue As Variant
Dim r As Long, c As Long, n As Long
For r = 1 To srCount
cValue = CStr(sData(r, CriteriaColumn))
If cValue = CriteriaString Then
n = n + 1
For c = 1 To cCount
dData(n, c) = sData(r, c)
Next c
End If
Next r
GetCriteriaRows = dData
End Function
' Writes the values from a 2D one-based array to a range.
Sub WriteData( _
ByVal Data As Variant, _
ByVal FirstCellRange As Range, _
Optional ByVal AutoFitColumns As Boolean = False)
If FirstCellRange Is Nothing Then Exit Sub
If IsEmpty(Data) Then Exit Sub
Dim srCount As Long: srCount = UBound(Data, 1)
Dim scCount As Long: scCount = UBound(Data, 2)
Dim DoesFit As Boolean
Dim DoesNotFitExactly As Boolean
With FirstCellRange.Cells(1)
If .Worksheet.Columns.Count - .Column + 1 >= scCount Then
Select Case .Worksheet.Rows.Count - .Row + 1
Case srCount
DoesFit = True
Case Is > srCount
DoesFit = True
DoesNotFitExactly = True
End Select
End If
If DoesFit Then
Dim drg As Range: Set drg = .Resize(srCount, scCount)
drg.Value = Data
If DoesNotFitExactly Then
drg.Resize(.Worksheet.Rows.Count - .Row - srCount + 1) _
.Offset(srCount).ClearContents
End If
If AutoFitColumns Then
drg.EntireColumn.AutoFit
End If
End If
End With
End Sub
' Returns a 2D one-based array containing the values of a range
' (Not used because it is incorporated in 'GetCriteriaRows').
Function GetRange( _
ByVal rg As Range) _
As Variant
If rg Is Nothing Then Exit Function
Dim Data As Variant
If rg.Rows.Count + rg.Columns.Count = 2 Then
ReDim Data(1 To 1, 1 To 1): Data(1, 1) = rg.Value
Else
Data = rg.Value
End If
GetRange = Data
End Function
If you don't want to consider autofilter option.
Option Explicit
Sub Copy_Data_On_Condition()
'_____________________________________________________________
Dim StartTime As Double
Dim SecondsElapsed As Double
StartTime = Timer
'_____________________________________________________________
Dim arr, findT As Range, findU As Range, arrStr As String, i As Long, j As Long
Dim LastRow As Long, ColT As Range, ColU As Range, k As Long, n As Long
LastRow = Sheet1.Cells(Sheet1.Rows.Count, 1).End(xlUp).Row
k = 3000
For j = 2 To LastRow Step WorksheetFunction.Min(LastRow, k)
'_____________________________________________________________
'Evaluate Column T for "Yes" and create range findT
Set ColT = Sheet1.Range("T" & j + 1 & ":T" & WorksheetFunction.Min(j + k, LastRow))
arr = Evaluate("Transpose(IF((" & ColT.Address & "=" & """YES""" & ")," & _
"""A""" & "& ROW(" & ColT.Address & ") &" & _
""":U""" & "& ROW(" & ColT.Address & "),""0""))")
arrStr = Replace(Join(arr, ","), ",0", "")
If Left(arrStr, 2) = "0," Then
arrStr = Right(arrStr, Len(arrStr) - 2)
End If
For n = 15 To Len(arrStr) - Len(Replace(arrStr, ",", "", , , vbTextCompare)) Step 15
arrStr = WorksheetFunction.Substitute(arrStr, ",", "|", n)
Next n
arr = Split(arrStr, "|")
For n = 0 To UBound(arr)
If findT Is Nothing Then
'arr = Split(arrStr, "|")
Set findT = Evaluate(arr(n))
Else
Set findT = Union(Evaluate(arr(n)), findT)
End If
Next n
Debug.Print findT.Cells.Count
'_____________________________________________________________
'Evaluate Column U for "Yes" and create range findU
Set ColU = Sheet1.Range("U" & j + 1 & ":U" & WorksheetFunction.Min(j + k, LastRow))
arr = Evaluate("Transpose(IF((" & ColU.Address & "=" & """YES""" & ")," & _
"""A""" & "& ROW(" & ColU.Address & ") &" & _
""":U""" & "& ROW(" & ColU.Address & "),""0""))")
arrStr = Replace(Join(arr, ","), ",0", "")
If Left(arrStr, 2) = "0," Then
arrStr = Right(arrStr, Len(arrStr) - 2)
End If
For n = 15 To Len(arrStr) - Len(Replace(arrStr, ",", "", , , vbTextCompare)) Step 15
arrStr = WorksheetFunction.Substitute(arrStr, ",", "|")
Next n
arr = Split(arrStr, "|")
For n = 0 To UBound(arr)
If findU Is Nothing Then
'arr = Split(arrStr, "|")
Set findU = Evaluate(arr(n))
Else
Set findU = Union(Evaluate(arr(n)), findU)
End If
Next n
Debug.Print findU.Cells.Count
'_____________________________________________________________
Next j
findT.Copy Sheet2.Range("A" & Sheet2.Rows.Count).End(xlUp).Offset(1)
findU.Copy Sheet3.Range("A" & Sheet3.Rows.Count).End(xlUp).Offset(1)
'_____________________________________________________________
SecondsElapsed = Round(Timer - StartTime, 2)
Debug.Print "This code ran successfully in " & SecondsElapsed & " seconds"
End Sub

Autofill every n rows

How can I autofill the entirety of column B based on column A but with n empty rows in between each letter?
Column A:
a
b
c
Column B:
a
...
...
b
...
...
c
I have tried the VBA code below:
Range("A1:A3").AutoFill Destination:=Range("A1:A10"), Type:=xlFillDefault
The code works with numbers but not when the cell references a formula (in this case, =A1, ...) as the code seems to reference the row the formula is, instead of the list in column A.
For example, the code inserts the formula a row after c in B7, however would insert =A7 instead of =A4 which would be the letter d.
Any help with this would be greatly appreciated.
To insert n row for each value in Column A, I will use offset to solve it, here is the solution and hope you find it useful:
Sub ty()
Dim count As Long, i As Long, nextrow As Long
count = Application.WorksheetFunction.CountA(Sheet1.Range("A:A"))
nextrow = 1
For i = 1 To count
Sheet1.Cells(nextrow, 2).Value = Sheet1.Cells(i, 1).Value
nextrow = Cells(nextrow, 2).Offset(3, 1).Row
Next
End Sub
Expected Output:
In order to preserve the formula into new cells, then you may need copy method` by change this part:
For i = 1 To count
Sheet1.Cells(i, 1).Copy Sheet1.Cells(nextrow, 2)
nextrow = nextrow + 3
Next
AutoFill Every n Rows
You run GetGappedColumnTEST. GetGappedColumn is being called by the GetGappedColumnTEST.
Adjust the values in the constants section and the workbook, and rename the Sub appropriately.
Option Explicit
Sub GetGappedColumnTEST()
Const sName As String = "Sheet1"
Const sFirst As String = "A1"
Const dName As String = "Sheet1"
Const dFirst As String = "B1"
Const dGap As Long = 2
Dim wb As Workbook: Set wb = ThisWorkbook ' workbook containing this code
Dim Data As Variant
Data = GetGappedColumn(wb.Worksheets(sName).Range(sFirst), dGap)
If IsEmpty(Data) Then Exit Sub
Dim drCount As Long: drCount = UBound(Data, 1)
With wb.Worksheets(dName).Range(dFirst)
.Resize(drCount).Value = Data
.Resize(.Worksheet.Rows.Count - .Row - drCount + 1) _
.Offset(drCount).ClearContents
End With
End Sub
Function GetGappedColumn( _
ByVal FirstCell As Range, _
Optional ByVal Gap As Long = 0) _
As Variant
Const ProcName As String = "GetGappedColumn"
On Error GoTo clearError
If FirstCell Is Nothing Then Exit Function
If Gap < 0 Then Exit Function
Dim srg As Range
With FirstCell.Cells(1)
Dim lCell As Range
Set lCell = .Resize(.Worksheet.Rows.Count - .Row + 1) _
.Find("*", , xlFormulas, , , xlPrevious)
If lCell Is Nothing Then Exit Function
Set srg = .Resize(lCell.Row - .Row + 1)
End With
Dim rCount As Long: rCount = srg.Rows.Count
Dim sData As Variant
If rCount = 1 Then
ReDim sData(1 To 1, 1 To 1): sData(1, 1) = srg.Value
Else
sData = srg.Value
End If
Dim dData As Variant: ReDim dData(1 To rCount + rCount * Gap - Gap, 1 To 1)
Dim d As Long: d = 1
Dim s As Long
For s = 1 To rCount - 1
dData(d, 1) = sData(s, 1)
d = d + 1 + Gap
Next s
dData(d, 1) = sData(s, 1)
GetGappedColumn = dData
ProcExit:
Exit Function
clearError:
Debug.Print "'" & ProcName & "': Unexpected Error!" & vbLf _
& " " & "Run-time error '" & Err.Number & "':" & vbLf _
& " " & Err.Description
Resume ProcExit
End Function

Resources