Excel VBA delete entire row if cell in column D is empty - vba

Can anyone walk me through how to write a script to delete the entire row if a cell in column D = "" on sheet 3 in range D13:D40.
Also, how to prevent the user from accidentally running the script again once those cells in the range are already deleted and other cells are now on the D13:D40 range?

Solution: This is working for me:
Sub DeleteRowsWithEmptyColumnDCell()
Dim rng As Range
Dim i As Long
Set rng = ThisWorkbook.ActiveSheet.Range("D13:D40")
With rng
' Loop through all cells of the range
' Loop backwards, hence the "Step -1"
For i = .Rows.Count To 1 Step -1
If .Item(i) = "" Then
' Since cell is empty, delete the whole row
.Item(i).EntireRow.Delete
End If
Next i
End With
End Sub
Explanation: Run a for loop through all cells in your Range in column D and delete the entire row if the cell value is empty. Important: When looping through rows and deleting some of them based on their content, you need to loop backwards, not forward. If you go forward and you delete a row, all subsequent rows get a different row number (-1). And if you have two empty cells next to each other, only the row of the first one will be deleted because the second one is moved one row up but the loop will continue at the next line.

No need for loops:
Sub SO()
Static alreadyRan As Integer
restart:
If Not CBool(alreadyRan) Then
With Sheets("Sheet3")
With .Range("D13:D40")
.AutoFilter 1, "="
With .SpecialCells(xlCellTypeVisible)
If .Areas.Count > 1 Then
.EntireRow.Delete
alreadyRan = alreadyRan + 1
End If
End With
End With
.AutoFilterMode = False
End With
Else
If MsgBox("procedure has already been run, do you wish to continue anyway?", vbYesNo) = vbYes Then
alreadyRan = 0
GoTo restart:
End If
End If
End Sub
Use AutoFilter to find blank cells, and then use SpecialCells to remove the results. Uses a Static variable to keep track of when the procedure has been run.

Here's my take on it. See the comments in the code for what happens along the way.
Sub deleterow()
' First declare the variables you are going to use in the sub
Dim i As Long, safety_net As Long
' Loop through the row-numbers you want to change.
For i = 13 To 40 Step 1
' While the value in the cell we are currently examining = "", we delete the row we are on
' To avoid an infinite loop, we add a "safety-net", to ensure that we never loop more than 100 times
While Worksheets("Sheet3").Range("D" & CStr(i)).Value = "" And safety_net < 100
' Delete the row of the current cell we are examining
Worksheets("Sheet3").Range("D" & CStr(i)).EntireRow.Delete
' Increase the loop-counter
safety_net = safety_net + 1
Wend
' Reset the loop-counter
safety_net = 0
' Move back to the top of the loop, incrementing i by the value specified in step. Default value is 1.
Next i
End Sub
To prevent a user from running the code by accident, I'd probably just add Option Private Module at the top of the module, and password-protect the VBA-project, but then again it's not that easy to run it by accident in the first place.

This code executes via a button on the sheet that, once run, removes the button from the worksheet so it cannot be run again.
Sub DeleteBlanks()
Dim rw As Integer, buttonID As String
buttonID = Application.Caller
For rw = 40 To 13 Step -1
If Range("D" & rw) = "" Then
Range("D" & rw).EntireRow.Delete
End If
Next rw
ActiveSheet.Buttons(buttonID).Delete
End Sub
You'll need to add a button to your spreadsheet and assign the macro to it.

