Excel VBA: Copy a row if cells contain certain data - vba

I am relatively new to coding in general, but here goes:
I have a huge list of membershipdata which I am trying to organize. This is going to be done weekly as the data is variable, so I am trying to automate the work a bit.
My problem is, I want to copy an entire row of data if a specific cell contains a specific text.
I have been able to do so using this code:
Sub OK()
Dim c As Range
Dim j As Integer
Dim Source As Worksheet
Dim Target As Worksheet
Set Source = ActiveWorkbook.Worksheets("Status")
Set Target = ActiveWorkbook.Worksheets("OK")
j = 2
For Each c In Source.Range("F1:F300")
If c = "Yes" Then
Source.Rows(c.Row).Copy Target.Rows(j)
j = j + 1
End If
Next c
End Sub
However, I want to use multiple conditions i.e. I only want the row to be copied if both column E and I contains "Yes".
My initial guess was this, but it doesnt seem right:
For Each c In Source.Range("F1:F300") AND Source.Range("I1:I300")
How can i add a condition to my code? I have tried using "and", but cant get it right it seems.
Thank you in advance.

You cannot add another Range in the way you have to a loop. Instead loop the one range as you originally put and as the additional range you want to check matches on a row by row basis but differs in terms of column use OFFSET to test the column I value in the same row. As below:
If c = "Yes" And c.Offset(0, 3) = "Yes"
So all together:
Sub OK()
Dim c As Range
Dim j As Integer
Dim Source As Worksheet
Dim Target As Worksheet
Set Source = ActiveWorkbook.Worksheets("Status")
Set Target = ActiveWorkbook.Worksheets("OK")
j = 2
For Each c In Source.Range("F1:F300")
If c = "Yes" And c.Offset(0, 3) = "Yes" Then
Source.Rows(c.Row).Copy Target.Rows(j)
j = j + 1
End If
Next c
End Sub

Related

Loop through data validation list, copy and paste (variable number of cells) into another sheet, below each other

Hoping someone's able to kindly help me out with this!
I'm essentially trying to create a macro, which will loop through a list("A3") in one sheet("Dashboard"), and then copy the results (B3:B7) and paste into a second sheet ("PrintSheet", Column "A"), with all the results being pasted under each other.
So far, I've managed to come up with the following code, but for some reason, it only seems to copy and paste one row of results (B3, not B4,5,6 or 7).
Any help would be truly appreciated!
Sub SpitValues()
Dim dvCell As Range
Dim inputRange As Range
Dim c As Range
Dim i As Long
'Cell that contains data validation list
Set dvCell = Worksheets("Dashboard").Range("A3")
'Determine where validation comes from
Set inputRange = Evaluate(dvCell.Validation.Formula1)
i = 1
'Begin loop
Application.ScreenUpdating = False
For Each c In inputRange
dvCell = c.Value
Worksheets("PrintSheet").Cells(i, "A").Value = Worksheets("Dashboard").Range("B3:B7").Value
i = i + 1
Next c
Application.ScreenUpdating = True
End Sub
You can't directly assign the values from a multi-cell range to a single cell: both the source and destination must be the same size:
Worksheets("PrintSheet").Cells(i, "A").Resize(5, 1).Value = _
Worksheets("Dashboard").Range("B3:B7").Value

Excel VBA: How to find first empty row within a Table for a Loop routine?

