vba: Compare columns and return mismatched values - excel

I'm new here and at VBA.
My question goes:
I have 3 sheets(1, 2 and 3). At sheet 1 I have column A(range A2-end) with data that I want to compare with column A(range A2-end) and D(range D2-end) on sheet 2. If a value in sheet 1 column A is not found on sheet 2column A and D, then it should list the mismatched value in sheet 3 starting at Range A2.
Here is what I have:
Sub Makro5()
Dim lastRowE As Integer
Dim lastRowF As Integer
Dim lastRowM As Integer
Dim foundTrue As Boolean
Application.ScreenUpdating = False
lastRowE = Sheets("1").Cells(Sheets("1").Rows.Count, "A2").End(xlUp).row
lastRowE = Sheets("2").Cells(Sheets("2").Rows.Count, "A2").End(xlUp).row
lastRowF = Sheets("2").Cells(Sheets("2").Rows.Count, "D2").End(xlUp).row
lastRowM = Sheets("3").Cells(Sheets("3").Rows.Count, "A2").End(xlUp).row
For i = 1 To lastRowE
foundTrue = False
For j = 1 To lastRowF
If Sheets("1").Cells(i, 1).value = Sheets("2").Cells(j, 1).value Then
foundTrue = True
and
If Sheets("1").Cells(i, 1).value = Sheets("2").Cells(j, 4).value Then
foundTrue = True
Exit For
End If
Next j
If Not foundTrue Then
Sheets("3").Rows(i).Copy Destination:= _
Sheets("3").Rows(lastRowM + 1)
lastRowM = lastRowM + 1
End If

This reads col A and D from Sheet2 in a dictionary
Then searches for values in col A of Sheet1 in the dictionary
Items not found are placed in Sheet3, starting at cell A2
Option Explicit
Public Sub FindMissing()
Dim ws1 As Worksheet, colA1 As Variant, r As Long, d1 As Object, d2 As Object
Dim ws2 As Worksheet, colA2 As Variant, colD2 As Variant, ws3 As Worksheet
Set d1 = CreateObject("Scripting.Dictionary")
Set d2 = CreateObject("Scripting.Dictionary")
Set ws1 = ThisWorkbook.Worksheets("Sheet1")
Set ws2 = ThisWorkbook.Worksheets("Sheet2")
Set ws3 = ThisWorkbook.Worksheets("Sheet3")
colA1 = ws1.Range("A2:A" & ws1.Cells(ws1.Rows.Count, 1).End(xlUp).Row) 'Sheet1.colA
colA2 = ws2.Range("A2:A" & ws2.Cells(ws2.Rows.Count, 1).End(xlUp).Row) 'Sheet2.colA
colD2 = ws2.Range("D2:D" & ws2.Cells(ws2.Rows.Count, 4).End(xlUp).Row) 'Sheet2.colD
If Not IsArray(colA1) Then MakeArray colA1 'Sheet1.colA contains only 1 row
If Not IsArray(colA2) Then MakeArray colA2 'Sheet2.colA contains only 1 row
If Not IsArray(colD2) Then MakeArray colD2 'Sheet2.colD contains only 1 row
For r = 1 To UBound(colA2)
d1(colA2(r, 1)) = vbNullString 'read Sheet2.ColA in dictionary d1.Keys
Next
For r = 1 To UBound(colD2)
d1(colD2(r, 1)) = vbNullString 'read Sheet2.ColD in dictionary d1.Keys
Next
For r = 1 To UBound(colA1) 'search vals from Sheet1.colA in dictionary d1
If Not d1.Exists(colA1(r, 1)) Then d2(colA1(r, 1)) = vbNullString
Next
ws3.Columns(1).Delete
If d2.Count > 0 Then ws3.Cells(2, 1).Resize(d2.Count, 1) = Application.Transpose(d2.Keys)
End Sub
Private Sub MakeArray(ByRef arr As Variant)
Dim tmp As Variant
tmp = arr
ReDim arr(1 To 1, 1 To 1)
arr(1, 1) = tmp
End Sub

Try using this code below...
Public Function Find_First(FindString As String, WithinRange As Range) As Boolean
Dim rng As Range
Find_First = False
If Trim(FindString) <> "" Then
With WithinRange
Set rng = .Find(What:=FindString, _
After:=.Cells(.Cells.Count), _
LookIn:=xlValues, _
LookAt:=xlWhole, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=False)
If Not rng Is Nothing Then
Find_First = True
End If
End With
End If
End Function