There is no need for loops or filters to find the blank cells in the specified Range. The Range.SpecialCells property can be used to find any blank cells in the Range coupled with the Range.EntireRow property to delete these. To preserve the run state, the code adds a Comment to the first cell in the range. This will preserve the run state even if the Workbook is closed (assuming that it has been saved).
Sub DeleteEmpty()
Dim ws As Excel.Worksheet
Set ws = ActiveSheet ' change this as is appropriate
Dim sourceRange As Excel.Range
Set sourceRange = ws.Range("d13:d40")
Dim cmnt As Excel.Comment
Set cmnt = sourceRange.Cells(1, 1).Comment
If Not cmnt Is Nothing Then
If cmnt.Text = "Deleted" Then
If MsgBox("Do you wish to continue with delete?", vbYesNo, "Already deleted!") = vbNo Then
Exit Sub
End If
End If
End If
Dim deletedThese As Excel.Range
On Error Resume Next
' the next line will throw an error if no blanks cells found
' hence the 'Resume Next'
Set deletedThese = sourceRange.SpecialCells(xlCellTypeBlanks)
On Error GoTo 0
If Not deletedThese Is Nothing Then
deletedThese.EntireRow.Delete
End If
' for preserving run state
If cmnt Is Nothing Then Set cmnt = sourceRange.Cells(1, 1).AddComment
cmnt.Text "Deleted"
cmnt.Visible = False
End Sub

I've recently had to write something similar to this. I'm not sure that the code below is terribly professional, as it involves storing a value in cell J1 (obviously this can be changed), but it will do the job you require. I hope this helps:
Sub ColD()
Dim irow As long
Dim strCol As String
Sheets("sheet2").Activate
If Cells(1, 10) = "" Then
lrun = " Yesterday."
Else: lrun = Cells(1, 10)
End If
MsgBox "This script was last run: " & lrun & " Are you sure you wish to continue?", vbYesNo
If vbYes Then
For irow = 40 To 13 step -1
strCol = Cells(irow, 4).Value
If strCol = "" Then
Cells(irow, 4).EntireRow.Delete
End If
Next
lrun = Now()
Cells(1, 10) = lrun
Else: Exit Sub
End If
End Sub

Related

Exiting if statement without exiting sub

I have a VBA code that removes empty cells, allowing for data further down in the column to move up to the top. There are two things that I am looking to fix. One, I don't want to exit the sub, I want to exit the if statement. I've read that you can exit loops, whiles, fors, etc. But not ifs. I don't want to exit the sub because I have more code in my sub. Any suggestions?
I am also having issues adding another line in the code to run if there is already data at the top of the column.
Sub Remove_Empties ()
Dim Last as Long
Dim Mcol as Range
Last = Cells(Rows.Count, "AD").End(xlUp).Row
If Last = 1 Then Exit Sub 'no value only header in row 1
'otherwise
Set Mcol = Range("AD2:AD" & Last) 'contains any value
Application.ScreenUpdating = True
Mcol.SpecialCells(xlCellTypeBlanks).Delete xlUp
Application.ScreenUpdating = True
End Sub
I want it to be able to run the following three scenarios. The first contains data in the row below the header. The second has a couple of empty cells. The third has all empty cells. 1 should not change, 2 should change to match 1's format, and 3 should do nothing. 2 and 3 can be done with the code above, but not 1.
1 2 3
A
B
C A
D B
C
D
Perhaps something like this, noting the following:
You don't need two Application.ScreenUpdating = True lines. You can probably get rid of both.
You need to handle the possibility of no blank cells (column 1).
Sub Remove_Empties()
Dim Last As Long
Last = Cells(Rows.Count, "AD").End(xlUp).Row
If Last <> 1 Then
Dim Mcol As Range
Set Mcol = Range("AD2:AD" & Last) 'contains any value
Dim cellsToDelete As Range
On Error Resume Next
Set cellsToDelete = Mcol.SpecialCells(xlCellTypeBlanks)
On Error GoTo 0
If Not cellsToDelete Is Nothing Then
cellsToDelete.Delete shift:=xlUp
End If
End If
End Sub
Or slightly condensed:
Sub Remove_Empties()
Dim Last As Long
Last = Cells(Rows.Count, "AD").End(xlUp).Row
If Last <> 1 Then
On Error Resume Next
Range("AD2:AD" & Last).SpecialCells(xlCellTypeBlanks).Delete Shift:=xlUp
On Error GoTo 0
End If
End Sub
I will just add another answer that you can possibly use in your logic (code) moving forward. Using a simple GoTo statement will break out of an If. For example:
Sub UsingGoTo()
If Last = 1 Then
'do something
GoTo CheckComplete
Else
'if not maybe exit sub?
Exit Sub
End If
CheckComplete:
'continue with program
End Sub

