Delete a row in Excel VBA - excel

I have this piece of code which finds the excel row of an item from a list and deletes the items from a list. What I want... is to delete the Excel row as well.
The code is here
Private Sub imperecheaza_Click()
Dim ws As Worksheet
Dim Rand As Long
Set ws = Worksheets("BD_IR")
Rand = 3
Do While ws.Cells(Rand, 4).Value <> "" And Rand < 65000
If ws.Cells(Rand, 4).Value = gksluri.Value * 1 And ws.Cells(Rand, 5).Value = gksluri.List(gksluri.ListIndex, 1) * 1 Then
ws.Range(Rand, 1).EntireRow.Delete '(here I want to delete the entire row that meets the criteria from the If statement)
gksluri.RemoveItem gksluri.ListIndex
Exit Do
End If
Rand = Rand + 1
Loop
End Sub
Where I added ws.Range(Rand,1).EntireRow.Delete is where I want to delete the entire row but I don't know how to do it. What I want... if it finds the same value in a cell like in some selected item of my list to be able to remove both the entire row in excel and the item from the listbox. It works to remove the item from the listbox but I don't know how to remove the row as well

Chris Nielsen's solution is simple and will work well. A slightly shorter option would be...
ws.Rows(Rand).Delete
...note there is no need to specify a Shift when deleting a row as, by definition, it's not possible to shift left
Incidentally, my preferred method for deleting rows is to use...
ws.Rows(Rand) = ""
...in the initial loop. I then use a Sort function to push these rows to the bottom of the data. The main reason for this is because deleting single rows can be a very slow procedure (if you are deleting >100). It also ensures nothing gets missed as per Robert Ilbrink's comment
You can learn the code for sorting by recording a macro and reducing the code as demonstrated in this expert Excel video. I have a suspicion that the neatest method (Range("A1:Z10").Sort Key1:=Range("A1"), Order1:=xlSortAscending/Descending, Header:=xlYes/No) can only be discovered on pre-2007 versions of Excel...but you can always reduce the 2007/2010 equivalent code
Couple more points...if your list is not already sorted by a column and you wish to retain the order, you can stick the row number 'Rand' in a spare column to the right of each row as you loop through. You would then sort by that comment and eliminate it
If your data rows contain formatting, you may wish to find the end of the new data range and delete the rows that you cleared earlier. That's to keep the file size down. Note that a single large delete at the end of the procedure will not impair your code's performance in the same way that deleting single rows does

Change your line
ws.Range(Rand, 1).EntireRow.Delete
to
ws.Cells(Rand, 1).EntireRow.Delete