I reformatted a range of Sheets("Records") in a workbook as a Table (named "RecordsTable") to make it easier to do INDEX(MATCH,MATCH) functions for generating reports.... but now I screwed up my looping routine for filling that range from the input on Sheets("FORM").
It used to be:
Set r = Sheets("Records").Range(A & Rows.Count).End(x1Up).Offset(1, 0)
i = 0
For Each c In Range("dataRange")
'dataRange is a list of cells to reference from the FORM input sheet
r.Offset(0, i).Value = Worksheets("FORM").Range(c)
i = i + 1
Next
However this code is now selecting the first row at the END of "RecordsTable" (row 501, as I defined 500 rows in my table) and inserting the data there.
I tried to change it to this:
Set r = Sheets("Records").ListObjects("RecordsTable").DataBodyRange("A" & Rows.Count).End(x1Up).Offset(1, 0)
i = 0
For Each c In Range("dataRange")
r.Offset(0, i).Value = Worksheets("FORM").Range(c)
i = i + 1
Next
But this code is still selecting row 501 and making that row part of "RecordsTable".
How can I properly Set "r" to = the first empty row in "RecordsTable"?
For reference, Column "A" in "RecordsTable" has the header [INV #]. Also, when I step into the "Set r = ..." line, Rows.Count is returning a value of 1million+ (ie, total rows on the sheet) - if I understand this correctly, I want it to return a value of 500 (ie, total rows in table) - is that correct?
EDIT
"dataRange" is a single column list of cell references (I do have them labeled in column B, as #chrisneilsen suggest:
A
J6
Y6
J8
J10
Y8
etc.
They are the cells on Sheets("FORM") that I need to pull data from and populate into my table, in the order indicated in "dataRange".
Assuming you really have a Table, adding data to a Table (ListObject) using it's properties and methods:
Sub Demo()
Dim lo As ListObject
Dim c As Range
Set lo = Worksheets("Records").ListObjects("RecordsTable")
For Each c In Sheets("V").Range("dataRange")
If Not lo.InsertRowRange Is Nothing Then
lo.InsertRowRange.Cells(1, 1) = Sheets("FORM").Range(c)
Else
lo.ListRows.Add.Range.Cells(1, 1) = Sheets("FORM").Range(c)
End If
Next
End Sub
Note: looping a range on sheet V and using that as a pointer to data on sheet FORM, copied from your answer - I'm assuming you know what you are doing here
Based on OP comment, adding data a single new row
Sub Demo()
Dim lo As ListObject
Dim c As Range, TableRange As Range
Dim i As Long
Set lo = Worksheetsheets("Records").ListObjects("RecordsTable")
If Not lo.InsertRowRange Is Nothing Then
Set TableRange = lo.InsertRowRange
Else
Set TableRange = lo.ListRows.Add.Range
End If
i = 1
For Each c In Sheets("V").Range("dataRange")
TableRange.Cells(1, i) = Sheets("FORM").Range(c)
i = i + 1
Next
End Sub
Note, this assumes that the order of the table columns is the same as the order of dataRange. It may be better to include table field names in dataRange to avoid any mismatch issues
As mentioned in updated OP, if column labels are in the next column, replace the For loop with this (and add Dim r as Range, col as long to declarations)
For Each c In Sheets("V").Range("dataRange")
If Not c = vbNullString Then
Set r = Worksheets("FORM").Range(c.Value)
col = lo.ListColumns(c.Offset(, 1).Value).Index
TableRange.Cells(1, col) = r.Value
End If
Next

Speed up Excel VBA search script

I need to search for duplicate values and mark them in an Excel spreadsheet. I have my data to verify in column D and the data where possible duplicates are in column K. I need to check for each row in column D all the rows in col. K.
This is my current script for this:
Sub MySub()
Dim ThisCell1 As Range
Dim ThisCell2 As Range
For Each ThisCell1 In Range("D1:D40000")
'This is the range of cells to check
For Each ThisCell2 In Range("K1:K40000")
'This is the range of cells to compare
If ThisCell1.Value = ThisCell2.Value Then
If ThisCell1.Value <> "" Then
ThisCell1.Interior.ColorIndex = 3
End If
Exit For
End If
Next ThisCell2
Next ThisCell1
End Sub
The problem with this is that it's VERY slow. I mean it takes hours to check the data which is not acceptable. Even when the range is set to 1:5000, it still takes 10-15 minutes to finish. Is there any way to make it faster?
A dictionary will be the fastest way to achieve what you are looking for. Don't forget to add a reference to the 'microsoft scripting runtime' in your project
Sub MySubFast()
Dim v1 As Variant
Dim dict As New Scripting.Dictionary
Dim c As Range
v1 = Range("D1:D40000").Value
For Each c In Range("K1:K40000")
If Not dict.Exists(c.Value) Then
dict.Add c.Value, c
End If
Next
Dim i As Long
For i = LBound(v1, 1) To UBound(v1, 1)
If v1(i, 1) <> "" Then
If dict.Exists(v1(i, 1)) Then
Range("D" & i).Interior.ColorIndex = 3
End If
End If
Next i
End Sub
note : this is an improvement of #Jeanno answer.
Use arrays instead of referencing objects (Ranges) way faster.
Sub MySubFast()
Dim v1 As Variant
Dim v2 As Variant
v1 = Range("D1:D40000").Value
v2 = Range("K1:K40000").Value
Dim i As Long, j As Long
For i = LBound(v1, 1) To UBound(v1, 1)
For j = LBound(v2, 1) To UBound(v2, 1)
If v1(i, 1) = v2(j, 1) Then
If v1(i, 1) <> "" Then
Range("D" & i).Interior.ColorIndex = 3
End If
Exit For
End If
Next j
Next i
End Sub
Aren't you just highlighting cells in column D if the value exists in column K? No need for VBA for this, just use conditional formatting.
Select column D (selecting the whole column is fine)
Add a conditional format using this formula: =COUNTIF($K:$K,$D1)>0
The conditional format will apply and update automatically as you change data in columns D and K, and it should be basically instant

Excel VBA for loop a Named List

I have a spreadsheet with a column of data day of the week and using a macro to execute a VBA. Column A is the day of the week and Column B is the name of the object. When I run the macro, it runs a For loop through a Named List and will populate the items in a calendar on another sheet. The macro works fine as long as I have the Named List in a fixed length (ie $L2:$A14) so if I add new data, I would need to fix the Named List.
Sub UpdateCalendar()
i = 2
Dim strRngName As String
lngLast = Sheets("Servers").Range("B" & Rows.Count).End(xlUp).Row
For Each c In Application.Range("ScheduledDates")
strRngName = c.Text
strUser = c.Offset(0, -1).Value
User = c.Offset(0, -10).Value
If (i > 45) Then
<code stuff>
i = i + 1
Next
End Sub
I tried switching line 5 to something like this:
For Each c In Sheets("Servers").Range("L" & Rows.Count).End(x1Up).Row
but it doesn't like that (I'm guessing it doesn't see it as a full array?). The problem with the way this executes is if the "ScheduledDates" field is blank, it will throw an error and stop the script, thus I'm using a fixed length in my Named List. Not sure if there's any way around this.
First, dim c as range, then update your code to:
For Each c In Sheets("Servers").Range("L2:L" & Sheets("Servers").cells(Rows.Count,"L").End(xlUp).Row).cells
or
dim c as range, lLastRow as long
lLastRow=Sheets("Servers").cells(Rows.Count,"L").End(xlUp).Row
For Each c In Sheets("Servers").Range("L2:L" & lLastRow).cells
You can also update the definition of your named range so it becomes a dynamic named range, either using an =offset( / counta structure, of by referencing a listObject
Assuming that column B always has an entry, I prefer this approach:
Sub UpdateCalendar()
Dim rng as Range
Dim strRngName As String
Set rng as Sheets("Servers").Range("B2")
While rng <> ""
strRngName = rng.Text
strUser = rng.Offset(0, -1).Value
'!!!Below line will cause an error in your code as B2 offset by -10 would be B-8!!!
User = rng.Offset(0, -10).Value
If (rng.Row > 45) Then
'<code stuff>
Set rng = rng.Offset(1)
Wend
End Sub
You can use your original code by making the named range dynamic.
For Example, entering the below formula in the 'Refers To' field of the named range selects a range from A2:C where is the row number of the last filled row.
=OFFSET(Sheet1!$A$1,1,0,COUNTA(Sheet1!$A:$A)-1,3)
(assuming data extends from col A to col C with headers in row1)

How to get the value of a range within a range

So I need to extract information from a sheet with only certain values. From about 550 rows down to 50 which are spread across the entire sheet.
So I used autofilter for that. Now I only see the rows which match to my criteria but how can I get the values of a specific range from?
This far I came:
I know that I have to use
RangeINamed.SpecialCells(xlCellTypeVisible)
to work with only the visible information.
It worked for getting the starting and last row
startRow = bulkbatchRange.SpecialCells(xlCellTypeVisible).row
endRow = startRow + bulkbatchRange.SpecialCells(xlCellTypeVisible).rows.Count
But now I need to get the value of a specific column, I want to use a For loop so I can loop through all visible rows.
So I tried to do
RangeINamed.SpecialCells(xlCellTypeVisible).range("U" & rowNumber).value
That didn't work it gave me nothing. Now I'm rather clueless so does someone maybe know how I get the value of that row in column U in RangeINamed?
Thank you
You can always retrieve the value in a specific cell like U10 with:
Range("U10").Value
whether the row is hidden or not.
EDIT#1:
Here is a little example that loops down thru column A of an AutoFiltered table. It looks for the third visible row (not including the header row):
Sub GoDownFilter()
Dim rLook As Range, r As Range
Set rLook = Intersect(ActiveSheet.UsedRange, Range("A:A").Cells.SpecialCells(xlCellTypeVisible))
rLook.Select
K = 0
For Each r In rLook
If K = 3 Then
r.Select
MsgBox "The third visible row has been selected"
Exit Sub
End If
K = K + 1
Next r
End Sub
I think you need to choose if you want to get a specific cell like:
Range("U10").Value
Or a relative cell using something like
RangeINamed.SpecialCells(xlCellTypeVisible)(2,3).Value
Or
RangeINamed.SpecialCells(xlCellTypeVisible)(2,3).Address 'To see if you are getting it right
EDIT:
A complete code to Filter and Iterate.
Sub Filter()
Dim tableRange As Range, var, actualRow As Integer, lastRow As Integer
Set tableRange = Range("PUT_THE_TABLE_RANGE_HERE")
' Filter
With tableRange
Call .AutoFilter(5, "SPECIFIC_FILTER")
End With
Set f = tableRange.SpecialCells(xlCellTypeVisible)
With tableRange
Call .AutoFilter(5)
End With
For Each var In f.Cells.Rows
actualRow = var.Row
If actualRow <> 1 Then
' Do something
End If
Next
End Sub