Code Only Runs When Stepping Through Code - Race Condition?

I have a macro that seems to work when I step through the code using F8, but not when I try to run the entire sub or call it from a button the user will push in the Worksheet.
When I run the code in its entirety, I can tell it does some of the steps, but not all of them.
I have read a couple existing posts about this happening and it seems like everytime the person was using alot of .Select .Activate and so on. I am not using those commands, and I tried to set my sheets and variables more dynamically than that. I also included Application.ScreenUpdating = False.
Since I'm not using those types of commands, I'm assuming it is some sort of Race Condition, where it needs more time to pause. I tried adding a couple Application.Wait(Now + TimeValue("00:00:01")) lines, but when I added those to the VBA code it would just freeze Excel entirely when I tried running the entire code. Not sure why it did that, but I would have to kill Excel in the Task Manager.
Here is the VBA, sorry for all the comments I added:
Sub CombineExcels()
'***** This sub is to autofilter for each available filter option and put the matching Excel file paths into one cell on the FINAl sheet *****
UserForm1.Show vbModeless
'***** Setting variables *****
Dim RngOne As Range, cell As Range
Dim LastCell As Long
Dim LastCellC As Long
Dim Row As Long
Dim i As Integer
Dim count As Integer
Dim s As String
Dim EnterVal As Range
Dim FirstUsedRow As Long
Dim FirstEmptyCell As Long
'***** In the event of an error, we will skip to our Error Handler *****
On Error GoTo EH
'***** Turn off Excel Screen Updating so the screen doesn't keep flashing and slow the macro *****
Application.ScreenUpdating = False
'***** Finding the last used row, first empty row, and largest range that we will work with *****
With Sheets("Final")
LastCell = .Range("A" & Sheets("Final").Rows.count).End(xlUp).Row
LastCellC = .Range("C" & Sheets("Final").Rows.count).End(xlUp).Row + 1
Set RngOne = .Range("A2:A" & LastCell)
End With
'***** This section is a loop that will apply the filter for each option and combine the results onto the Final sheet *****
For Each cell In RngOne
With Sheets("Folder Output")
'***** If a filter is already applied, we will remove the filter *****
If .FilterMode Then .ShowAllData
'***** Clearing any remaining data from the location we will temporarily store file paths in *****
Worksheets("Final").Range("Q1:Q100").Clear
'***** Apply the filter. The criteria is named CELL which is a loop for each filter option *****
.Columns("A").AutoFilter Field:=1, Criteria1:=cell
'***** Find the last row of filter results in Column C *****
Row = .Range("C" & Sheets("Folder Output").Rows.count).End(xlUp).Row
'***** If the row number returned is 2 then we know that there is only 1 file path result *****
If Row = "2" Then Row = .Range("C" & Sheets("Folder Output").Rows.count).End(xlUp).Row + 1
'***** Setting a new range for only the filtered results in Column C *****
Dim rng As Range: Set rng = .Range("C2:C" & Row).SpecialCells(xlCellTypeVisible)
Dim rngCell As Range
'***** Loop to get each result and place it on the FINAL sheet in column Q for now *****
For Each rngCell In rng
If Sheets("Final").Range("Q1").Value = "" Then
FirstEmptyCell = .Range("Q" & Sheets("Final").Rows.count).End(xlUp).Row
Worksheets("Final").Range("Q" & FirstEmptyCell) = rngCell.Value
Else
FirstEmptyCell = .Range("Q" & Sheets("Final").Rows.count).End(xlUp).Row + 1
Worksheets("Final").Range("Q" & FirstEmptyCell) = rngCell.Value
End If
'***** Continue to the next filtered result until all file paths for that filter are complete *****
Next rngCell
'***** Finding the last used row from the pasted file path results in Column Q *****
count = Sheets("Final").Cells(Rows.count, "Q").End(xlUp).Row
'***** Loop to combine all the paths into one string but separate the paths with a ; *****
For i = 1 To count
If Cells(i, 17).Value <> "" Then s = s & Cells(i, 17).Value & ";"
Next
'***** Find the last used row from Column C in the Final sheet. Then paste the combined file paths to Column C *****
Set EnterVal = Worksheets("Final").Range("C" & LastCellC)
EnterVal.Value = s
Set EnterVal = Nothing
s = ""
'***** This tells the macro to move a row down next time through the loop *****
LastCellC = LastCellC + 1
End With
Next
'***** Once the loop is finished, we will end this sub in the CleanUp section *****
GoTo CleanUp
'***** Before exiting the sub we will turn Screen Updating back on and notify the user the Excel file paths are combined *****
CleanUp:
On Error Resume Next
Application.ScreenUpdating = True
UserForm1.Hide
MsgBox ("Excel File Paths Have Been Concatenated!")
Exit Sub
'***** If an error occurs during the loop, we go here to redirect to turn updating on and end the sub *****
EH:
' Do error handling
GoTo CleanUp
End Sub
I can tell that when I run the entire code it is doing all the filtering, and I believe putting the results in Column Q on the "Final" worksheet, but those results are not being merged together with the ; as a delimiter and then being put in Column C as one string containing multiple paths.
So I think the issue is happening somewhere around here, but not sure:
'***** Finding the last used row from the pasted file path results in Column Q *****
count = Sheets("Final").Cells(Rows.count, "Q").End(xlUp).Row
'***** Loop to combine all the paths into one string but separate the paths with a ; *****
For i = 1 To count
If Cells(i, 17).Value <> "" Then s = s & Cells(i, 17).Value & ";"
Next
'***** Find the last used row from Column C in the Final sheet. Then paste the combined file paths to Column C *****
Set EnterVal = Worksheets("Final").Range("C" & LastCellC)
EnterVal.Value = s
Set EnterVal = Nothing
s = ""
'***** This tells the macro to move a row down next time through the loop *****
LastCellC = LastCellC + 1
End With
Next
Any tips or ideas would be greatly appreciated. Thank you.
YOu shouldfully qualify your references:
count = Sheets("Final").Cells(Rows.count, "Q").End(xlUp).Row
Should be:
With Sheets("Final)
count = .Cells(.Rows.count, "Q").End(xlUp).Row
End with
Similarly in above areas, you have added qualifications when using a with statement:
Row = .Range("C" & Sheets("Folder Output").Rows.count).End(xlUp).Row '.Rows.Count as sheet is already qualified

VBA Numeric Value Find / Overwrite data

I am in the process of writing a macro that allows me to update data monthly. However, I realized that sometimes I will need to overwrite the data from the same month when there is a correction issued to the data. I am trying to come up with a macro that will allow me to search the entire column and if there is a match with the data, allow me to run another macro to overwrite the old data with the new data. Any ideas of how to go about this?
Here is what I have so far. I need to replace to i to 500 with the entire column.
Sub FindMatchingValue()
Dim i As Integer, ValueToFind As Integer
intValueToFind = Sheet8.Range("L6")
For i = 1 To 500 ' This needs to be the entire column
If Cells(i, 1).Value = intValueToFind Then
MsgBox ("Found value on row " & i)
Exit Sub
End If
Next i
MsgBox ("Value not found in the range!")
End Sub
You do not want to run a loop down your entire column (slightly over 1 mil X). Instead, find your last row form the bottom, and loop through that range.
If your goal is to run a second Macro when you do find a match, you can get rid of your msgbox and Exit Sub and replace with Call SecondMacro, where "SecondMacro" is the name you assigned to your sub of course. Just an option ~
Sub FindMatchingValue()
Dim i As Integer, ValueToFind As Integer, LRow as Integer
intValueToFind = Sheet8.Range("L6")
LRow = Range("A" & Rows.Count).End(XlUp).Row
For i = 1 To LRow
If Cells(i, 1).Value = intValueToFind Then
MsgBox ("Found value on row " & i)
Exit Sub
End If
Next i
MsgBox ("Value not found in the range!")
End Sub

Search for specific string in an Excel Workbook

So, I need to make an Excel Macro in VBA that will search for a string, then compare it with a pre-set string of my choice and then change the value of a cell in another Sheet.
It goes like this:
Sub Macro1()
Dim A As Integer
Dim WS As Worksheet
Dim ToCompare, Coniburo As String
Coniburo = "My String"
For Each WS In Worksheets
For A = 1 To Rows.Count
ToCompare = Left(Cells(A, 3), 100)
If InStr(ToCompare, Coniburo) > 0 Then
Sheets("Last Sheet").Cells(21, 2).Value = "233"
End If
Next A
Next
The macro works....... If I remove the first For (the one that search through sheets) and as long as I'm in a sheet where "My string" is present. Otherwise, it doesn't work. It takes a long time to process, over a minute since there are 17 sheets.
Why isn't working? I read a lot of posts here, the Microsoft Dev forum, a site called Tech on the Net, and still there is something I'm missing, but I don't know why.
Can anybody point me in the right direction?
Use a With ... End With to focus the parent worksheet for each iteration of the loop.
Option Explicit
Sub Macro1()
Dim a As Long, Coniburo As String, ws As Worksheet
Coniburo = "My String"
For Each ws In Worksheets
With ws
For a = 1 To .Cells(.Rows.Count, "C").End(xlUp).Row
If CBool(InStr(Left(.Cells(a, 3), 100), Coniburo, vbTextCompare)) Then
Worksheets("Last Sheet").Cells(21, 2).Value = 233
End If
Next a
End With
Next
End Sub
You need to prefix Rows, Range and Cells calls with a period like .Rows... or .Range(...) or .Cells(...) when inside a With ... End With block. This identifies them with the parent worksheet described by the With .. End With.
I also made the comparison case-insensitive with vbTextCompare.
There is the remaining problem of writing and rewriting 233 into the same cell on the same worksheet but that is another matter.
I've bent the rules a little here but I want to show how we could use the built in FIND function to speed things up dramatically. Simply, we'll work through each sheet within column C only; we'll use the FIND function to find the ROW number where column C contains your search string.... then we'll double-check that cell to see if your search string is within the first 100 characters, per your requirement. If it is, we'll consider that a match. In addition to your result of logging "233" into the sheet "Last Page" I've included some bright green highlighting just to help see what's going on...
Sub findConiburo()
Coniburo = "My String"
For Each ws In Worksheets
With ws.Range("C:C")
myName = ws.Name 'useful for debugging
queue = 1 'will be used to queue the FIND function
x = 0 'loop counter
Do 'loop to find multiple results per sheet
On Error Resume Next 'Disable error handling
'FIND Coniburo within ws column C, log row number:
'Note ".Cells(queue, 1)" is a relative reference to the current WS, column C
foundRow = .Find(What:=Coniburo, After:=.Cells(queue, 1), LookIn:=xlFormulas, LookAt _
:=xlPart, SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:= _
False, SearchFormat:=False).Row
'If no result found then an error number is stored. Perform error handling:
If Err.Number <> 0 Then
'No results found, don't do anything, exit DO to skip to next sheet:
Exit Do
End If
On Error GoTo 0 'Re-enable error handling
If x = 0 Then
'first loop - log the first row result:
originalFoundRow = foundRow
ElseIf foundRow = originalFoundRow Then
'Not the first loop. Same result as original loop = we're back at the start, so exit loop:
Exit Do
End If
'Update queue so next loop will search AFTER the previous result:
queue = foundRow
'check if the string is not only SOMEWHERE in the cell,
'but specifically within the first 100 characters:
ToCompare = Left(.Cells(foundRow, 1), 100)
If InStr(ToCompare, Coniburo) > 0 Then
.Cells(foundRow, 1).Interior.ColorIndex = 4 'highlight green
Sheets("Last Sheet").Cells(21, 2).Value = "233"
End If
'Update loop counter:
x = x + 1
Loop
End With
Next ws
End Sub

Extracting values from listbox

I created a UserForm in VBA with 2 ListBoxes.
What I want to do is extract values from the right ListBox (and keep them in temp) and delete every row in another sheet which contains these names.
Writing a code to delete rows is not an issues. I have no clue how to use these selected items in another VBA module. Any ideas?
You can save them in a Collection, and then use that Collection as argument in other Procedure.
Actually I would like my code to look more like this:
For Each c In Range
If c.Value = [any of values from list box] Then
c.EntireRow.Delete
End If
Next c
Update to process rows in reverse (since delete will shift rows up). See comments at top of code.
Option Explicit
Sub cmdDelete_Click()
' This subroutine will allow a user to delete selected rows from an Excel sheet.
' In a multi-select listbox on a user form, select the items that you want to delete.
' Click the 'Delete' button on the form and the following will occur:
' a. Each selected item will be delimited and concatenated into one string.
' (The reason for doing that is to avoid spinning thru each listbox item for
' every row)
' b. Each row in the selected range will have it's value checked within the string.
' c. If found. the row will be deleted.
'
' Notes: - Need to loop thru the rows from bottom upwards to top because
' as each row is deleted, it will shift remainder upwards.
' - You don't really need the delimiters if values can never be confused
Dim sList As String
Dim lItem As Long
Dim r As Range
Dim ws As Worksheet
Dim lFirstRow As Long
Dim lLastRow As Long
Dim lRow As Long
For lItem = 0 To lstCountries.ListCount - 1
If lstCountries.Selected(lItem) = True Then
sList = sList & "<" & Me.lstCountries.column(0, lItem) & ">" ' Adjust to the column YOU want (relative to zero)
End If
Next
Debug.Print "Full List to Delete: " & sList
Set ws = ThisWorkbook.Sheets("Sheet1") ' Change to YOUR worksheet name
' Find the last used row
lLastRow = Cells.Find(What:="*", after:=Range("A1"), SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
lFirstRow = 2 ' Set to YOUR first used row
' Spin thru all rows in the range of rows
' Go in reverse order since the Delete will shift the rows UP
For lRow = lLastRow To lFirstRow Step -1
' See if row value exists in the selections made in the listbox
If InStr(1, sList, "<" & ws.Cells(lRow, 1) & ">") > 0 Then
ws.Rows(lRow).Delete ' Delete row if a match is found
End If
Next lRow
End Sub
This code solved my issue
Private Sub delete_button_Click()
On Error Resume Next
Dim custom_range(1 To 5) As Range
Set custom_range(1) = ActiveWorkbook.Sheets("Countries").Columns(5).Cells
Set custom_range(2) = ActiveWorkbook.Sheets("Operations").Columns(2).Cells
Set custom_range(3) = ActiveWorkbook.Sheets("Costs").Columns(2).Cells
Set custom_range(4) = ActiveWorkbook.Sheets("Revenue").Columns(2).Cells
Set custom_range(5) = ActiveWorkbook.Sheets("FS").Columns(2).Cells
For i = 0 To ListBox_selected_countries.ListCount - 1
country_to_delete = ListBox_selected_countries.List(i)
For j = 1 To 5
Set active_range = custom_range(j)
For Each c In active_range
If c.Value = country_to_delete Then
c.EntireRow.delete
End If
Next c
Next j
Next i
End Sub