I need to copy the data in table 1 to a new sheet and format it like
on table 2.
I am using below function which does half of the job.
Function csvRange(myRange As Range)
Dim csvRangeOutput
Dim entry as variant
For Each entry In myRange
If Not IsEmpty(entry.Value) Then
csvRangeOutput = csvRangeOutput & entry.Value & ","
End If
Next
csvRange = Left(csvRangeOutput, Len(csvRangeOutput) - 1)
End Function
Join Data
JoinData is the recommended solution with some 'error handling'.
JoinDataShort is the same without any error handling.
Adjust the values in the constants section before using them.
Both of the mentioned procedures use the remaining three procedures.
The Code
Option Explicit
Sub joinData()
' Source
Const srcName As String = "Sheet1"
Const srcCols As String = "A:B"
Const srcLastRowCol As Long = 2
Const srcFirstRow As Long = 1
' Destination
Const dstName As String = "Sheet1"
Const dstFirst As String = "D1"
' Workbook
Dim wb As Workbook: Set wb = ThisWorkbook
' Source
Dim rng As Range
With wb.Worksheets(srcName)
Set rng = defRangeFrLrc(.Columns(srcCols), srcFirstRow, srcLastRowCol)
End With
If rng Is Nothing Then
Exit Sub
End If
' Data
Dim Data As Variant: Data = getDelimited(rng)
If IsEmpty(Data) Then
Exit Sub
End If
' Destination
Dim isWritten As Boolean
With wb.Worksheets(dstName)
isWritten = writeDataToRange(.Range(dstFirst), Data, True)
End With
' Information
If isWritten Then
MsgBox "Data transferred.", vbInformation, "Success"
Else
MsgBox "An error occurred.", vbCritical, "Fail"
End If
End Sub
' Compare with 'joinData' to understand what can go wrong.
Sub joinDataShort()
' Source
Const srcName As String = "Sheet1"
Const srcCols As String = "A:B"
Const srcLastRowCol As Long = 2
Const srcFirstRow As Long = 1
' Destination
Const dstName As String = "Sheet1"
Const dstFirst As String = "D1"
' Workbook
Dim wb As Workbook: Set wb = ThisWorkbook
' Source
Dim rng As Range
With wb.Worksheets(srcName)
Set rng = defRangeFrLrc(.Columns(srcCols), srcFirstRow, srcLastRowCol)
End With
' Data
Dim Data As Variant: Data = getDelimited(rng)
' Destination
writeDataToRange wb.Worksheets(dstName).Range(dstFirst), Data, True
End Sub
Function defRangeFrLrc( _
rng As Range, _
Optional ByVal FirstRow As Long = 1, _
Optional ByVal LastRowColumn As Long = 1) _
As Range
On Error GoTo clearError
If Not rng Is Nothing Then
Dim cel As Range
With rng
Set cel = .Columns(LastRowColumn) _
.Resize(.Rows.Count - FirstRow + 1).Offset(FirstRow - 1).Find( _
What:="*", _
LookIn:=xlFormulas, _
SearchDirection:=xlPrevious)
If Not cel Is Nothing Then
Set defRangeFrLrc = .Resize(cel.Row - FirstRow + 1) _
.Offset(FirstRow - 1)
End If
End With
End If
ProcExit:
Exit Function
clearError:
Resume ProcExit
End Function
Function getDelimited( _
rng As Range, _
Optional ByVal Delimiter As String = ",") _
As Variant
On Error GoTo clearError
' Arrays
With rng.Columns(1)
Dim rCount As Long
rCount = .Rows.Count - Application.CountBlank(.Offset)
Dim Data As Variant: Data = .Resize(, 2).Value
End With
Dim sCount As Long: sCount = UBound(Data, 1)
Dim Result() As String: ReDim Result(1 To rCount, 1 To 2)
' Headers
Result(1, 1) = Data(1, 1)
Result(1, 2) = Data(1, 2)
' Body
Dim arl As Object: Set arl = CreateObject("System.Collections.ArrayList")
Dim i As Long
Dim k As Long: k = 1
Dim cIndex As Long: cIndex = 1
Dim doAdd As Boolean
For i = 2 To sCount
If Len(Data(i, 1)) > 0 Then
If i > 2 Then
Result(cIndex, 2) = Join(arl.ToArray, Delimiter)
arl.Clear
End If
k = k + 1
cIndex = k
Result(k, 1) = Data(i, 1)
End If
arl.Add Data(i, 2)
Next i
Result(cIndex, 2) = Join(arl.ToArray, Delimiter)
' Result
getDelimited = Result
ProcExit:
Exit Function
clearError:
Resume ProcExit
End Function
Function writeDataToRange( _
FirstCellRange As Range, _
Data As Variant, _
Optional ByVal ClearToBottom As Boolean = False, _
Optional ByVal ClearWorksheet As Boolean = False) _
As Boolean
On Error GoTo clearError
With FirstCellRange.Resize(, UBound(Data, 2))
If ClearWorksheet Then
.Worksheet.Cells.Clear
Else
If ClearWorksheet Then
.Resize(.Worksheet.Rows.Count - .Row + 1).Clear
End If
End If
.Resize(UBound(Data, 1)).Value = Data
End With
writeDataToRange = True
ProcExit:
Exit Function
clearError:
Resume ProcExit
End Function
Here's a simple code which should work for you. See the comments and edit them to suit your needs.
Public Sub RearrangeData()
Dim rngSource As Range, rng As Range
Dim lngOutRow As Long
Application.ScreenUpdating = False
'\\ Set Source Range
Set rngSource = Range("A2:B" & Range("B" & Rows.Count).End(xlUp).Row)
lngOutRow = 1 '\\Define Output Row
For Each rng In rngSource.Columns(1).Cells
If Len(rng.Value) > 0 Then
lngOutRow = lngOutRow + 1
Range("C" & lngOutRow).Value = rng.Value '\\Define Output Column 1
End If
If Len(rng.Offset(0, 1).Value) > 0 Then
If Len(Range("D" & lngOutRow).Value) = 0 Then
Range("D" & lngOutRow).Value = rng.Offset(0, 1).Value '\\Define Output Column 2
Else
Range("D" & lngOutRow).Value = Range("D" & lngOutRow).Value & "," & rng.Offset(0, 1).Value '\\Define Output Column 2
End If
End If
Next rng
Application.ScreenUpdating = True
End Sub
Related
I created a log details sheet to track changes made in an excel spreadsheet, but my code is not returning the column/header name.
The column name should return the column where changes occurred. In this case, it would be employee status.
This is what my excel file looks like.
Here is my VBA Code
Dim lastRng
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
If ActiveSheet.Name <> "logdetails" Then
Application.EnableEvents = False
Sheets("logdetails").Range("A" & Rows.Count).End(xlUp).Offset(1, 0).Value = ActiveSheet.Name & "-" & Target.Address(0, 0)
Sheets("logdetails").Range("A" & Rows.Count).End(xlUp).Offset(0, 1).Value = Target.Address
Sheets("logdetails").Range("A" & Rows.Count).End(xlUp).Offset(0, 2).Value = lastRng
Sheets("logdetails").Range("A" & Rows.Count).End(xlUp).Offset(0, 3).Value = Target.Value
Sheets("logdetails").Range("A" & Rows.Count).End(xlUp).Offset(0, 4).Value = Environ("username")
Sheets("logdetails").Range("A" & Rows.Count).End(xlUp).Offset(0, 5).Value = Now
Sheets("logdetails").Columns("A:H").AutoFit
Application.EnableEvents = True
End If
End Sub
Private Sub Workbook_Open()
Set lastRng = ActiveCell
End Sub
Private Sub Workbook_SheetSelectionChange(ByVal Sh As Object, ByVal Target As Range)
lastRng = Target.Value
End Sub
Assuming that the column name is located in row 1:
Dim colName As String
colName = ActiveSheet.Cells(1, Target.Column)
Sheets("logdetails").Range("A" & Rows.Count).End(xlUp).Offset(0, 1).Value = colName
A Workbook Sheet Change: Logging Old and New (Application.Undo)
This is a different approach that first writes the Target formulas (if no formula, it's equivalent to values) to an array, then uses Application.Undo, then writes the old Target values to another array, then writes back the new values and populates the log worksheet using the information from the arrays.
It covers multiple cells when e.g. copy-pasting.
It partially covers multi-ranges (discontinuous, incontiguous) i.e. you can only write to those by using VBA e.g. Range("A1,C3").Value = "Test". But the issue is that in the case of using VBA, Application.Undo will not work so you cannot get the old data in the 3rd column. Even worse, it might work wrong e.g. if you have previously changed something in a worksheet that is not affected by the code (in this case, only the log worksheet).
Sheet Module e.g. Sheet1
Private Sub Workbook_SheetChange(ByVal Sh As Object, ByVal Target As Range)
Const dName As String = "LogDetails"
Const HeaderRow As Long = 1
Const dcCount As Long = 6
On Error GoTo ClearError
If Not TypeOf Sh Is Worksheet Then Exit Sub
Dim sName As String: sName = Sh.Name
If StrComp(sName, dName, vbTextCompare) = 0 Then Exit Sub
Dim uName As String: uName = Environ("USERNAME")
Dim tStamp As Date: tStamp = Now
Dim aData: aData = GetCellAddresses(Target)
Dim hData: hData = GetHeaders(Target, HeaderRow)
Dim drCount As Long: drCount = UBound(aData)
Dim nJag(): nJag = GetMultiRangeFormulas(Target)
Application.EnableEvents = False
Dim IsUndoClear As Boolean
On Error Resume Next
Application.Undo
IsUndoClear = Err.Number = 0
On Error GoTo ClearError
Dim oJag(): Dim arg As Range, a As Long
If IsUndoClear Then
oJag = GetMultiRangeFormulas(Target)
For Each arg In Target.Areas
a = a + 1
arg.Value = nJag(a)
Next arg
End If
Dim dData(): ReDim dData(1 To drCount, 1 To dcCount)
Dim r As Long, c As Long, dr As Long
For a = 1 To UBound(nJag)
For r = 1 To UBound(nJag(a), 1)
For c = 1 To UBound(nJag(a), 2)
dr = dr + 1
dData(dr, 1) = dName & "-" & aData(dr)
dData(dr, 2) = hData(dr)
If IsUndoClear Then dData(dr, 3) = oJag(a)(r, c)
dData(dr, 4) = nJag(a)(r, c)
dData(dr, 5) = uName
dData(dr, 6) = tStamp
Next c
Next r
Next a
Dim dws As Worksheet: Set dws = Sh.Parent.Sheets(dName)
Dim dfcell As Range
Set dfcell = dws.Cells(dws.Rows.Count, "A").End(xlUp).Offset(1)
Dim drg As Range: Set drg = dfcell.Resize(drCount, dcCount)
drg.Value = dData
drg.EntireColumn.AutoFit
ProcExit:
On Error Resume Next
If Not Application.EnableEvents Then Application.EnableEvents = True
On Error GoTo 0
Exit Sub
ClearError:
Debug.Print "Run-time error '" & Err.Number & "':" & Err.Description
Resume ProcExit
End Sub
Standard Module e.g. Module1
Function GetHeaders( _
ByVal mrg As Range, _
Optional ByVal HeaderRow As Long = 1) _
As Variant
Dim Data() As String: ReDim Data(1 To mrg.Cells.CountLarge)
Dim mCell As Range, c As Long
For Each mCell In mrg.Cells
c = c + 1
Data(c) = mCell.EntireColumn.Cells(HeaderRow).Value
Next mCell
GetHeaders = Data
End Function
Function GetCellAddresses( _
ByVal mrg As Range) _
As Variant
Dim Data() As String: ReDim Data(1 To mrg.Cells.CountLarge)
Dim mCell As Range, c As Long
For Each mCell In mrg.Cells
c = c + 1
Data(c) = mCell.Address(0, 0)
Next mCell
GetCellAddresses = Data
End Function
Function GetMultiRangeFormulas( _
ByVal mrg As Range) _
As Variant
Dim Jag(): ReDim Jag(1 To mrg.Areas.Count)
Dim arg As Range, Data(), a As Long
For Each arg In mrg.Areas
a = a + 1
Data = GetRangeFormulas(arg)
Jag(a) = Data
Next arg
GetMultiRangeFormulas = Jag
End Function
Function GetRangeFormulas( _
ByVal rg As Range) _
As Variant
Dim Data()
If rg.Rows.Count * rg.Columns.Count = 1 Then
ReDim Data(1 To 1, 1 To 1): Data(1, 1) = rg.Formula
Else
Data = rg.Formula
End If
GetRangeFormulas = Data
End Function
Good day. I have sheet with 2 columns A and B. I want to know if how many in the items in Column A and are not mapped to Column B and display it if what are those items. Thank you so much.
Return Not Matching Items
Excel
Plain
=UNIQUE(FILTER(A2:A21,ISNA(XMATCH(A2:A21,B2:B21))))
LET
=LET(vCol,A2:A21,lCol,B2:B21,fInc,ISNA(XMATCH(vCol,lCol)),
UNIQUE(FILTER(vCol,fInc)))
LET Variables
vCol - Value Column
lCol - Lookup Column
fInc - Filter Include
VBA
Sheet Module e.g. Sheet1
Private Sub Worksheet_Activate()
CheckMappings Me
End Sub
The rest goes into one or more standard modules e.g. Module1.
Simple Test
Sub CheckMappingsTEST()
Dim ws As Worksheet: Set ws = ThisWorkbook.Worksheets("Sheet1")
CheckMappings ws
End Sub
Main
Sub CheckMappings(ByVal ws As Worksheet)
Const SEARCH_FIRST_CELL As String = "A2"
Const MATCH_FIRST_CELL As String = "B2"
Dim srg As Range: Set srg = RefColumn(ws.Range(SEARCH_FIRST_CELL))
If srg Is Nothing Then Exit Sub
Dim mrg As Range: Set mrg = RefColumn(ws.Range(MATCH_FIRST_CELL))
If mrg Is Nothing Then Exit Sub
Dim sData(): sData = GetColumnRange(srg)
Dim sDict As Object: Set sDict = DictColumn(sData)
If sDict Is Nothing Then Exit Sub
Dim mData(): mData = GetColumnRange(mrg)
Dim mDict As Object: Set mDict = DictColumn(mData)
If mDict Is Nothing Then Exit Sub
RemoveDictFromDict sDict, mDict
If sDict.Count = 0 Then
MsgBox "No items to fix.", vbInformation
Else
MsgBox "The following " & IIf(sDict.Count = 1, "item is", _
sDict.Count & " items are") & " not mapped:" & vbLf & vbLf _
& Join(sDict.Keys, vbLf) & vbLf & vbLf & "Please fix.", vbCritical
End If
End Sub
The Help
Reference Non-Empty Column
Function RefColumn( _
ByVal FirstCell As Range) _
As Range
With FirstCell.Cells(1)
Dim cel As Range: Set cel = .Resize(.Worksheet.Rows.Count - .Row + 1) _
.Find("*", , xlFormulas, , , xlPrevious)
If Not cel Is Nothing Then Set RefColumn = .Resize(cel.Row - .Row + 1)
End With
End Function
Column To Array
Function GetColumnRange( _
ByVal rg As Range, _
Optional ByVal ColumnIndex As Long = 1) _
As Variant
With rg.Columns(ColumnIndex)
If .Rows.Count = 1 Then
ReDim Data(1 To 1, 1 To 1): Data(1, 1) = .Value
Else
Data = .Value
End If
End With
GetColumnRange = Data
End Function
Unique From Array to Dictionary
Function DictColumn( _
Data() As Variant, _
Optional ByVal ColumnIndex As Variant) _
As Object
Dim dict As Object: Set dict = CreateObject("Scripting.Dictionary")
dict.CompareMode = vbTextCompare ' case-insensitive
Dim c As Long
If IsMissing(ColumnIndex) Then
c = LBound(Data, 2) ' use first column index
Else
c = CLng(ColumnIndex)
End If
Dim Key As Variant
Dim r As Long
For r = LBound(Data, 1) To UBound(Data, 1)
Key = Data(r, c)
If Not IsError(Key) Then ' exclude error values
If Len(CStr(Key)) > 0 Then ' exclude blanks
dict(Key) = Empty
End If
End If
Next r
If dict.Count = 0 Then Exit Function ' only error values and blanks
Set DictColumn = dict
End Function
Remove Matches
Sub RemoveDictFromDict( _
ByRef RemoveDict As Object, _
ByVal MatchDict As Object)
Dim rkey As Variant
For Each rkey In RemoveDict.Keys
If MatchDict.Exists(rkey) Then RemoveDict.Remove rkey
Next rkey
End Sub
I am currently working on a code for data validation. The excel colors the cells that are entered incorrectly (orange for wrong range, red for wrong datatype). I first used message boxes to show the wrong values but when I have a lot of entries it is annoying to all click all of them away. My new idea would be to save all the errors as Strings in a dynamic array, which i can print out in a loop at the end and show all at once. Unfortunately, I am a beginner in vba and dont know if this idea is even possible to execute. How could I implement this idea?
Sub CheckColumns()
Dim rng As Range
Dim lCol As Long, lRow As Long
Dim DblLengthMin As Double
'Dim dynamicArray() As String
'Dim f As Integer
DblLengthMax = 20000
DblLengthMin = 5
lCol = Range("C2").End(xlToRight).Column
lRow = Range("C2").End(xlDown).Row
For Each rng In Range("C2", Cells(lRow, lCol))
If IsNumeric(rng) = False Then
rng.Interior.ColorIndex = 3
'Array Entry: "A number has to be entered " & "Row " & rng.Row & " Column " &
'rng.Column
End If
If IsNumeric(rng) And rng.Value > DblLengthMax Or rng.Value < DblLengthMin Then
rng.Interior.ColorIndex = 46
'ArrayEntry "Value in " & "Row " & rng.Row & " Column " & rng.Column & " is out of
'range. Check for unit (mm)"
End If
Next rng
' Print out an extra window that shows the number of mistakes made and a list of them
and their place in their worksheet
End Sub
Data example
Create a Report For Cells Not Matching Criteria
Option Explicit
Sub CheckColumns()
' Define constants.
Const sName As String = "Sheet1"
Const sfCol As Long = 3
Dim dHeaders() As Variant: dHeaders = VBA.Array( _
"Id", "Mistake", "Value", "Row", "Column", "Action Needed")
Const gteMin As Double = 2
Const lteMax As Double = 20000
Const rColor As Long = 26367 ' a kind of orange
Const cColor As Long = 255 ' red
' Write the source data to a 2D one-based array ('sData').
Dim swb As Workbook: Set swb = ThisWorkbook ' workbook containing this code
Dim sws As Worksheet: Set sws = swb.Worksheets(sName)
Dim srg As Range: Set srg = sws.Range("A1").CurrentRegion
Dim srOffset As Long: srOffset = 1
Dim srCount As Long: srCount = srg.Rows.Count - srOffset
Dim scOffset As Long: scOffset = sfCol - 1
Dim scCount As Long: scCount = srg.Columns.Count - scOffset
Dim sdrg As Range
Set sdrg = srg.Resize(srCount, scCount).Offset(1, sfCol - 1)
Dim sData() As Variant: sData = sdrg.Value
' Write the report data to 1D one-based arrays ('dDataRow')
' of a collection ('coll') and combine the cells containinig mistakes
' into ranges ('rrg','nrg').
Dim dcCount As Long: dcCount = UBound(dHeaders) + 1
Dim dDataRow() As Variant: ReDim dDataRow(1 To dcCount)
Dim coll As Collection: Set coll = New Collection
Dim rrg As Range ' not in range
Dim nrg As Range ' not a number
Dim sItem As Variant
Dim sRow As Long
Dim sCol As Long
Dim sr As Long
Dim sc As Long
Dim dr As Long
Dim IsNumber As Boolean
Dim InRange As Boolean
For sr = 1 To srCount
For sc = 1 To scCount
sItem = sData(sr, sc)
If VarType(sItem) = vbDouble Then
IsNumber = True
If sItem >= gteMin Then
If sItem <= lteMax Then
InRange = True
End If
End If
End If
If InRange Then
InRange = False
IsNumber = False
Else
dr = dr + 1
dDataRow(1) = dr
dDataRow(3) = sItem
sRow = sr + srOffset
dDataRow(4) = sRow
sCol = sc + scOffset
dDataRow(5) = sCol
If IsNumber Then
dDataRow(2) = "Not in range"
dDataRow(6) = "Check for unit (mm)"
Set rrg = RefCombinedRange(rrg, sws.Cells(sRow, sCol))
IsNumber = False
Else
dDataRow(2) = "Not a number"
dDataRow(6) = "Enter a number"
Set nrg = RefCombinedRange(nrg, sws.Cells(sRow, sCol))
End If
coll.Add dDataRow
End If
Next sc
Next sr
If coll.Count = 0 Then
MsgBox "No mistakes found.", vbExclamation
Exit Sub
End If
Application.ScreenUpdating = False
' Highlight cells.
srg.Interior.Color = xlNone
If Not rrg Is Nothing Then rrg.Interior.Color = rColor ' not in range
If Not nrg Is Nothing Then nrg.Interior.Color = cColor ' not a number
' Write the report data from the arrays in the collection
' to a 2D one-based array, the destination array ('dData').
Dim drCount As Long: drCount = dr + 1 ' include headers
Dim dData() As Variant: ReDim dData(1 To drCount, 1 To dcCount)
Dim dc As Long
' Write headers.
For dc = 1 To dcCount
dData(1, dc) = dHeaders(dc - 1)
Next dc
' Write data
dr = 1 ' skip headers
For Each sItem In coll
dr = dr + 1
For dc = 1 To dcCount
dData(dr, dc) = sItem(dc)
Next dc
Next sItem
' Write the data from the destination array to a new single-worksheet
' workbook, the destination workbook ('dwb').
Dim dwb As Workbook: Set dwb = Workbooks.Add(xlWBATWorksheet)
With dwb.Worksheets(1).Range("A1").Resize(, dcCount)
.Resize(drCount).Value = dData
.Font.Bold = True
.EntireColumn.AutoFit
End With
dwb.Saved = True ' just for easy closing
Application.ScreenUpdating = True
' Inform.
MsgBox "Columns checked.", vbInformation
End Sub
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Purpose: Creates a reference to a range combined from two ranges.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function RefCombinedRange( _
ByVal CombinedRange As Range, _
ByVal AddRange As Range) _
As Range
If CombinedRange Is Nothing Then
Set RefCombinedRange = AddRange
Else
Set RefCombinedRange = Union(CombinedRange, AddRange)
End If
End Function
I might recommend you to save all cells addresses with errors in one string variable with separator, and what is wrong in the other string variable. For example:
Dim strErrorAdress as String
Dim strError as String
For Each rng In Range("C2", Cells(lRow, lCol))
If IsNumeric(rng) = False Then
rng.Interior.ColorIndex = 3
If strErrorAdress = "" Then
strErrorAdress = rng.address & "/"
strError = "A number has to be entered" & "/"
Else
strErrorAdress =strErrorAdress & "/" & rng.address & "/"
strError = strError & "/" & "A number has to be entered" & "/"
End if
End If
If IsNumeric(rng) And rng.Value > DblLengthMax Or rng.Value < DblLengthMin Then
rng.Interior.ColorIndex = 46
If strErrorAdress = "" Then
strErrorAdress = rng.address & "/"
strError = "A number has to be entered" & "/"
Else
strErrorAdress =strErrorAdress & "/" & rng.address & "/"
strError = strError & "/" & "range. Check for unit (mm)" & "/"
End if
End If
Next rng
'Afterr all code delete last "/" in strings with
strErrorAdress = Left(strErrorAdress , Len(strErrorAdress ) - 1)
strError = Left(strError , Len(strError ) - 1)
'Then make arrays with split function
Dim arrSplitstrError() As String
Dim arrSplitstrErrorAdress() As String
arrSplitstrError = Split(strError , "/")
arrSplitstrErrorAdress = Split(strErrorAdress , "/")
'Now print errors like
dim counter as long
For counter = 0 to UBound(arrSplitstrError)
debug.print arrSplitstrErrorAdress(counter) & " - " & arrSplitstrError(counter) & vbNewLine
next counter
I'm not an expert, maybe there is a mistake in the code but the idea should be understood.
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
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