Delete rows based on header name - vba

I'm trying to delete a lot of rows in my Excel sheet.
My VBA is really simple:
Sub delNA()
lr = Cells(Rows.Count, "A").End(xlUp).Row 'find last row
For i = lr To 2 Step -1 'loop thru backwards, finish at 2 for headers
If Cells(i, "H").Text = "#N/A" Then Rows(i).EntireRow.Delete
Next i
End Sub
But my problem is that sometimes my headers are different. In this case the header BTEX (Sum) Is in H2, but sometimes that parameter is in G2 and sometimes it's in E2, so what I'm trying to do is make the VBA search for the header name, so instead of H the criteria is "BTEX (Sum)".
Is there a way to make the VBA run in the column where the value in row 2 is "BTEX (Sum)"

Give this a try:
Option Explicit
Sub delNA()
Const HEADER_TEXT As String = "BTEX (Sum)"
Dim thisSheet As Worksheet
Set thisSheet = ActiveSheet
With thisSheet
Dim myHeader As Range
Set myHeader = .Cells.Find(What:=HEADER_TEXT, after:=.Cells(1, 1), LookIn:=xlValues, LookAt:=xlWhole, SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:=False, SearchFormat:=False)
If Not myHeader Is Nothing Then 'i.e. we found the header text somewhere
Dim headerColumn As Long
headerColumn = myHeader.Column
Dim lastRow As Long
'Update here:
lastRow = .Cells(.Rows.Count, headerColumn).End(xlUp).Row 'find last row
Dim i As Long
For i = lastRow To 2 Step -1 'loop thru backwards, finish at 2 for headers
'Update here:
If IsError(.Cells(i, headerColumn).Value2) Then
.Rows(i).EntireRow.Delete
End If
Next i
Else
MsgBox ("Could not find a column for the header text " & HEADER_TEXT)
End If
End With
End Sub
It uses .Find() on the range to very quickly identify where your header column is and pops up an error message if it's not found (just in case)
NOTE: .Find() will use the current setting of anything in the Find dialog box that isn't explicitly set, and the next time you use the Find dialog box, the setting will be whatever they are set to by your code. i.e. the Find dialog box and the .Find() function share a common, persistent set of parameters.
It assigns a worksheet variable to the current worksheet, just to ensure that, if this should run a while, a bored user doesn't click on another worksheet and break stuff.
All worksheet rows explicitly reference thisSheet by using .Cells() and .Rows() (notice the leading .) because of the line: With thisSheet
It uses .Value2 instead of .text (See here for the reason.)
I declared Option Explicit at the beginning to ensure that all variables are declared before being used.
This is a good habit to be in as it will eliminate frustrating bugs from using MyText as a variable in one place and MyTxt as a variable somewhere else and not understanding why it isn't working.
You now have the framework to make a more generic function by converting the Const declaration to a parameter that is accepted by delNA() and you can use it for any header row instead of this fixed one.

#Mikkel Astrup you first just use an for loop to find the col index you are looking for, in this case col index of the cell in row 2 have value "BTEX (Sum)"
Dim lColumn,indexCol As Long
dim ws as worksheet
dim headerKey as string
set ws = thisworkbook.worksheets("Sheet1")
lColumn = ws.Cells(2, Columns.Count).End(xlToLeft).Column
headerKey = "BTEX (Sum)"
for indexCol = 1 to lColumn step 1
if ws.cells(2,indexCol).value = headerKey then
' your code ' indexCol is the index of col you are looking for'
exit for ' exit the loop now
end if
Next

Related

Fill Empty Blank Cells with value within a region horizontaly defined