Related

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

Split cells by line break while keeping other data

I have multiple rows in a spreadsheet set up like the following:
TEST 1 Y N TEST_1 1234 Derived
TEST_2 56
I need to split the cells that have a line break while copying the remaining cells into the new row:
TEST 1 Y N TEST_1 1234 Derived
TEST 1 Y N TEST_2 56 Derived
I tested code by changing line breaks to commas (I don't know the VBA symbol for linebreak). The code I tried only works for one column E, not Column F:
Sub splitByCol()
Dim r As Range, i As Long, ar
Set r = Worksheets("Sheet1").Range("E999999:F999999").End(xlUp)
Do While r.row > 1
ar = Split(r.value, ",")
If UBound(ar) >= 0 Then r.value = ar(0)
For i = UBound(ar) To 1 Step -1
r.EntireRow.Copy
r.Offset(1).EntireRow.Insert
r.Offset(1).value = ar(i)
Next
Set r = r.Offset(-1)
Loop
End Sub
I just did a brief test, might not be perfect. If you have a ton of rows and columns this might be a tad slow aswell.
Dim rowiter As Long
Dim coliter As Long
Dim lastrow As Long
Dim lastcol As Long
Dim rowcount As Long
Dim rowadd As Boolean
With ActiveSheet
lastrow = .Cells(.Rows.Count, "A").End(xlUp).Row
lastcol = .Cells.Find(What:="*", after:=ActiveSheet.Cells(1, 1), LookIn:=xlFormulas, LookAt:= _
xlPart, SearchOrder:=xlByColumns, SearchDirection:=xlPrevious, MatchCase:=False).Column
rowcount = lastrow + 1
For rowiter = 1 To lastrow
rowadd = False
For coliter = 1 To lastcol
If InStr(1, .Cells(rowiter, coliter), vbLf) Then
.Cells(rowcount, coliter).Value = Split(.Cells(rowiter, coliter), vbLf)(1)
.Cells(rowiter, coliter).Value = Split(.Cells(rowiter, coliter), vbLf)(0)
rowadd = True
End If
Next
If rowadd = True Then
For coliter = 1 To lastcol
If .Cells(rowcount, coliter).Value = "" Or IsNull(.Cells(rowcount, coliter).Value) Then
.Cells(rowcount, coliter).Value = .Cells(rowiter, coliter).Value
End If
Next
rowcount = rowcount + 1
End If
rowadd = False
Next
.Range(Cells(1, 1), Cells(rowcount, lastcol)).Sort Key1:=Columns("A"), Order1:=xlDescending
End With
Actually you were almost there:
You need to split by vbLf instead of ","
You need to split column E and F into seperate arrays
So you end up with:
Option Explicit
Sub splitByCol()
Dim ws As Worksheet
Set ws = ThisWorkbook.Worksheets("Sheet1")
Dim CurrentCell As Range
Set CurrentCell = ws.Range("E" & ws.Rows.Count).End(xlUp)
Dim ArrE As Variant 'split array for column E
Dim ArrF As Variant 'split array for column F
Do While CurrentCell.Row > 1
ArrE = Split(CurrentCell.Value, vbLf)
ArrF = Split(CurrentCell.Offset(ColumnOffset:=1).Value, vbLf)
If UBound(ArrE) >= 0 Then CurrentCell.Value = ArrE(0)
If UBound(ArrF) >= 0 Then CurrentCell.Offset(ColumnOffset:=1).Value = ArrF(0)
Dim i As Long
For i = UBound(ArrE) To 1 Step -1
CurrentCell.EntireRow.Copy
CurrentCell.Offset(1).EntireRow.Insert
CurrentCell.Offset(1).Value = ArrE(i)
If UBound(ArrF) >= i Then
CurrentCell.Offset(1, 1).Value = ArrF(i)
Else
CurrentCell.Offset(1, 1).Value = vbNullString
End If
Next i
Set CurrentCell = CurrentCell.Offset(-1)
Loop
End Sub
Input
Output

VBA - Vlookup Multiple Columns and Fill to End of Range

I need to do a Vlookup of an ID on the source sheet to a table in the data sheet. When the Vlookup is done, it needs to return the cell values from 6 different columns.
Here I have a function to get the range:
Function find_Col(header As String) As Range
Dim aCell As Range, rng As Range, def_Header As Range
Dim col As Long, lRow As Long, defCol As Long
Dim colName As String, defColName As String
Dim y As Workbook
Dim ws1 As Worksheet
Set y = Workbooks("Template.xlsm")
Set ws1 = y.Sheets("Results")
With ws1
Set def_Header = Cells.Find(what:="ID", LookIn:=xlValues, lookat:=xlWhole, MatchCase:=False, SearchFormat:=False)
Set aCell = .Range("B2:Z2").Find(what:=header, LookIn:=xlValues, lookat:=xlWhole, MatchCase:=False, SearchFormat:=False)
If Not aCell Is Nothing Then
defCol = def_Header.Column
defColName = Split(.Cells(, defCol).Address, "$")(1)
col = aCell.Column
colName = Split(.Cells(, col).Address, "$")(1)
lRow = Range(defColName & .Rows.count).End(xlUp).Row - 1
Set myCol = Range(colName & "2")
'This is your range
Set find_Col = Range(myCol.Address & ":" & colName & lRow).Offset(1, 0)
'If not found
Else
MsgBox "Column Not Found"
End If
End With
End Function
Then in my sub, I select the range and do a Vlookup which fills this range:
Selection.FormulaR1C1 = "=VLOOKUP(RC[-4],myTable,2,FALSE)"
And this works great.
Then I needed to return more than just one column, so I ended up with the formula:
Selection.FormulaArray = "=VLOOKUP($C3,myTable,{2,3,4,5,6},FALSE)"
Source Sheet:
Data Sheet:
So, my function returns only the range for one column, which I think I can use in terms of getting a row count then using something like this:
Set myRng = find_Col("Product")
For currentRow = myRng.Rows.count To 1 Step -1
Selection.FormulaArray = "=VLOOKUP($C3,myTable,{2,3,4,5,6},FALSE)"
Next currentRow
Then perhaps instead of C3 it could look something like this:
C & currentRow --> Selection.FormulaArray = "=VLOOKUP($C & currentRow,myTable,{2,3,4,5,6},FALSE)"
But then I have the issue that only one cell is selected (G3) and from H-L is not. And I have no idea whether this is even a plausible effort.
Ideally of course, I would have cells G3:L3 selected and fill the formula down to the last row.
My brain is just fried from all the thinking and attempts.
So this should do the trick... I've explained every instance but if you need help understanding just ask:
Option Explicit
Sub FillData1()
Dim ws As Worksheet, wsData As Worksheet, arr As Variant, arrData As Variant
Dim DictHeaders As Scripting.Dictionary, DictIds As Scripting.Dictionary, DictDataHeaders As Scripting.Dictionary, _
DictDataIds As Scripting.Dictionary
Dim LastRow As Long, LastCol As Integer, i As Long, j As Integer
With Application
.ScreenUpdating = False
.EnableEvents = False
End With
With ThisWorkbook
Set ws = .Sheets("Results")
Set wsData = .Sheets("List")
End With
'Lets suppose your data always starts on row 2 in both sheets and column B will always have the max amount of rows filled
With ws 'filling the first array
LastRow = .Cells(.Rows.Count, 2).End(xlUp).Row
LastCol = .Cells(2, .Columns.Count).End(xlToLeft).Column
arr = .Range("B2", .Cells(LastRow, LastCol)).Value
End With
With wsData 'filling the data array
LastRow = .Cells(.Rows.Count, 4).End(xlUp).Row
LastCol = .Cells(2, .Columns.Count).End(xlToLeft).Column
arrData = .Range("A2", .Cells(LastRow, LastCol)).Value
End With
'Now lets put everything into Dictionaries so if the data moves columns or rows won't matter
Set DictHeaders = New Scripting.Dictionary
Set DictIds = New Scripting.Dictionary
For i = 1 To UBound(arr, 2) 'this will fill the headers positions on the main sheet
If Not DictHeaders.Exists(arr(1, i)) Then DictHeaders.Add arr(1, i), i
Next i
For i = 2 To UBound(arr, 1) 'this will fill the IDs positions on the main sheet
If Not DictIds.Exists(arr(i, DictHeaders("KW ID"))) Then DictIds.Add arr(i, 1), i
Next i
Set DictDataHeaders = New Scripting.Dictionary
Set DictDataIds = New Scripting.Dictionary
For i = 1 To UBound(arrData, 2) 'this will fill the headers positions on the data sheet
If Not DictDataHeaders.Exists(arrData(1, i)) Then DictDataHeaders.Add arrData(1, i), i
Next i
For i = 2 To UBound(arrData, 1) 'this will fill the IDs positions on the data sheet
If Not DictDataIds.Exists(arrData(i, DictDataHeaders("KW ID"))) Then DictDataIds.Add arrData(i, DictDataHeaders("KW ID")), i
Next i
'Finally will loop through the main array to fill it with the data from the data array
On Error Resume Next
For i = 2 To UBound(arr)
For j = 6 To UBound(arr, 2) 'I'm assuming you want to avoid the first columns which are hidden
arr(i, j) = arrData(DictDataIds(arr(i, 1)), DictDataHeaders(arr(1, j)))
Next j
Next i
On Error GoTo 0
With ws 'filling the first array
LastRow = .Cells(.Rows.Count, 2).End(xlUp).Row
LastCol = .Cells(2, .Columns.Count).End(xlToLeft).Column
.Range("B2", .Cells(LastRow, LastCol)).Value = arr
End With
With Application
.ScreenUpdating = True
.EnableEvents = True
End With
End Sub
I don't know if I got the true issue of your goal. However, since your Selection parts in your code should be avoid, why don't make something like the following?
Set myRng = find_Col("Product")
For currentRow = myRng.Rows.count To 1 Step -1
Range(Cells(currentRow, 5), Cells(currentRow, 9)).FormulaArray = "=VLOOKUP(RC3,myTable,{2,3,4,5,6},FALSE)"
Next currentRow

Cut-Paste copies only one row and not all the rows

I am trying to match all the cells of the "M" column in Sheet1 and Sheet3, and copy and delete all the rows from Sheet1 that contain any value from Sheet3's "M" column.
Also, I want the records to get copied into "Sheet2" (all records to be deleted).
However, it is deleting all the records but copying only the first row and not all the required rows.
Below is the code:
Sub DeleteRows()
Dim rng As Range
Dim r As Long
Dim lr1 As Long
Dim lr3 As Long
Dim str As Variant
Dim i As Long: i = 1
Application.ScreenUpdating = False
lr3 = Sheets("Sheet3").Cells(Rows.Count, "M").End(xlUp).Row
Set rng = Sheets("Sheet3").Range("M2:M" & lr3)
lr1 = Sheets("Sheet1").Cells(Rows.Count, "M").End(xlUp).Row
For r = lr1 To 2 Step -1
str = Sheets("Sheet1").Cells(r, "M")
If Application.WorksheetFunction.CountIf(rng, str) > 0 Then
Sheets("Sheet1").Range(Cells(r, "A"), Cells(r, "N")).Cut Sheets("Sheet2").Cells(i, "A")
Sheets("Sheet1").Range(Cells(r, "A"), Cells(r, "N")).Delete (xlShiftUp)
i = i + 1
End If
Next r
Application.ScreenUpdating = True
End Sub
Well here's your almost exact same code just added With blocks and .'s because that might've been the problem
Sub DeleteRows()
Dim rng As Range
Dim r As Long
Dim lr1 As Long
Dim lr3 As Long
Dim str As Variant
Dim i As Long: i = 1
Application.ScreenUpdating = False
With ThisWorkbook.Worksheets("Sheet3")
lr3 = .Cells(.Rows.Count, "M").End(xlUp).Row
Set rng = .Range("M2:M" & lr3)
End With
With ThisWorkbook.Worksheets("Sheet1")
lr1 = .Cells(.Rows.Count, "M").End(xlUp).Row
For r = lr1 To 2 Step -1
str = .Cells(r, "M").Value
If Application.WorksheetFunction.CountIf(rng, str) > 0 Then
Sheets("Sheet2").Range(Sheets("Sheet2").Cells(i, "A"), Sheets("Sheet2").Cells(i, "N")).Value = _
.Range(.Cells(r, "A"), .Cells(r, "N")).Value
.Range(.Cells(r, "A"), .Cells(r, "N")).Delete (xlShiftUp)
i = i + 1
End If
Next r
End With
Application.ScreenUpdating = True
End Sub

MS Excel macro deleting duplicate rows and sum their values

I'm very briefly familiar with vba and I cannot work out how to amend the following script to make it do what I expect.
Basically I have 5 column excel. Column A are the values I would like to sum, providing that B and C and D and E are unique as a row.
I found the following script, which does nearly what I need:
Option Explicit
Sub RedoDataset()
Dim LastCol As Long
Dim LastRowData As Long
Dim LastRow As Long
Dim Ctr As Long
Dim CompanyArr
Dim RowFoundArr
Dim SumArr
Dim Rng As Range
Dim SettingsArray(1 To 2) As Integer
On Error Resume Next
With Application
SettingsArray(1) = .Calculation
SettingsArray(2) = .ErrorCheckingOptions.BackgroundChecking
.Calculation = xlCalculationManual
.EnableEvents = False
.ErrorCheckingOptions.BackgroundChecking = False
.ScreenUpdating = False
End With
On Error GoTo 0
With ThisWorkbook
With .Sheets("Sheet1")
LastRowData = .Cells(Rows.Count, 1).End(xlUp).Row
LastCol = .Cells(1, Columns.Count).End(xlToLeft).Column
Set Rng = .Range(.Cells(1, 1), .Cells(1, LastCol))
.Columns(2).AdvancedFilter Action:=xlFilterCopy, _
CopyToRange:=.Cells(1, LastCol + 2), Unique:=True
LastRow = .Cells(Rows.Count, LastCol + 2).End(xlUp).Row
ReDim CompanyArr(1 To LastRow - 1)
ReDim RowFoundArr(1 To LastRow - 1)
ReDim SumArr(1 To LastRow - 1)
For Ctr = 1 To LastRow - 1
CompanyArr(Ctr) = .Cells(Ctr + 1, LastCol + 2)
RowFoundArr(Ctr) = Application.Match(CompanyArr(Ctr), .Columns(2), 0)
SumArr(Ctr) = Application.SumIf(.Columns(2), CompanyArr(Ctr), .Columns(1))
.Cells(RowFoundArr(Ctr), 1) = SumArr(Ctr)
Set Rng = Union(Rng, .Range(.Cells(RowFoundArr(Ctr), 1), _
.Cells(RowFoundArr(Ctr), LastCol)))
Next Ctr
.Columns(LastCol + 2).Delete
For Ctr = LastRowData To 2 Step -1
If IsError(Application.Match(Ctr, RowFoundArr, 0)) Then
.Rows(Ctr).Delete
End If
Next Ctr
End With
End With
On Error Resume Next
With Application
.Calculation = SettingsArray(1)
.ErrorCheckingOptions.BackgroundChecking = SettingsArray(2)
.EnableEvents = True
.ScreenUpdating = True
.ScreenUpdating = True
End With
On Error GoTo 0
End Sub
this sums values of column A leaving column B unique. How do I extend this so not only B is unique, but condition is - B and C and D and E are unique in combination as a row.
Basically where the whole row is unique comparing to other, but not necessary each column contain only unique values:
A B C D E
1 0.01 La Ba foo boo
2 0.03 La boo foo Ba
3 0.12 La foo Ba boo
4 1.05 Ba La foo boo
Try this code - it uses a different approach, that's more flexible:
Const cStrDelimiter As String = ";"
Sub Aggregate()
Dim dic As Object
Dim rng As Range
Dim strCompound As String
Dim varKey As Variant
Set dic = CreateObject("Scripting.Dictionary")
'Store all unique combinations in a dictionary
Set rng = Worksheets("Sheet1").Range("A1")
While rng <> ""
strCompound = fctStrCompound(rng.Offset(, 1).Resize(, 4))
dic(strCompound) = dic(strCompound) + rng.Value
Set rng = rng.Offset(1)
Wend
'Save all unique, aggregated elements in worksheet
Set rng = Worksheets("Sheet1").Range("G1")
For Each varKey In dic.Keys
rng = dic(varKey)
rng.Offset(, 1).Resize(, 4).Cells = Split(varKey, cStrDelimiter)
Set rng = rng.Offset(1)
Next
End Sub
Private Function fctStrCompound(rngSource As Range) As String
Dim strTemp As String
Dim rng As Range
For Each rng In rngSource.Cells
strTemp = strTemp & rng.Value & cStrDelimiter
Next
fctStrCompound = Left(strTemp, Len(strTemp) - Len(cStrDelimiter))
End Function

Resources