I have a macro that consolidate the values on another sheet, and based on these values, it´s has to go back on the first sheet and delete. But the macro is deleting what I want to keep.
The sheet.
The Macro:
Sub DeleteOthers()
Dim r1 As Range, c As Range
Dim t As String
With Sheets("Sheet2")
Set r1 = .Range(.Cells(2, "H"), .Cells(Rows.Count, "H").End(xlUp))
End With
For Each c In r1
If c.Text = "<<<Keep Row" Then
Sheets("Sheet1").Select
t = c.Offset(0, -1)
Rows(t).ClearContents
End If
Next
End Sub
Change to:
If c.Text <> "<<<Keep Row" Then
#Vityata's answer gets right to the heart of the issue -- using <> instead of = is the chief issue here.
(Using #arcadeprecinct's recommendation of c.Value instead of c.Text is also a great idea.)
If you were to tackle this by hand in Excel, you probably wouldn't go line-by-line though -- right? You would use Excel's built-in filtering... and with VBA, you can get the same wonderful results in a hurry by using the Range object's built in AutoFilter method.
Option Explicit
Sub DeleteOthersWithAutoFilter()
Dim r1 As Range, c As Range
'Grab the full data range, not just column H
With Sheets("Sheet2")
Set r1 = .Range(.Cells(1, 1), .Cells(Rows.Count, "N").End(xlUp))
End With
With r1
'Apply the Autofilter method to column H in the range
'identifying any cells NOT containing "<<<Keep Row"
.AutoFilter Field:=8, _
Criteria1:="<><<<Keep Row"
'Clear the remaining, visible rows while keeping the headers
Set c = .Offset(1, 0).Resize(.Rows.Count - 1).SpecialCells(xlCellTypeVisible)
c.ClearContents
End With
'Clear the filter
Sheets("Sheet2").AutoFilterMode = False
End Sub
Using Range.AutoFilter instead of looping through individual rows can be very fast, especially as your data sets get larger.
You can learn more about the differences between the two strategies, looping through rows and Range.AutoFilter, here:
https://danwagner.co/how-to-delete-rows-with-range-autofilter/
Which includes a YouTube video benchmarking each in action.
Related
I am new to Excel Macros and VBA, and am facing the following problem:
(1) I have a data-set which has ~50,000 rows and 11 columns.
(2) I need to extract rows from the sheet, based on a certain keyword - which matches the strings present in a particular column.
(3) I have the following code from another stack overflow question:
Sub testIt()
Dim r As Long, endRow as Long, pasteRowIndex As Long
endRow = 10 ' of course it's best to retrieve the last used row number via a function
pasteRowIndex = 1
For r = 1 To endRow 'Loop through sheet1 and search for your criteria
If Cells(r, Columns("B").Column).Value = "YourCriteria" Then 'Found
'Copy the current row
Rows(r).Select
Selection.Copy
'Switch to the sheet where you want to paste it & paste
Sheets("Sheet2").Select
Rows(pasteRowIndex).Select
ActiveSheet.Paste
'Next time you find a match, it will be pasted in a new row
pasteRowIndex = pasteRowIndex + 1
'Switch back to your table & continue to search for your criteria
Sheets("Sheet1").Select
End If
Next r
End Sub
(4) This works perfectly fine when the cell of the column being searched has "YourCriteria" as the only entry.
(5) However, in my data I have strings which have the "YourCriteria" embedded in them
For Example: "YourCriteria" = "ball" and the cell(s) in a particular column contain "the dog plays with the ball" , "the ball is bad" etc.
How can I extract the rows containing 'YourCriteria" ? What modification to the code is needed ?
Thanks
To expand on Doug's answer,
If InStr(Cells(r, 2).Value, "YourCriteria")>0 Then 'Found
' ^ Column A=1, B=2, ...
Edit Change 2 to whatever column number you want to look in (C=3, D=4, ...). You can also use Columns("B").Column like you had it, if you're more comfortable with that.
I have found If InStr()>0 to be more reliable than If Instr() since InStr has lots of return-value options.
A general thought, to avoid future problems - rather than switching sheets, refer expressly to which sheet you mean. Example (not all code shown):
dim shSource as Sheet
set shSource = ActiveWorkbook.Sheets("Sheet1")
dim shDest as Sheet
set shDest = ActiveWorkbook.Sheets("Sheet2")
...
If InStr(shSource.Cells(r, 2).Value, "YourCriteria")>0 Then 'Found
shSource.Rows(r).Copy
shDest.Rows(pasteRowIndex).Select
shDest.Paste
There's a built in operator for this in VBA: Like. You can just replace the current test with this:
If Cells(r, Columns("B").Column).Value Like "*YourCriteria*" Then 'Found
InStr( [start], string, substring, [compare] )
Parameters or Arguments
start
Optional. It is the starting position for the search. If this parameter is omitted, the search will begin at position 1.
string
The string to search within.
substring
The substring that you want to find.
compare Optional. It is the type of comparison to perform. It can be one of the following values:
VBA Constant Value Explanation
vbUseCompareOption -1 Uses option compare
vbBinaryCompare 0 Binary comparison
vbTextCompare 1 Textual comparison
borrowed from http://www.techonthenet.com/excel/formulas/instr.php
The fastest way is to:
Apply a Filter to the data
Set a range variable = .SpecialCells(xlCellTypeVisible)
Use range.Copy Sheets("Sheet2").Range("A1") to copy the data straight to Sheet2
Sub DoIt()
Dim SearchRange As Range
Sheets("Sheet1").UsedRange.AutoFilter Field:=2, Criteria1:="=*Ball*", _
Operator:=xlAnd
Set SearchRange = Sheets("Sheet1").UsedRange.SpecialCells(xlCellTypeVisible)
If Not SearchRange Is Nothing Then
SearchRange.Copy Sheets("Sheet2").Range("A1")
End If
End Sub
I'm trying to write a small piece of VBA that will shade a column of excel cells based on RGB variables in adjacent columns. So I have a table of data (no headings) with 3 columns (R G and B) and x rows (amount will vary). What I would like is to colour a 4th column to the right of this table in a colour based on the 3 numbers to the left. Note that I plan to select the top left cell each time I perform this.
Below is the code I have used but I get an error message:
Error 438... Object doesnt support this property or method
pointing to the Set rng line as being the problem.
Any thoughts or help much appreicated
Sub RGBTest()
Dim rng As Range
Dim n As Integer
Set rng = ActiveSheet.ActiveCell.CurrentRegion
ActiveCell.Offset(0, 3).Activate
For n = 1 To rng.Rows.Count
ActiveCell.Interior.Color = RGB(rng.Cells(n, 1), rng.Cells(n, 2), rng.Cells(n, 3))
ActiveCell.Offset(1, 0).Activate
Next n
End Sub
So the line
Set rng = ActiveSheet.ActiveCell.CurrentRegion
causes the error Error 438... Object doesnt support this property or method.
That means either ActiveSheet doesn't support .ActiveCell or ActiveCell doesn't support .CurrentRegion.
ActiveCell is a range and CurrentRegion is a property of Range so that shouldn't be it.
However, ActiveCell is not a property of a worksheet but of Application. That makes sense since there is only one active cell per Excel instance, not per sheet. So instead of
Set rng = ActiveSheet.ActiveCell.CurrentRegion
just use
Set rng = ActiveCell.CurrentRegion
I have a list of query words that I am submitting to a database (Column A) to generate a list of coded matches (Columns F-H). Column F is the original search word (so there is an exact match somewhere in Column A), Column G contains the match, and Column H contains the code for the match. What I need to do is take the query word in Column F and find its partner in Column A. Then I need to take the corresponding match and its code and paste it next to the original search term in Column A (in Columns B&C).
My problem here is getting the information pasted in the correct cell since the copy to and paste from locations change every time -- The list of coded matches in Columns F-H does NOT contain all of the terms in Column A.
I've been searching the internet and I can't seem to figure out what exactly I need to change to allow the paste function to work.
I have attached an image of a simplified version of my spreadsheet and a annotated version of the code I have been working with.
Sub FindMatch()
LastRow = Cells(Rows.Count, 6).End(xlUp).Row
For i = 1 To LastRow
FindMe = Cells(i, 6).Value
Set FoundinList = Cells.Find(What:=FindMe, After:=ActiveCell, LookAt:=xlWhole)
If Not FoundinList Is Nothing Then
FoundinList.Select
ActiveCell.Offset(0, 1).Select
'At this point the cell I want the information pasted into is selected. Yay!
'Example: I am trying to find "abnormal digits" (F1) in Column A and paste
'G1:H1 into the appropriate cells in Columns B & C (In this case B15:C15)
'At this point in the code my cursor is on cell B15 - which is where I need it.
Range(Cells(i, 7), Cells(i, 8)).Copy
'This selects the appropriate range (G1:H1 in my example).
ActiveCell.Paste
'This is the problem string. I've tried naming the "ActiveCell" before initiating the copy
'string (ActiveCell.Name = "PasteHere") and then pasting into the named cell
'(Cells("PasteHere").Paste), but that gives me an invalid procedure call or argument on:
'Cells("PasteHere").Paste I've also tried pasting into a range:Range(Cells(PasteHere, 2)
', Cells(PasteHere, 3)).Paste -AND- using the formula that is created when you a record a
'macro (Application.CutCopyMode = False) but both of those give me an application
'/object-defined error.
End If
Next i
End sub
Thank you so much in advance for reading this post and helping me out.
My Spreadsheet
End Product
This vba uses the worksheet function vlookup.
Sub ahhn()
Dim ws As Worksheet
Dim cel As Range
Set ws = ActiveSheet
With ws
For Each cel In .Range(.Range("A1"), .Range("A1").End(xlDown))
cel.Offset(0, 1) = WorksheetFunction.IfError(Application.VLookup(cel, .Range("F:H"), 2, 0), "")
cel.Offset(0, 2) = WorksheetFunction.IfError(Application.VLookup(cel, .Range("F:H"), 3, 0), "")
Next
End With
End Sub
I am not sure if what I want to achieve is possible. So here it is:
I got workbook with 2 sheets:
First sheet cointains raw data with employees and trainings they did or did not take (they could not come to the the training). Sheets cointains few columns like: name, special ID (different for every employee), 2 blank columns, presence (yes/no), and few more...
Second sheet will create report based on range and presence criteria.
Technically it's something like that:
Report sheet has list of employees, they will be filtered using autofilter. Those filtered employees will be checked if they were on 14 categories of trainings. Categories differs with range (ranges are known; with time ranges will be added or adjusted to newly added trainings).
My problem:
Is it possible to create vba code that will check if employee was on certain trainings (countif in certain range with condition: not present = do not count) and paste the value to certain cells? If it is could you give some advice on how to do that? I am not asking for ready code.
Trying to make it work but I stuck at this. Error at line "if cells.find...".
Sub Check()
MyRange = Range("A1", Selection.End(xlDown))
For Each MyCell In MyRange
With Range("pp2dni2007")
If Cells.Find(MyCell.Value) Is Nothing Then
Else
If ActiveCell.Value = ActiveCell.Offset(0, 3).Value Then
MyCell.Offset(0, 6).Value = 1
Else
MyCell.Offset(0, 6).Value = 0
End If
End If
End With
Next
End Sub
2nd edit, earlier code did infinite loop. Now I think if-statements reference to wrong range but not sure how to deal with it.
Sub Check()
Dim MyRange As Range, MyCell As Variant
Range("A1").Select
Set MyRange = Range(Selection, Selection.End(xlDown)).Rows.SpecialCells(xlCellTypeVisible)
For Each MyCell In MyRange.Cells
With Range("pp2dni2007")
If .Cells.Find(MyCell.Value) Is Nothing Then
Else
If .Cells.Find(MyCell.Value).Value = .Cells.Find(MyCell.Value).Offset(0, 3).Value Then
MyCell.Offset(0, 6).Value = 1
Else
MyCell.Offset(0, 6).Value = 0
End If
End If
End With
Next
End Sub
Sample workbook: https://dl.dropboxusercontent.com/u/7421442/sample%20workbook%20(1).xls
Declare all your variables, e.g.,
Dim MyRange as Range, MyCell as Range.
Without declaration, MyCell is variant data type, and that is why you're getting an Object Required error. Then do:
For Each MyCell In MyRange.Cells
Inside the With block, you may want to use (note the . in front of Cells):
If .Cells.Find(MyCell.Value) Is Nothing Then
Further, you will probably need to revise what you're doing with ActiveCell, since this is never changing which cell is active, I'm not sure it would give you the expected results. It is always preferable to avoid relying on Active and Select methods (except for to use them as user-input). INstead, work with the range objects directly.
[This is in Excel 2007]
In other words, the loop will cycle through all the active cells in a one-column range (rngAddressName) and, if the cell in the range AND the cell directly to the left of it contain the string "#N/A", then it will delete that row.
Unfortunately, nothing I have tried has had any actual effect. Here is my best go at it:
i = 1
For counter = 1 To rngSC2A.Rows.Count
Contents = rngSC2A.Cells(i).Value
If Contents = "#N/A" Then
If rngAddressName.Cells(i).CellOffset(0, -1).Value = "#N/A" Then
rngAddressName.Cells(i).EntireRow.Delete
Else
End If
Else
i = i + 1
End If
Next
But this doesn't seem to find any rows with the conditions satisfied (even though such rows exist in the worksheet).
I think it might have something to do with the fact that I am looking in the Cell.Value, but I am not sure.
You can autofilter your range, delete any rows that meet your criteria, then turn the autofilter off. This is a much more efficient approach than looping.
The example below works on columns A and B in Sheet1. Modify the variables to reference the range and sheet in your workbook.
Sub DeleteDoubleNA()
Dim ws As Worksheet
Dim rng As Range
Dim lastRow As Long
Set ws = ThisWorkbook.Sheets("Sheet1")
lastRow = ws.Range("A" & ws.Rows.Count).End(xlUp).Row
Set rng = ws.Range("A1:B" & lastRow)
' filter and delete all but header row
With rng
.AutoFilter field:=1, Criteria1:="#N/A"
.AutoFilter field:=2, Criteria1:="#N/A"
.Offset(1, 0).SpecialCells(xlCellTypeVisible).EntireRow.Delete
End With
' turn off the filters
ws.AutoFilterMode = False
End Sub
This is a different take on on the excellent answer posted by #Jon Crowell.
If you use an Excel table, you can use the table's ListObject to get the data range which automatically excludes the header and footer rows.
This avoids the sometimes incorrect calculation search for a last row.
You also want to clear any pre-existing filters on the data so that you don't overlook any rows.
Dim myTable As Object
Set myTable = ActiveSheet.ListObjects(1) ' Works if worksheet contains only one table
' Clear pre-existing filters on the table
myTable.AutoFilter.ShowAllData
' Filter the table
With myTable.DataBodyRange
.AutoFilter field:=1, Criteria1:="#N/A"
.AutoFilter field:=2, Criteria1:="#N/A"
End With
' Delete visible cells in the filtered table
myTable.DataBodyRange.SpecialCells(xlCellTypeVisible).EntireRow.Delete
' Clear filters on the table
myTable.AutoFilter.ShowAllData
The (1) in ListObjects(1) is the first (in my case only) table in the worksheet. DataBodyRange refers to the data range of that table excluding header and footer rows.