Better yet, use union to grab all the rows you want to delete, then delete them all at once. The rows need not be continuous.
dim rng as range
dim rDel as range
for each rng in {the range you're searching}
if {Conditions to be met} = true then
if not rDel is nothing then
set rDel = union(rng,rDel)
else
set rDel = rng
end if
end if
next
rDel.entirerow.delete
That way you don't have to worry about sorting or things being at the bottom.

Something like this will do it:
Rows("12:12").Select
Selection.Delete
So in your code it would look like something like this:
Rows(CStr(rand) & ":" & CStr(rand)).Select
Selection.Delete

Related

VBA Trying to loop through a column and delete the entire row if they contain a value

I'm not very good at loops.
I'm trying to use VBA to loop through a column to look for any value, and then delete the entire row if it doesn't find anything (It's essentially a way of deleting rows of data that I've marked (or unmarked in this case)).
I've tried various things. My most recent attempt is below but its just deleting every row regardless of whether that cell has a value or not. Any suggestions?
Dim i as Long
For i = 1 To 50
If Cells(i, 1).Value = "" Then
Selection.EntireRow.Delete
Else
i = i + 1
End If
Next i
End Sub
There are several issues here:
When deleting rows in a loop work backwards, if going forward your row number changes as you delete.
There is no need to increment variable "i" next i already does this
Use the Worksheet object to delete the row rather than Selection
I would rewrite like this:
Sub delete()
Dim i As Long
For i = 50 To 1 Step -1
If Cells(i, 1).Value = "" Then
Worksheets("Sheet1").Rows(i).EntireRow.delete
End If
Next i
End Sub

VBA For Each ... Loop skipping cells when clearing row

I know this is a common problem people have when trying to delete rows when looping through ranges but that is not the case here. Rather than deleting the rows, I am cutting the entire row and pasting into another worksheet, leaving a blank row. The issue is that the For Each ... Loop acts as if I deleted the row and skips the next cell in the loop. Below is a snippet of the code.
last_row = 100
Set search_rng = n_ws.Range(n_ws.Range("L2"), n_ws.Cells(last_row, 12))
For Each cell In search_rng
find_amt = cell.Value * -1
Set s_cell = search_rng.Find(find_amt, LookIn:=xlFormulas)
If Not s_cell Is Nothing Then
' do stuff
Else
'No matching value found, move row to o_ws
paste_row = o_ws.UsedRange.Rows.Count + 1
n_ws.Rows(cell.Row).Cut o_ws.Cells(paste_row, 1).EntireRow
End If
Next
Anyone know what I'm doing wrong here? I imagine I can fix it by changing the For Each ... Loop to a For i in Range loop but I'm more curious as to why this is happening.
Cutting the row still deletes it in pratice. You can convince yourself of this by just manually doing so on the spread sheet. You will see a shift in range which means this is the same issue as deleting rows in a loop. A common way to avoid this issue would be to switch to For i loop and loop backwards.
Since you are not actually deleting rows here you could also try the following....
Using your same code, you can just copy & paste the row, not cut, and then circle back around and clear the rows content to leave a blank row in place.
Else
paste_row = o_ws.UsedRange.Rows.Count + 1
n_ws.Rows(Cell.Row).Copy o_ws.Cells(paste_row, 1)
n_ws.Rows(Cell.Row).ClearContents
End If
Next cell
Note
The better way to do this would be to loop through your range and create a Union (collection of cells) that meet your criteria. Once the loop is done you can copy, paste, & clear the Union all at once. This means you have one instance of actions taken to worksheet rather many

How to cycle through merged cells (and populate values from a one-dimensional array)?

I would like to find a way how to cycle through merged cells, e.g. using a For...Next loop.
I could only manage to make it work like this:
Cells(1,1).Select
For i=1 to 6
Selection.Value = arrData(i)
Selection.Offset(0,1).Select
Next i
I hate using .Select - but if I use .Offset(0,i) it won't move from merged cell to merged cell, but just the number of columns from the original cell.
For more detail - I am copying values from a csv-like format into a nicer formatted output sheet, that is then supposed to be exported with bunch of merged cells.
There are multiple sections to the sheet but within each section there is a known amount of cells per row.
My only working solution without .Select is to use .Cells
Example:
For row=0 to 12
with rng.Offset(1,0)
.cells(row+1,1)=arrdata(1+(row*6))
.cells(row+1,3)=arrdata(2+(row*6))
.cells(row+1,7)=arrdata(3+(row*6))
.cells(row+1,9)=arrdata(4+(row*6))
.cells(row+1,14)=arrdata(1+(row*6))
.cells(row+1,16)=arrdata(1+(row*6))
End with
Next row
but this is pretty ardous.
EDIT: Here is a screenshot:
target area
The idea is that the amount of rows is completely flexible, depending on the transaction. So sometimes there is only one row, but can be anything really.
My code generates this section using relative references based on named ranges.
And then from the ugly sheet (where all information is stored in a single row) the values are fed into a one-dimensional array, then the array should be fed into the nice looking sheet.
If the sheet had no merged cells, the formula would look quite simple:
Dim i as integer, j as integer
Dim ws as worksheet: set ws = Worksheets("Printable")
'data array has already been filled with info in a linear order beforehand
k=1
For i=1 to item_qt 'number of cost items lines
For j=1 to item_col 'number of detail columns (in this section)
ws.Range("item_title").Offset(1,0).Cells(i,j).Value=data(k)
k=k+1
Next j
Next i
But because of the nature of this sheet - supposed to be printable and nicer on the eyes - I can't do that and have to find a way how to switch between the merged cells.
Hope this Edit cleared some things up.
I am also looking into the suggestions now to see if I can apply those somehow, but if anybody knows of something better, I am open for everything.
If you're stepping through merged columns, you could use something like
For i = startColumn To endColumn
If Cells(row,StartColumn).MergeArea.Columns.Count > 1 Then
'Do Stuff
i = i + Cells(row,StartColumn).MergeArea.Columns.Count - 1
End If
Debug.Print i
Next i
This will test for merged columns and then jump to the next column after the merge.
EDIT:
Seeing your data structure added in your edit, you could incorporate the MergeArea.Columns.Count method into your For j-Next j loop like
k=1
For i=1 to item_qt 'number of cost items lines
For j=1 to item_col 'number of detail columns (in this section) <-this will need to
'be the total number of columns, not just the number of
'detail fields
ws.Range("item_title").Offset(1,0).Cells(i,j).Value=data(k)
j = j + ws.Range("item_title").Offset(1,0).Cells(i,j).MergeArea.Columns.Count - 1
k=k+1
Next j
Next i
By searching for "excel find merged cells vba" Google comes up with:
How To Identify And Select All Merged Cells In Excel?
https://www.extendoffice.com/documents/excel/962-excel-select-merged-cells.html
Sub FindMergedcells()
'updateby Extendoffice 20160106
Dim x As Range
For Each x In ActiveSheet.UsedRange
If x.MergeCells Then
x.Interior.ColorIndex = 8
End If
Next
End Sub
and
2 Practical Methods to Find Merged Cells in Your Excel
https://www.datanumen.com/blogs/2-practical-methods-find-merged-cells-excel/
Sub FindMerge()
Dim cel As Range
For Each cel In ActiveSheet.Range(“A1:G13”)
If cel.MergeCells = True Then
‘change the color to make it different
cel.Interior.Color = vbYellow
End If
Next cel
End Sub

Delete a duplicated line (only if it's the next one!)

Is it possible to use a function in Excel 2003 to delete an entire row if it's the same as the previous one? For example:
apple
apple
plum
vinegar
apple
banana
banana
banana
apple
I want to delete #2, 7# and #8, but I don't want #5 and #9 to be deleted. I want to delete a duplicated entry ONLY IF it's the next one. I hope I managed to keep it clear to you.
If there isn't a function, how can I do that in VBA? Thanks in advance!
If it is a one-off, you can do it without VBA fairly simple by adding a formula in the next column - let's say your column is A:
in B2 put the following:
=IF(A1=A2,"DELETE","")
drag the formula down
add an autofilter on both columns
filter on "DELETE" in column B
delete all visible rows
remove column B
Here's something that might fit, deleting all duplicate rows (physically, not only cell data!), so keep in mind that it can be used only if you use one-column sheets. Otherwise you will loose some other data.
Sub Unduplicate()
Dim prev As String
Dim sel As Range
Dim i As Integer
Set sel = Range(Selection, Selection)
prev = sel.Offset(0).Value ' set prev as the first value - never will be deleted
i = 1 ' start from 2nd row
Do While sel.Offset(i).Value <> "" And sel.Offset(i).Value <> ""
If sel.Offset(i).Value = prev Then ' if duplicate - delete row
sel.Offset(i).EntireRow.Delete
Else ' else set new prev, and go further
prev = sel.Offset(i).Value
i = i + 1
End If
Loop
End Sub
After running this macro for your example, I'm getting:
You can modify it, to store the values in an array, and than fill the column with remembered array instead of deleting the rows, but that should be easy now.
Just a quick note...Make sure you work your way from the bottom of the range if you are deleting entire rows. You may get some unexpected results if you work your way down from the top of the range. You may also want to consider clearing the cell value and then sorting, instead of deleting. I would write up an example for you but I am short on time. If you get stuck I can write it for you later.
Edit:
My original answer is not necessarily true as shown in makciook's solution below. In the past I have mistakenly approached the problem this way (DO NOT USE THIS AS A SOLUTION!!!):
Sub duplicates()
Dim c As Range, rng As Range
Set rng = Selection ''Select the entire list before running
For Each c In rng
If c.Value = c.Offset(-1, 0).Value Then c.EntireRow.Delete
Next
End Sub
With this script, the cell range does not reset once a row is deleted and shifted up. So running this would not catch the second duplicate of banana in your list. As an alternative I usually find the last row and work my way up to the first row to account for the rows shifting up when a deletion occurs. I am going to give myself a -1 if possible.

Speeding up a search and delete macro

I have a list containing three columns. The first column contains Names and the other two columns have numbers. The macro takes the first name(A1) and then searches down column A for another occurrence.
When it finds it, it deletes the entire row.It then goes to A2 and does the same thing agan. It works ok for about 500 entries, but using 3000 entries slows it down considerably. Is there a way to speed up this code?
Sub Button1_DeleteRow()
Dim i As Integer
Dim j As Integer
Dim Value As Variant
Dim toCompare As Variant
For i = 1 To 3000
Value = Cells(i, 1)
For j = (i + 1) To 3000
toCompare = Cells(j, 1)
If (StrComp(Value, toCompare, vbTextCompare) = 0) Then
Rows(j).EntireRow.Delete
End If
Next j
Next i
End Sub
If you are running xl07/10 then you can do this with a single line with Remove Duplicates. If you are running 03 then a solution with AutoFilter will be most efficient (I can provide this if you are on the older version)
Remove Duplicates
Manually
Select column A
Data .... Remove Duplicates
Expand selection
Select only column A to find duplicates on
Code
ActiveSheet.Range("$A$1:$A$3000").EntireRow.RemoveDuplicates Columns:=1, Header:=xlNo
To supplement #brettdj's answer, if you are running Excel 2003, you can do this using AdvancedFilter as follows:
Range("A1:A11").AdvancedFilter Action:=xlFilterInPlace, Unique:=True
Note: AdvancedFilter assumes that the first row of your range (row A in this example) contains column headers and will not include that row in the filtering.
To do this manually: Data > Filter > Advanced Filter... > Unique records only
Using Bretts technique is a good answer: but to answer your question about why does it take so long:
- Your macro is getting a value from over 4 million cells one by one. This is very slow.
- I don't see that your macro has switched off screenupdating and automatic calculation: every time a row is deleted the screen will refresh and Excel will recalculate. If you have not switched these off it is very slow.
This code should run a lot faster
Option Explicit
Sub Button1_DeleteRow()
Dim i As Long
Dim j As Long
Dim vArr As Variant
Dim iComp As Long
Dim Deletes(1 To 3000) As Boolean
Application.ScreenUpdating = False
iComp = Application.Calculation
Application.Calculation = xlCalculationManual
vArr = Range("a1:A3000")
For i = 1 To 3000
For j = (i + 1) To 3000
If (StrComp(vArr(i, 1), vArr(j, 1), vbTextCompare) = 0) Then
Deletes(j) = True
End If
Next j
Next i
For j = 3000 To 1 Step -1
If Deletes(j) Then Rows(j).EntireRow.Delete
Next j
Application.ScreenUpdating = True
Application.Calculation = iComp
End Sub
Sorting the data on column A would then make it trivial to identify and remove the duplicates in a single pass
In response to the comment below, I'll explain why sorting is a useful technique.
By sorting column A into order, duplicate removal simply becomes a matter of comparing adjacent entries in column A. You can then either delete the duplicate rows as you find them or flag them for later deletion.
The process should actually be a lot less tedious as you only have to sort the list (and sorting, being built-in, tends to be very fast) and then do one pass (instead of 4498500) through the list deleting/flagging as you go (obviously you need a subsequent clean-up pass if you go for flagging).
On the issue of changing the order of the list, start by adding an extra column (e.g. column D) and have D2 contain the value 2 (i.e. just the row number). A quick fill-down later and every row is numbered. After sorting and deleting/flagging, restoring the original order is just a matter of re-sorting on column D which could then be deleted.
I use this method when I have to perform some operation or other on the duplicates. In other words, column A has duplicate values but the values in columns B and C are meaningful (for example, I might want to sum these values from all of the entries relating to the specific value of column A). In many cases, however, it would be easier just to use SQL to achieve the same result

Resources