I'm trying to fill blank cells in a certain region with 0. The reagion should be defined in the current workbook but in sheet2 (not the current sheet). Also the place where it is supposed to fill is between columns
BU:CQ in the current region (not all 100 000 000 lines). Just the number of lines that define the table between columns BU and CQ. I know the problem lies in defining the region... See the code below.
What is missing?
Sub FillEmptyBlankCellWithValue()
Dim cell As Range
Dim InputValue As String
On Error Resume Next
InputValue = "0"
For Each cell In ThisWorkbook.Sheets("Sheet2").Range(BU).CurrentRegion
'.Cells(Rows.Count, 2).End(xlUp).Row
If IsEmpty(cell) Then
cell.Value = InputValue
End If
Next
End Sub
I've this code that i'm positive that works! But i don't wnat selection! I want somthing that specifies the sheet and a fixed range.
Now my idea is to replace "selection" with the desired range. - In this case in particular the range should be 1 - between BU:CQ; 2 - starting at row 2; 3 - working the way down until last row (not empty = end of the table that goes from column A to DE)
Sub FillEmptyBlankCellWithValue()
Dim cell As Range
Dim InputValue As String
On Error Resume Next
For Each cell In Selection
If IsEmpty(cell) Then
cell.Value = "0"
End If
Next
End Sub'
PS: And I also need to specify the sheet, since the button that will execute the code will be in the same workbook but not in the same sheet.
Use SpecialsCells:
On Error Resume Next 'for the case the range would be all filled
With ws
Intersect(.UsedRange, .Range("BU:CQ")).SpecialCells(xlCellTypeBlanks).Value = 0
End With
On Error GoTo 0
MUCH faster than looping !
Try using cells() references, such as:
For i = cells(1,"BU").Column to cells(1,"CQ").Column
cells(1,i).value = "Moo"
Next i
In your current code you list Range(BU) which is not appropriate syntax. Note that Range() can be used for named ranges, e.g., Range("TheseCells"), but the actual cell references are written as Range("A1"), etc. For Cell(), you would use Cells(row,col).
Edit1
With if statement, with second loop:
Dim i as long, j as long, lr as long
lr = cells(rows.count,1).end(xlup).row
For i = 2 to lr 'assumes headers in row 1
For j = cells(1,"BU").Column to cells(1,"CQ").Column
If cells(i,j).value = "" then cells(i,j).value = "Moo"
Next j
Next i
First off, you should reference the worksheet you're working with using:
Set ws = Excel.Application.ThisWorkbook.Worksheets(MyWorksheetName)
Otherwise VBA is going to choose the worksheet for you, and it may or may not be the worksheet you want to work with.
And then use it to specify ranges on specific worksheets such as ws.Range or ws.Cells. This is a much better method for specifying which worksheet you're working on.
Now for your question:
I would reference the range using the following syntax:
Dim MyRange As Range
Set MyRange = ws.Range("BU:CQ")
I would iterate through the range like so:
Edit: I tested this and it works. Obviously you will want to change the range and worksheet reference; I assume you're competent enough to do this yourself. I didn't make a variable for my worksheet because another way to reference a worksheet is to use the worksheet's (Name) property in the property window, which you can set to whatever you want; this is a free, global variable.
Where I defined testWS in the properties window:
Public Sub test()
Dim MyRange As Range
Dim tblHeight As Long
Dim tblLength As Long
Dim offsetLen As Long
Dim i As Long
Dim j As Long
With testWS
'set this this to your "BU:CQ" range
Set MyRange = .Range("P:W")
'set this to "A:BU" to get the offset from A to BU
offsetLen = .Range("A:P").Columns.Count - 1
'set this to your "A" range
tblHeight = .Range("P" & .Rows.Count).End(xlUp).Row
tblLength = MyRange.Columns.Count
End With
'iterate through the number of rows
For i = 1 To tblHeight
'iterate through the number of columns
For j = 1 To tblLength
If IsEmpty(testWS.Cells(i, offsetLen + j).Value) Then
testWS.Cells(i, offsetLen + j).Value = 0
End If
Next
Next
End Sub
Before:
After (I stopped it early, so it didn't go through all the rows in the file):
If there's a better way to do this, then let me know.

How to avoid using select in VBA for variable cell ranges?

I have heard of the dislike for using .select in VBA for excel macros, but I am wondering how my particular goal can be achieved without its use? For example, say there is a cell(used as a header) with the value "Commodity". Beneath it, all cells need to have a VLookup function. However, on each and every iteration of the macro, the column will shift (as new columns are added) and new rows will be added (so that newly added rows will need to have the function added as well). How is it possible to consistently locate this Commodity column and find its lowest unfilled row? It is very simple to do using select:
Do Until ActiveCell.Value = "Commodity"
Activecell.offset(0,1).select
loop
Do Until ActiveCell.Value = ""
ActiveCell.offset(1,0).select
loop
Obviously, I would prefer to avoid using this type of syntax, but I do not know how to get around it. All answers I have seen regarding the avoidance of select appear to set, for example, rng = Cell(x,y) or something, but they are always known-location cells. I do not know how to do this without utilizing select to check cell values.
First find the column that your Sting is located, then count the rows beside it, set your range and enter the formula.
Sub FindColumn()
Dim f As Range, c As Integer
Dim LstRw As Long, rng As Range
Set f = Rows(1).Find(what:="Commodity", lookat:=xlWhole)
If Not f Is Nothing Then
c = f.Column
Else: MsgBox "Not Found"
Exit sub
End If
LstRw = Cells(Rows.Count, c - 1).End(xlUp).Row
Set rng = Range(Cells(2, c), Cells(LstRw, c))
rng = "My Formula"
End Sub
Here are two iterate rows to based on the ActiveCell.
Sub Examples()
Dim Target As Range
Dim x As Long
Set Target = ActiveCell
Do Until Target.Value = "Commodity"
Set Target = Target.Offset(0, 1)
Loop
Do Until ActiveCell.Offset(x, 1).Value = ""
x = x + 1
Loop
End Sub
Assuming the wanted header IS there, you can use this function:
Function FindLowestUnfilledCell(headerRow As Range, header As String) As Range
With headerRow.Find(What:=header, lookat:=xlWhole, LookIn:=xlValues, MatchCase:=False) '<--| look for header in passed row
Set FindLowestUnfilledCell = headerRow.Parent.Cells(headerRow.Parent.Rows.Count, .Column).End(xlUp)
End With
End Function
to be used by your main sub as follows
Sub main()
FindLowestUnfilledCell(Rows(1), "Commodity").Formula = "myformula"
End Sub
should the absence of the wanted header be handled, the same function gets a little longer like follows
Function FindLowestUnfilledCell(headerRow As Range, header As String) As Range
Dim r As Range
Set r = headerRow.Find(What:=header, lookat:=xlWhole, LookIn:=xlValues, MatchCase:=False) '<--| look for "Commodity" in row 1
If Not r Is Nothing Then Set FindLowestUnfilledCell = headerRow.Parent.Cells(headerRow.Parent.Rows.Count, r.Column).End(xlUp)
End Function
and its exploitation would consequently take into account the possibility of not founding the wanted header:
Sub main()
Dim lowestUnfilledRange As Range
Set lowestUnfilledRange = FindLowestUnfilledCell(Rows(1), "Commodity")
If Not lowestUnfilledRange Is Nothing Then lowestUnfilledRange.Formula = "myformula"
End Sub
I want to simplify the answer a bit. For example
Set r = ActiveCell
MsgBox r.Address ' $A$1
Columns("A").Insert ' insert column before the first column
MsgBox r.Address ' $B$1
so you can change your code to
Dim cell As Range ' optional
Set cell = ActiveCell
While cell = "Commodity"
Set cell = cell(, 2) ' similar to Set cell = cell.Resize(1,1).Offset(, 1)
Wend
While cell = ""
Set cell = cell(, 2)
Wend

How to debug VBA code using .Find and Offset?

I'm practising VBA and I need some help / correction for my code.
In this task I'm creating a search tool which looks up each worksheet for the selected value from a combobox. Each result is listed on the first page.
Problems:
In the code I defined the .Find method in to a range rFound. On each worksheet the searched value is at column D. I would like to copy the row from column B to E. I've commented an attempt how did I tried to select that range, with offset but I receive an error. Why and how to fix that?
When I want to paste (list) the results I want it to start from the 1st page 3rd row column K. After running the code it selects the right target but pastes nothing. How to fix this?
I've also made some attempts to copy the document header after each search result, but I commented them out, please ignore lines with getOwner.
Dim ws As Worksheet, OutputWs As Worksheet, wsLists As Worksheet
Dim rFound As Range ', getOwner As Range
Dim strName As String
Dim count As Long, LastRow As Long
Dim IsValueFound As Boolean
'Dim cboSelectName As ComboBox
Dim a As String
IsValueFound = False
Set OutputWs = Worksheets("Teszt") '---->change the sheet name as required
LastRow = OutputWs.Cells(Rows.count, "A").End(xlUp).Row
Set wsLists = Worksheets("Lists")
a = ComboBox1.Value
On Error Resume Next
strName = a
If strName = "" Then Exit Sub
For Each ws In Worksheets
If ws.Name <> "Output" Then
With ws.UsedRange
Set rFound = .Find(What:=strName, After:=.Cells(1, 1), LookIn:=xlValues, LookAt:=xlWhole)
If Not rFound Is Nothing Then
Application.Goto rFound, True
IsValueFound = True
'MsgBox rFound.Row
rFound.EntireRow.Copy
'Rfound keres - rFound.Range(rFound(Offset(-2,")),rFound.Offset(1,"")).Copy ' ---> This is a suggestion
OutputWs.Cells(LastRow + 2, 11).PasteSpecial xlPasteAll
'getOwner.Range(K2, R2).Copy ' attempt to copy the header for each search result
'getOwner.Cells(LastRow + 1, 6).Paste
Application.CutCopyMode = False
LastRow = LastRow + 1
End If
End With
End If
Next ws
On Error GoTo 0
If IsValueFound Then
OutputWs.Select
MsgBox "Search Complete!"
Else
MsgBox "Value not found"
End If
You are selection entire row but you are pasting it to the column K. If you copy entire row, you can only copy it to column A. That's why it is not working. So I suggest you to work on Offset part.
In Offset, first part is rows, second part is columns.
you can do something like that,
Dim sth as Range
set sth = .range(.rfound.offset(0,-2),.rfound.offset(0,1)).copy
But I am not sure of it. Not very good at that.

VBA Excel code not deleting "blank" cells

I have been using this code to define a variable of a range that contains data. I then tried to use it to clear a worksheet (minus the headers) but it keeps showing that the last row is A2, when there is "ghost" data leftover from a previous paste in range W4 for example.
Why isn't this code finding the true last row? Even after I run this, then go to Find Special > Last Row - it finds the last pasted blank range (which is "empty"). You can see the variable LastRow is always "1". In the mean time, I've just been setting the delete range to A2:W5000 because I know I'll never have that much data, but it would be nice to have VBA find the true last row for me and store in a variable.
Thank you,
Public Sub ClearSheet()
Dim LastRow As Long
Dim LastCol As Long
If WorksheetFunction.CountA(Cells) > 0 Then
LastRow = Cells.Find(What:="*", After:=[A1], SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
LastCol = Cells.Find(What:="*", After:=[A1], SearchOrder:=xlByColumns, SearchDirection:=xlPrevious).Column
MsgBox (LastRow)
MsgBox (LastCol)
Range("A2").Resize(LastRow, LastCol).EntireRow.Delete 'retains headers in A1:W1
Try the UsedRange function. UsedRange includes "ghost" cells, and you can manipulate it to delete unwanted rows, excluding the header, like so:
Sub RemoveUnwantedRows()
Dim wk As Worksheet
Set wk = ActiveSheet
Dim rng As Range
Set rng = wk.UsedRange
'Error handling incase there are no "Ghost Cells".
On Error Resume Next
Set rng = rng.Resize(rng.Rows.Count - 1, rng.Columns.Count)
Set rng = rng.Offset(1, 0)
rng.Rows.Delete
On Error GoTo 0
'Proof that only the headers are left, you can leave this code out if you want.
wk.UsedRange.Select
End Sub

Effective Looping Checkup VBA

Summary: My company has two different spreadsheets with many policies on each. They want me to match up policies by a policy ID and transfer all the old notes from the old spreadsheet to the new spreadsheet.
Reasoning: my issue is not with not understanding how to do this, but the BEST way to do this. Since joining StackOverflow I've been told things I should and shouldn't do. I've been told different times it is better to use a For Each loop instead of a simple Do loop. Also, I've been told I shouldn't use .Select heavily (but I do).
How I Would Normally Do It: I would normally just use a Do Loop and go through the data just selecting the data with .Find and using ActiveCell and when I wanted to interact with other Columns in that current row I would just use ActiveCell.Offset(). I tend to love .Select and use it all the time, however on this project I'm trying to push myself out of the box and maybe change some bad coding habits and start using what may be better.
Question: How would I go about doing the equivalent of an ActiveCell.Offset() when I'm using a For Each loop?
My Code So Far: **Questions/Criticisms welcome
Sub NoteTransfer()
transferNotes
End Sub
Function transferNotes()
Dim theColumn As Range
Dim fromSheet As Worksheet
Dim toSheet As Worksheet
Dim cell As Range
Dim lastRow As Integer
Set fromSheet = Sheets("NotesFrom")
Set toSheet = Sheets("NotesTo")
With fromSheet 'FINDING LAST ROW
lastRow = .Range("B" & .Rows.Count).End(xlUp).Row
End With
Set theColumn = fromSheet.Range("B5:B" & lastRow)
For Each cell In theColumn 'CODE FOR EACH CELL IN COLUMN
If cell.Text = "" Then
'do nothing
Else
With toSheet 'WANT TO FIND DATA ON THE toSheet
Cells.find(What:=cell.Text, After:=ActiveCell, LookIn:=xlFormulas, _
LookAt:=xlPart, SearchOrder:=xlByRows, SearchDirection:=xlNext, _
MatchCase:=False, SearchFormat:=False).Activate
End With
End If
Next cell
End Function
Example
Bottom of the sheet
First, your question:
Question: How would I go about doing the equivalent of an ActiveCell.Offset() when I'm using a For Each loop?
Doesn't make much sense given the code you posted. It's a very general question, and would need some context to better understand. It really depends on your loop. If you are looping a contiguous range of cells from the ActiveCell then you could say ...
For each cel in Range
myValue = ActiveCell.Offset(,i)
i = i + 1
Next
To get the column next to each cell in the loop. But in general I wouldn't call that great programming. Like I said, context is important.
As far as your code goes, see if this makes sense. I've edited and commented to help you a bit. Oh yeah, good job not using Select!
Sub transferNotes() '-> first no need for a function, because you are not returning anything...
'and no need to use a sub to call a sub here as you don't pass variables,
'and you don't have a process you are trying to run
Dim theColumn As Range, cell As Range '-> just a little cleaner, INMHO
Dim fromSheet As Worksheet, toSheet As Worksheet '-> just a little cleaner, INMHO
Dim lastRow As Integer
Set fromSheet = Sheets("NotesFrom")
Set toSheet = Sheets("NotesTo")
With fromSheet ' -> put everything you do in the "fromSheet" in your With block
lastRow = .Range("B" & .Rows.Count).End(xlUp).Row 'FINDING LAST ROW
Set theColumn = .Range("B5:B" & lastRow)
theColumn.AutoFilter 1, "<>"
Set theColumn = theColumn.SpecialCells(xlCellTypeVisible) '-> now you are only looping through the cells are that are not blank, so it's more efficient
For Each cell In theColumn
'-> use of ActiveCell.Offset(), it's not ActiveCell.Offset(), but it uses Offset
Dim myValue
myValue = cell.Offset(, 1) '-> gets the cell value in the column to the right of the code
'WANT TO FIND DATA ON THE toSheet
toSheet.Cells.Find(What:=cell.Text, After:=ActiveCell, LookIn:=xlFormulas, _
LookAt:=xlPart, SearchOrder:=xlByRows, SearchDirection:=xlNext, _
MatchCase:=False, SearchFormat:=False).Activate
Next cell
End With
End Sub
This is my suggestion so far.
Function transferNotes()
Dim SourceColumn As Range
Dim fromSheet As Worksheet
Dim toSheet As Worksheet
Dim cell As Range
Dim lastRow As Long '<--changed to Long
Set fromSheet = Sheets("NotesFrom")
Set toSheet = Sheets("NotesTo")
With fromSheet 'FINDING LAST ROW
lastRow = .Range("B" & .Rows.Count).End(xlUp).Row
End With
Set SourceColumn = fromSheet.Range("B5:B" & lastRow)
For Each cell In SourceColumn 'CODE FOR EACH CELL IN COLUMN
If cell.Value = "" Then 'the .Text property can
'make for some confusing errors.
'Try to avoid it.
'nothng to search for
Else
With toSheet 'WANT TO FIND DATA ON THE toSheet
Dim destRng As Range
Set destRng = .Range("A:A").Find(What:=cell.Value)
If Not destRng Is Nothing Then
.Cells(destRng.Row, <your mapped column destination>)
= fromSheet.Cells(cell.Row,<your mapped column source>)
' you can either repeat the above line for all of your non-contiguous
'sections of data you want to move from sheet to sheet
'(i.e. if the two sheets are not arranged the same)
'if the two sheets are aranged the same then change
'the .cells call to call a range and include
'the full width of columns
Else
'nothing was found
End If
End With
End If
Next cell
End Function