I have a table in Sheet1. I need to search in Sheet1 for terms in Sheet2-ColumnA.
The exclusion list in Sheet2-ColumnA does not match the cell contents in Sheet1, but is found within the cell contents (Ex: find "orange" in "yellow;orange" or "orange;yellow").
If that criteria is found, delete the row. If it doesn't find the criteria, continue on down the list until it reaches an empty cell.
I recorded one round of this, but I need to modify it to loop through the entire exclusion list until it reaches an empty cell in the exclusion list.
Sub ExclusionList()
'
' ExclusionList Macro
' Find terms from exclusion list and delete row
'
' Go to sheet2 and select first term in exclusion list
Sheets("Sheet2").Select
Range("A1").Select
' Copy cell contents and find in sheet 1
Application.CutCopyMode = False
Selection.Copy
Sheets("Sheet1").Select
Cells.Find(What:="orange", After:=ActiveCell, LookIn:=xlFormulas, LookAt _
:=xlPart, SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:= _
False, SearchFormat:=False).Activate
' Delete row if found
Application.CutCopyMode = False
Selection.EntireRow.Delete
End Sub
In this example, "orange" is the criteria in Sheet2 A1. If it is possible to skip the copy/paste and refer directly to the exclusion list in the Cells.Find() function it seems like that would clean up the code and be more efficient overall.
Try this.
Here is a useful resource on avoiding Select/Activate. This shortens code considerably and makes it more effective.
Sub ExclusionList()
Dim r As Range, rFind As Range
With Sheets("Sheet2")
For Each r In .Range("A1", .Range("A" & Rows.Count).End(xlUp)) 'loop through every cell in sheet2 column A
Set rFind = Sheets("Sheet1").Cells.Find(What:=r.Value, LookAt:=xlPart, MatchCase:=False, SearchFormat:=False)
If Not rFind Is Nothing Then 'check that value is found to avoid error on next line
rFind.EntireRow.Delete
End If
Next r
End With
End Sub
Related
I am trying to check whether Row 1 of my active sheet named "Exceptions" contains the text "Control Date" (two spaces) or "Control Date".
My code finds the condition false.
Dim a As Range
Dim exceptions As Worksheet
Set exceptions = ActiveWorkbook.Worksheets("Exceptions")
For Each c In Exceptions.Range("A1:Z1")
If c = "Control Date" Then
Cells.Find(What:="control date", After:=ActiveCell, LookIn:=xlFormulas, _
LookAt:=xlPart, SearchOrder:=xlByColumns, SearchDirection:=xlNext, _
MatchCase:=False, SearchFormat:=False).Activate
Else
Cells.Find(What:="Control Date", After:=ActiveCell, LookIn:=xlFormulas, _
LookAt:=xlPart, SearchOrder:=xlByColumns, SearchDirection:=xlNext, _
MatchCase:=False, SearchFormat:=False).Activate
End If
Next c
Example of a worksheet with two spaces in "Control Date"
How to write the condition
As far as checking if the cell value is "Control Date" with a single space or one with two spaces, there are two ways of going about it:
Use the like operator
The like operator makes it easy to compare a string to a basic pattern. In this example, using the wildcard character * (Zero or more characters) will return true regardless of how many spaces are between Control and Date.
If cell.Value2 Like "Control*Date" Then
' Do something with that cell
End If
Use the or operator
Using the or operator ok to use as well, although not a flexible and perhaps a bit more difficult to see what's going on for your specific example.
If cell.Value2 = "Control Date" Or cell.Value2 = "Control Date" Then
' Do something with that cell
End If
Worksheet Codename
Each worksheet has whats called a codename. This is a reference that can be called directly in the code to that specific worksheet by it's name.
To set this name, in the properties window update the name property
So instead of
Dim Exceptions As Worksheet
Set Exceptions = ActiveWorkbook.Worksheets("Exceptions")
For Each cell In Exceptions.Range("A1:Z1")
' Do something...
Next cell
you can call the worksheet reference directly
For Each cell In Exceptions.Range("A1:Z1")
' Do something...
Next cell
Putting it together
Instead of using c for your variable, I like to make my variables easier to read and follow so I used cell.
Also, instead of hard coding your header columns in range, you could loop the cells of the entire first row. This is option suggestion though.
Lastly, be more explicit in what property you are looking for in your Cell. In my example I use .value2 to show I am looking for the value of that cell.
Public Sub Demo()
Dim cell As Range
For Each cell In Exceptions.Rows(1).Cells
If cell.Value2 Like "Control*Date" Then
' Do something with that cell
End If
Next cell
End Sub
Why duplicate the data into a third column? Whenever you need the "combined" date, just go get it, but do not store it twice.
Option Explicit '<<-- always have this
Sub doFindControl()
Dim a As Range
Dim c As Range '<<-- add this
Dim colDate As Long, colNumber As Long, colBlank As Long '<<--add this
Dim exceptions As Worksheet
Set exceptions = ActiveWorkbook.Worksheets("Exceptions")
For Each c In exceptions.Range("A1:Z1")
' first find the 2 key columns
If InStr(c, "Control") > 0 Then
If InStr(c, "Date") > 0 Then
colDate = c.Column
ElseIf InStr(c, "Number") > 0 Then
colNumber = c.Column
End If
' second look for the first blank column for you to put results in
ElseIf c.Text = "" Then
colBlank = c.Column
Exit For ' stop looking after its found
End If
Next c
' now you have the 2 FROM columns, and the TO column
MsgBox (colDate & " " & colNumber & " " & colBlank)
' and you can loop thru all the rest of the rows doing combine
End Sub
Thank you for all the answers! Robert Todar's answer led me to my lightbulb moment, and I can't believe at how simple the answer was. All I had to do was change this code:
Cells.Find(What:="Control Date", After:=ActiveCell, LookIn:=xlFormulas, _
LookAt:=xlPart, SearchOrder:=xlByColumns, SearchDirection:=xlNext, _
MatchCase:=False, SearchFormat:=False).Activate
to:
Cells.Find(What:="Control*Date", After:=ActiveCell, LookIn:=xlFormulas, _
LookAt:=xlPart, SearchOrder:=xlByColumns, SearchDirection:=xlNext, _
MatchCase:=False, SearchFormat:=False).Activate
I have a workbook with 200+ Excel spreadsheets with the same structure. On these sheets the value for Theme is always in cell C2 and the value for Date is always in C7, but when it comes to Root_cause and Solutions they start from different rows.
I need to copy this information on the main sheet and append it:
Maybe it's a good idea to use the find function to find the word 'Root_cause', then choose one column to the right and drag down to copy all related rows?
Code:
Sub Protocol()
Dim wsheet As Worksheet
With ThisWorkbook.Sheets("Main")
For Each wsheet In ThisWorkbook.Sheets
If wsheet.Name <> "Main" Then
Set Date = .Cells(.Rows.Count, "A").End(xlUp).Offset(1, 0)
Set Theme = .Cells(.Rows.Count, "B").End(xlUp).Offset(1, 0)
Set Root_cause = .Cells(.Rows.Count, "C").End(xlUp).Offset(1, 0)
Set Solutions = .Cells(.Rows.Count, "D").End(xlUp).Offset(1, 0)
Date.Value = wsheet.Range("C7").Value
Theme.Value = wsheet.Range("C2").Value
#Then I need to use FIND function on each sheet, come to word 'Root_cause', choose all rows for Root_cause and Solutions, copy them and append on sheet "Main"
End If
Debug.Print wsheet.Name
Next wsheet
End With
End Sub
Here is a solution to use the find function and limit to the cells right below the "rootcause" label.
Sub SelectActualUsedRange()
Dim FirstCell As Range, LastCell As Range
Set LastCell = Cells(Cells.Find(What:="*", SearchOrder:=xlRows, _
SearchDirection:=xlPrevious, LookIn:=xlValues).Row, _
Cells.Find(What:="*", SearchOrder:=xlByColumns, _
SearchDirection:=xlPrevious, LookIn:=xlValues).Column)
Set FirstCell = Cells(Cells.Find(What:="root_cause", After:=LastCell, SearchOrder:=xlRows, _
SearchDirection:=xlNext, LookIn:=xlValues).Row, _
Cells.Find(What:="root_cause", After:=LastCell, SearchOrder:=xlByColumns, _
SearchDirection:=xlNext, LookIn:=xlValues).Column)
Range(FirstCell.Offset(1, 0), LastCell.Offset(0, -5)).Copy
End Sub
note I created two sheets one with the "rootcause" starting at row 13 and another starting at row 25. The rest is simply selecting the main sheet and pasting the "rootcause" values onto that sheet.
here is the image of the two separate sheets.
EDIT: notice that I'm only selecting the two columns in the middle from where "rootcause" is found all the way down to the last nonempty cell.
In Column L (only) I want to replace any instance of data with "True" regardless of what was originally in any of the Column L cells. The code I tried was:
With ActiveSheet
intLastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
Let strSelectRange = "L2" & ":" & "L" & intLastRow
Range(strSelectRange).Select
Cells.Replace What:="*", Replacement:="True", LookAt:=xlPart _
, SearchOrder:=xlByColumns, MatchCase:=False, SearchFormat:=False, _
ReplaceFormat:=False
End With
First, I used .Rows.Count, "A" because in that column every row has data so I know how many rows to go down in Column L. In Column L many cells will be blank.
When I run this, every cell in the entire worksheet that has anything in it, is changed to True, not just the data in Column L.
Another method I tried was:
Range("L2:L1200").Select
Selection.Replace What:="*", Replacement:="True", LookAt:=xlPart, _
SearchOrder:=xlByRows, MatchCase:=False, SearchFormat:=False, _
ReplaceFormat:=False
Range("A1").Select
What I don't like about this is that I picked L1200 as the number of rows just to be sure I'd search farther than the actual last row that can contain data. I'm worried that this method might cause some kind of problem at some point.
What I'd really like to know is what I'm doing wrong in the first code example.
Thanks for any help you can offer!!!
Search & Replace in Column
Use Option Explicit always, to quicker learn about the occurring
errors and to be forced to declare variables.
You should always declare your rows as Long.
When you use the With statement you use the dots on everything, even
on .Range and .Cells etc. The code might work in this case
(ActiveSheet) anyway, but it is incorrect.
Avoid the use of ActiveSheet, use the worksheet name.
Avoid the use of Select. There are many posts (articles) about this.
When ever you use Cells without anything behind it, it refers to all the
cells in the worksheet.
The first thing in the Replace function (Find function) is the range
where you're going to Replace (Find, Search). It can be a column, it
can be Cells or just a smaller range.
The Code
Sub SROneColumn()
Const cVntLRColumn As Variant = "A" ' Last Row Column Letter/Number
Const cVntCriteria As Variant = "L" ' Criteria Column Letter/Number
Const cLngFirstRow As Long = 2 ' First Row Number
Const cStrReplace As String = "True" ' Replace String
Dim lngLastRow As Long ' Last Row Number
Dim strSelectRange As String ' Select Range Address
With ActiveSheet
lngLastRow = .Cells(.Rows.Count, cVntLRColumn).End(xlUp).Row
strSelectRange = .Range(.Cells(cLngFirstRow, cVntCriteria), _
.Cells(lngLastRow, cVntCriteria)).Address
.Range(strSelectRange).Replace What:="*", Replacement:=cStrReplace, _
LookAt:=xlPart, SearchOrder:=xlByColumns, MatchCase:=False
End With
End Sub
An interesting way to use a worksheet without the use of an object variable:
Sub SRSheet()
Const cStrSheet As Variant = "Sheet1" ' Worksheet Name/Index
With ThisWorkbook.Worksheets(cStrSheet)
End With
End Sub
Range(strSelectRange).Select
selects a range (though best to avoid Select) but then your code does nothing with that selection because Cells is the entire sheet.
Maybe you want instead:
Range(strSelectRange).Replace What:="*", Replacement:="True", LookAt:=xlPart
I've seen several other questions similar to mine and I've tried several different solutions but I am still getting strange results. My code finds a value in another workbook in Column AA, then I want to copy that row from Column C to Column BC and paste in current workbook. All of the code works except copying from column C to BC. For some reason it starts copying the row from column AC. I've tried a standard range but I think it's relative from the active cell and I don't know if there is a way to do negative column letters so then I tried Offset and I tried .Cells but none select the correct range. Here is a couple of examples of the code I've tried:
Private Sub ComboBox1_Change()
Dim checknum As String
Dim chkrow As String
Dim Rng As Range
prfile1 = Worksheets("setup").Range("B10").Value
prfile2 = Worksheets("setup").Range("B7").Value
filepath = Worksheets("setup").Range("e10").Value
checknum = ComboBox1.Value
'Workbooks.Open filepath & prfile2
Windows(prfile2).Activate
Worksheets("MRegister").Select
With Worksheets("MRegister").Range("AA:AA")
Set Rng = .Find(What:=checknum, _
After:=.Cells(.Cells.Count), _
LookIn:=xlFormulas, _
LookAt:=xlWhole, _
SearchOrder:=xlByRows, _
SearchDirection:=xlNext, _
MatchCase:=False)
Rng.Select
.Range(.Cells(ActiveCell.Row, -24), .Cells(ActiveCell.Row, 28)).Select
Selection.Copy
End With
Windows(prfile1).Activate
Sheets("ReprintOld").Range("M203:BM203").Select
Selection.PasteSpecial Paste:=xlPasteValues, Operation:=xlNone, SkipBlanks _
:=False, Transpose:=False
Application.CutCopyMode = False
Windows(prfile2).Activate
Sheets("MRegister").Range("A1").Select
ActiveWorkbook.Saved = True
ActiveWorkbook.Close
Sheets("ReprintOld").Range("A1").Select
End Sub
for Offset:
.Range(ActiveCell.Offset(0, -24), ActiveCell.Offset(0, 28)).Select
for standard Range:
.Range("C" & ActiveCell.Row & ":BC" & ActiveCell.Row).Select
You would think all of these would work, but they all start the selection several columns to the right of the active cell.
The issue is, as has been mentioned by user3561813, the fact that you have a Range object on the end of your With statement. Perhaps the simplest solution would be to use:
Intersect(Rng.Entirerow, .Worksheet.Range("C:BC")).Copy
The issue is this line: .Range(.Cells(ActiveCell.Row, -24), .Cells(ActiveCell.Row, 28)).Select
Because the With statement references With Worksheets("MRegister").Range("AA:AA"), it's trying to find the .Range property of the Column "AA".
If you rewrite it to something like Worksheets("MRegister").Range(.Cells(ActiveCell.Row, -24), .Cells(ActiveCell.Row, 28)).Select, it should work.
How about something like this after the .Find:
Rng.offset(0,3-rng.column).resize(1,53).copy
Rng is a reference to the cell with the desired checknum, offset that zero rows and back to column C, then resize it to 1 row by 53 columns (C to BC) and copy it.
You should check that the find worked before the copy:
If not rng is nothing then
You don’t need the select
I'm still learning VB. I tried recording a VB script in excel 2010 that selects a name on the main sheet, then goes to another sheet and finds all the rows with that name, copies all the rows and returns to the main sheet and insert the copied cells below the selected name. The cells are pushed down. The code should repeat for the next name below where the copied cells were paste.
My recording failed to do all of the above. Do you have a suggestion?
Sub Macro5()
'
' Macro5 Macro
'
' Keyboard Shortcut: Ctrl+l
'
Selection.Copy
Sheets("Sheet1").Select
ActiveCell.Offset(2, 3).Range("A1").Select
Cells.Find(What:="Leeanne Hickmott", After:=ActiveCell, LookIn:= _
xlFormulas, LookAt:=xlPart, SearchOrder:=xlByRows, SearchDirection:= _
xlNext, MatchCase:=False, SearchFormat:=False).Activate
Cells.FindNext(After:=ActiveCell).Activate
ActiveCell.Rows("1:3").EntireRow.Select
ActiveCell.Offset(0, -7).Range("A1").Activate
Application.CutCopyMode = False
Selection.Copy
Sheets("Sheet4").Select
ActiveCell.Offset(1, 0).Rows("1:1").EntireRow.Select
Selection.Insert Shift:=xlDown
ActiveCell.Offset(3, 6).Range("A1").Select
End Sub
Excel's record macro process can't record the conditionals or loops you need to make that kind of macro work. It also can't record that you pasted a value into the find dialog, it just records that you want to search for "Leeanne Hickmott"
My first suggestion would be to use range variables to point to the important cells on each worksheet. For example...
Dim rngPasteHere as Range, rngCopyFrom as Range
set rngPasteHere = Selection
set rngCopyFrom = Sheets("Sheet1").Range("D1")
' find first row
set rngCopyFrom = rngCopyFrom.Cells.Find(What:=rngPasteHere.Value _
, After:=rngCopyFrom, LookIn:=xlFormulas _
, LookAt:=xlPart, SearchOrder:=xlByRows _
, SearchDirection:=xlNext, MatchCase:=False _
, SearchFormat:=False)
You will also need two loops, an outer loop that repeats the whole process for the next name and an inner loop that finds all the rows with the current name. I can't really get more specific than that without knowing what your data looks like.
Are the names sorted alphabetically?
Are all rows with the same name right next to each other or are they mixed with other names?
Are there just a few rows of each name or thousands?