I have a range of data in excel which is uniform, Column A has a description, Column B has a unique ID and column C is blank. Then the next 3 columns have the same set of data. I am trying to get my vba loop to go down the two sets of data and compare the unique ID's, if there are differences it copies it to the range of data to a new sheet.
The issue is it is not just 6 columns of data its about a couple of hundred, so once the final row has been reached after checking the first range of data, the loop needs to move over 6 columns to begin the process again.
I am having some difficulty getting the loop to move across 6 columns once the finalrow has been reached
Sub finddata()
Dim s As Worksheet
Dim uniqueId As String
Dim finalrow As Long
Dim i As Long
Dim c As Long
Dim rngSearch As Range
Dim rngFound As Range
Dim finalcolumn As Long
Dim offset As Integer
Application.ScreenUpdating = True
uniqueId = Sheets("Data").Range("B2").Value
finalrow = Sheets("Data").Range("G100000").End(xlUp).Row
finalcolumn = Sheets("Data").Range("XFD1").End(xlToLeft).Column
offset = 3
Set s = Sheets("Data")
Set rngSearch = s.Range(s.Cells(2, 5), s.Cells(finalrow, 5))
Sheets("DataValidation").Range("A1:C100000").ClearContents
If i = finalrow GoTo 'guessing this is how to being to loop to move over columns
For i = 2 To finalrow
uniqueId = s.Cells(i, 2).Value
Set rngFound = rngSearch.Find(What:=uniqueId, LookIn:=xlValues, LookAt:=xlWhole)
If rngFound Is Nothing Then
s.Range(Cells(i, 1), Cells(i, 6)).Copy
Sheets("DataValidation").Range("A1048575").End(xlUp).offset(1, 0).PasteSpecial xlPasteFormulasAndNumberFormats
End If
Next i
MsgBox "Done"
End Sub
Try that one
For j = 1 to finalcolumn step 6
Set rngSearch = s.Range(s.Cells(2, j - 1 + 5), s.Cells(finalrow, j - 1 + 5))
For i = 2 To finalrow
uniqueId = s.Cells(i, 2 + j - 1).Value
Set rngFound = rngSearch.Find(What:=uniqueId, LookIn:=xlValues, LookAt:=xlWhole)
If rngFound Is Nothing Then
s.Range(Cells(i, 1 + j - 1), Cells(i, 6 + j - 1)).Copy
Sheets("DataValidation").Range("A1048575").End(xlUp). _
offset(1, 0).PasteSpecial xlPasteFormulasAndNumberFormats
End If
Next i
Next j
Hope it helps
Here is a short example of how you can iterate on uniform ranges of 3 columns, and moving right by offset of 6. You said your ranges are uniform so I hard-coded the initial value of rngSet.
In the For Each I'm printing the address of the cells but you can do your checks there. The While loop will end when it reaches an empty cell.
Dim rngSet As Range, rngRow As Range
Set rngSet = Range("A1:B6")
While Not IsEmpty(rngSet.Cells(1, 1).Value)
For Each rngRow In rngSet.Rows
Debug.Print rngRow.Cells(1, 1).Address
Next rngRow
Set rngSet = rngSet.Offset(0, 6)
Wend
Related
I have a workbook includes 2 sheets.
In each sheet, it has couple columns like Name(column A), State(column B) and ID (column C). But the rows' sort sequence of two sheets are both random.
According to IDs, I need to use VBA to compare the value of Name and State.
If they don't match, then highlight both of 2 cells in 2 sheets.
The result should be like this:
But my code below can only run for Column A if IDs have the same order sequence.
I understand that it can be much easier if I use conditional formatting to create a new rule or use vlookup or index and match function to compare. But I am asked to use VBA
Thank you!
Sub Test_Sheet()
Dim sheetOne As Worksheet
Dim sheetTwo As Worksheet
Dim lastRow As Long
Dim lastRow2 As Long
Dim thisRow As Long
Dim thisRow2 As Long
Dim lastCol As Long
Dim lastCol2 As Long
Dim thisCol As Long
Dim thisCol2 As Long
Dim foundRow As Range
Dim foundRow2 As Range
Dim lastFoundRow As Long
Dim lastFoundRow2 As Long
Dim searchRange As Range
Dim searchRange2 As Range
Dim isMatch As Boolean
Dim isMatch2 As Boolean
' Set up the sheets
Set sheetOne = Sheets("Sheet1")
Set sheetTwo = Sheets("Sheet2")
' Find the last row of the active sheet
lastRow = sheetOne.Cells(sheetOne.Rows.Count, "A").End(xlUp).Row
lastRow2 = sheetOne.Cells(sheetOne.Rows.Count, "B").End(xlUp).Row
' Set the search range on the other sheet
Set searchRange = sheetTwo.Range("A2:A" & sheetTwo.Cells(sheetTwo.Rows.Count, "A").End(xlUp).Row)
Set searchRange2 = sheetTwo.Range("B2:B" & sheetTwo.Cells(sheetTwo.Rows.Count, "B").End(xlUp).Row)
' Look at all rows
For thisRow = 1 To lastRow
' Find the last column on this row
lastCol = sheetOne.Cells(thisRow, sheetOne.Columns.Count).End(xlToLeft).Column
' Find the first match
Set foundRow = searchRange.Find(sheetOne.Cells(thisRow, "A").Value, searchRange(searchRange.Count), xlValues, xlWhole)
' Must find something to continue
Do While Not foundRow Is Nothing
' Remember the row we found it on
lastFoundRow = foundRow.Row
' Check the found row has the same number of columns
If sheetTwo.Cells(lastFoundRow, sheetTwo.Columns.Count).End(xlToLeft).Column = lastCol Then
' Assume it's a match
isMatch = True
' Look at all the column values
For thisCol = 1 To lastCol
' Compare the column values
If sheetTwo.Cells(lastFoundRow, thisCol).Value <> sheetOne.Cells(thisRow, thisCol).Value Then
' No match
isMatch = False
Exit For
End If
Next thisCol
' If it's still a match then highlight the row
If isMatch Then sheetOne.Range(sheetOne.Cells(thisRow, "A"), sheetOne.Cells(thisRow, lastCol)).Interior.ColorIndex = 3
End If
' Find the next match
Set foundRow = searchRange.Find(sheetOne.Cells(thisRow, "A").Value, foundRow, xlValues, xlWhole)
' Quit out when we wrap around
If foundRow.Row <= lastFoundRow Then Exit Do
Loop
Next thisRow
For thisRow2 = 1 To lastRow2
lastCol2 = sheetOne.Cells(thisRow2, sheetOne.Columns.Count).End(xlToLeft).Column
Set foundRow2 = searchRange2.Find(sheetOne.Cells(thisRow2, "B").Value, searchRange2(searchRange2.Count), xlValues, xlWhole)
Do While Not foundRow2 Is Nothing
lastFoundRow2 = foundRow2.Row
If sheetTwo.Cells(lastFoundRow2, sheetTwo.Columns.Count).End(xlToLeft).Column = lastCol2 Then
isMatch2 = True
For thisCol2 = 1 To lastCol2
If sheetTwo.Cells(lastFoundRow2, thisCol2).Value <> sheetOne.Cells(thisRow2, thisCol2).Value Then
isMatch2 = False
Exit For
End If
Next thisCol2
If isMatch2 Then sheetOne.Range(sheetOne.Cells(thisRow2, "B"), sheetOne.Cells(thisRow2, lastCol2)).Interior.ColorIndex = 5
End If
Set foundRow2 = searchRange2.Find(sheetOne.Cells(thisRow2, "B").Value, foundRow2, xlValues, xlWhole)
If foundRow2.Row <= lastFoundRow2 Then Exit Do
Loop
Next thisRow2
End Sub
Please, try the next code. It uses arrays, for faster iteration, processing the matching in memory and Union ranges, coloring the cells interior at once, at the end. Modifying the interior of each cell consumes Excel resources and takes time:
Sub testCompareIDs()
Dim sheetOne As Worksheet, sheetTwo As Worksheet, lastRow1 As Long, lastRow2 As Long, i As Long, j As Long
Dim rng1 As Range, rng2 As Range, arr1, arr2, rngColA1 As Range, rngColA2 As Range, rngColB1 As Range, rngColB2 As Range
Set sheetOne = Sheets("Sheet1")
Set sheetTwo = Sheets("Sheet2")
lastRow1 = sheetOne.cells(sheetOne.rows.count, "C").End(xlUp).row
lastRow2 = sheetTwo.cells(sheetOne.rows.count, "C").End(xlUp).row
Set rng1 = sheetOne.Range("A2:C" & lastRow1)
Set rng2 = sheetTwo.Range("A2:C" & lastRow2)
arr1 = rng1.value: arr2 = rng2.value 'place ranges to be processed in arrays, for faster iteration
For i = 1 To UBound(arr1)
For j = 1 To UBound(arr2)
If arr1(i, 3) = arr2(j, 3) Then
If arr1(i, 1) <> arr2(j, 1) Then
If rngColA1 Is Nothing Then
Set rngColA1 = rng1.cells(i, 1)
Set rngColA2 = rng2.cells(j, 1)
Else
Set rngColA1 = Union(rngColA1, rng1.cells(i, 1))
Set rngColA2 = Union(rngColA2, rng2.cells(j, 1))
End If
End If
If arr1(i, 2) <> arr2(j, 2) Then
If rngColB1 Is Nothing Then
Set rngColB1 = rng1.cells(i, 2)
Set rngColB2 = rng2.cells(j, 2)
Else
Set rngColB1 = Union(rngColB1, rng1.cells(i, 2))
Set rngColB2 = Union(rngColB2, rng2.cells(j, 2))
End If
End If
Exit For 'exit iteration since the ID has been found
End If
Next j
Next i
If Not rngColA1 Is Nothing Then
rngColA1.Interior.ColorIndex = 3
rngColA2.Interior.ColorIndex = 3
End If
If Not rngColB1 Is Nothing Then
rngColB1.Interior.ColorIndex = 3
rngColB2.Interior.ColorIndex = 3
End If
End Sub
The strings compare is case sensitive. The code can be adapted to not be case sensitive (using Ucase for each compare line)
Please, send some feedback after testing it.
I have a bunch of non-contiguous data in a table across 108 rows and 15 columns. I'm trying to find the last used cell in every row and paste it to another worksheet.
For example: row 3 last data on column J, row 5 column O...row 38 column I, and so on
I am very new to writing VBA, any help would be great
EDITED
This is what i have:
Sub Selectionoflastcell()
Dim lRow As Long
Dim lCol As Long
Dim ccol As Range
Dim i As Long
With Worksheets("May-July Quarter").ListObjects("Table2").DataBodyRange
Worksheets("Sheet1").Cells(1, 2).ClearContents
lCol = .Cells(1, .Columns.Count).End(xlToRight).Column
Set colrange = .Range(.Cells(1, 1), .Cells(1, lCol))
i = 1
For Each ccol In colrange
lRow = .Cells(.Rows.Count, ccol.Column).End(xlUp).Row
Worksheets("Sheet1").Cells(i, 2).Value = .Cells(lRow, ccol.Column).Value
i = i + 1
Next ccol
End With
End Sub
But it is just giving me the last value of each column from the bottom, if I change it to xltoLeft, I get only the first row
Try this:
Sub Selectionoflastcell()
Dim i As Long, ws As Worksheet, rw As Range
Set ws = Worksheets("May-July Quarter")
i = 1
'loop over the table's rows
For Each rw in ws.ListObjects("Table2").DataBodyRange.Rows
Worksheets("Sheet1").Cells(i, 2).Value = _
ws.Cells(rw.Row, Columns.Count).End(xlToLeft).Value
i = i + 1
Next ccol
End Sub
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
The following code converts a table of values to a single column.
The problem is, with my table the number of rows in each column decreases by one for each successive column. Similar to the table shown below.
I am VERY new to writing code and only know the very basics. I copied a script found online to convert a range of values to a single column. The portion of code that I wrote to delete any blank cells is slowing the code tremendously. To convert around 250,000 points to a column is taking roughly 9 hours. I am hoping to reduce the processing time as this is a script I expect to use regularly.
Sub CombineColumns()
Application.ScreenUpdating = False
Application.EnableEvents = False
Dim rng As Range
Dim iCol As Long
Dim lastCell As Long
Dim K As Long
K = 484
'set K equal to the number of data points that created the range
Set rng = ActiveCell.CurrentRegion
lastCell = rng.Columns(1).Rows.count + 1
For iCol = 2 To rng.Columns.count
Range(Cells(1, iCol), Cells(rng.Columns(iCol).Rows.count, iCol)).Cut
ActiveSheet.Paste Destination:=Cells(lastCell, 1)
lastCell = lastCell + rng.Columns(iCol).Rows.count
Next iCol
Dim z As Long
Dim m As Long
z = K ^ 2
For Row = z To 1 Step -1
If Cells(Row, 1) = 0 Then
Range("A" & Row).Delete Shift:=xlUp
Application.StatusBar = "Progress: " & Row & " of z: " & Format((z - Row) / z, "Percent")
DoEvents
End If
Next
Application.StatusBar = False
Application.ScreenUpdating = True
Application.EnableEvents = True
End Sub
Sample Table Structure
Because I gave errant information on where this should be posted.
The following code will do what you want nearly instantly.
I used arrays to limit the number of interactions with the worksheet.
Sub foo5()
Dim ws As Worksheet
Dim rng() As Variant
Dim oarr() As Variant
Dim i&, j&, k&
Set ws = ThisWorkbook.Worksheets("Sheet19") 'Change to your sheet
With ws
rng = .Range("A1", .Cells(.Cells(.Rows.Count, 1).End(xlUp).Row, .Cells(1, .Columns.Count).End(xlToLeft))).Value
ReDim oarr(1 To Application.WorksheetFunction.CountA(rng), 1 To 1)
k = 1
For i = LBound(rng, 1) To UBound(rng, 1)
For j = LBound(rng, 2) To UBound(rng, 2)
If rng(i, j) <> "" Then
oarr(k, 1) = rng(i, j)
k = k + 1
End If
Next j
Next i
.Range("A1", .Cells(.Cells(.Rows.Count, 1).End(xlUp).Row, .Cells(1, .Columns.Count).End(xlToLeft))).Clear
.Range("A1").Resize(UBound(oarr), 1).Value = oarr
End With
End Sub
I'm trying to get my code to insert four rows every time it finds a difference in the cell below. If A5-55 = 1, A56-80 = 2, A81 - 100 = 3 I want the code to see that 56 isn't equal to 55 and insert 4 rows, then continue down the A column until there are no more values.
I keep getting an error from Excel,
can not complete task. Resources error
And then a runtime 1004 insert method of range class failed, and the debugger highlights the code for inserting rows
This is what my data looks like:
Worksheets("HR-Calc").Activate
For lRow = Cells(Cells.Rows.Count, "A").End(xlUp).Row To 6 Step -1
If Cells(lRow, "A") <> Cells(lRow - 1, "A") Then
Rows(lRow).EntireRow.Insert
Rows(lRow).EntireRow.Insert
Rows(lRow).EntireRow.Insert
Rows(lRow).EntireRow.Insert
End If
Next lRow
A neater way would be to use an autofilter on the table
(The code assumes that column A is a sorted integer ID - as seems to be the case from the image)
Sub InsertRowsBetweenIncrements()
Dim ws As Worksheet: Set ws = Worksheets("HR-Calc")
Dim HeaderRow As Long: HeaderRow = 4
Application.ScreenUpdating = False
Dim LastRow As Long: LastRow = ws.Columns(1).Find("*", _
SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
Dim LastCol As Long: LastCol = ws.Cells.Find("*", _
SearchOrder:=xlByColumns, SearchDirection:=xlPrevious).Column
Dim Tbl As Range: Set Tbl = ws.Range(Cells(HeaderRow, 1), Cells(LastRow, LastCol))
Dim i As Long, j As Long
For i = ws.Cells(LastRow, 1).Value To 1 Step -1
Tbl.AutoFilter Field:=1, Criteria1:=i
j = Tbl.SpecialCells(xlCellTypeVisible).SpecialCells(xlCellTypeLastCell).Row
Tbl.AutoFilter
If j <> HeaderRow And j < LastRow Then _
ws.Rows(j + 1 & ":" & j + 4).Insert Shift:=xlDown
Next i
Application.ScreenUpdating = True
End Sub
If you want a less-clunky was (as you mentioned), I would default to using arrays to increase speed. Give the code below a try and see what you think. This assumes your data starts in row 6 (if not, change the value of "offset" to the final row before the data in question starts). If you want to change how many rows you insert in the future, just change the value of rows_to_insert to the desired number.
Sub insertrows()
Dim check_col() As Variant
Dim rng As Range
Dim lcell As Range
Dim i As Long
Dim rows_to_insert As Long
Dim rows_added As Long
Dim offset As Long
Dim insert_cell As Long
Worksheets("HR-Calc").Activate
lrow = Cells(Cells.Rows.Count, "A").End(xlUp).Row
Set lcell = Cells(lrow, 1)
Set rng = Range("A6", lcell)
check_col = rng
rows_to_insert = 4
rows_added = 0
offset = 5
rows_added = 0
For i = 1 To (UBound(check_col, 1) - 1)
If check_col(i, 1) <> check_col(i + 1, 1) Then
check_col(i, 1) = i + rows_added + offset
rows_added = rows_added + rows_to_insert
Else: check_col(i, 1) = VBnllstring
End If
Next i
check_col(UBound(check_col, 1), 1) = vbNullString
rows_to_insert = rows_to_insert - 1
For i = 1 To UBound(check_col, 1)
If check_col(i, 1) <> vbNullString Then
insert_cell = check_col(i, 1) + 1
Range(Cells(insert_cell, 1), Cells(insert_cell + rows_to_insert, 1)).EntireRow.Select
Range(Cells(insert_cell, 1), Cells(insert_cell + rows_to_insert, 1)).EntireRow.Insert
End If
Next i
End Sub