I loop through the Sheet via While, so when I find the string "Out" in the first row I delete the row.
The problem is that when I delete that column the next one goes to the deleted position and thus the loop will pass and wont delete it.
Can you help me?
Thanks
Sub DeleteCol()
xx = 37
Do While Worksheets("Data").Cells(1, xx) <> ""
Agrup = Worksheets("Data").Cells(1, xx)
Rubri = Worksheets("Data").Cells(2, xx)
If Agrup = "Out" Then
'Worksheets("Data").Columns(xx).Clear
Worksheets("Data").Columns(xx).Delete Shift:=xlShiftToLeft
End If
xx = xx + 1
Loop
End Sub
When deleting rows or columns you always need to loop backwards. Otherwise the current row/column position changes during delete.
This is because deleting always affects the position of rows/columns after the current row/column but not before. Therefore looping backwards does not affect our loop because un-processed rows/columns are before current row/column which are not affected by deleting.
Option Explicit 'very first line to ensure all variable are declared
Public Sub DeleteColumns()
Dim ws As Worksheet
Set ws = Worksheets("Data") 'define worksheet
Dim Argup As Range, Rubri As Range
Dim iCol As Long, lCol As Long 'iteration column, last column
Const fCol = 37 'first column
With ws '<-- use With statement
lCol = .Cells(1, .Columns.Count).End(xlToLeft).Column 'find last used column
For iCol = lCol To fCol Step -1 'loop from last column backwards to column 37
Agrup = .Cells(1, iCol)
Rubri = .Cells(2, iCol)
If Argup = "Out" Then
.Columns(iCol).Delete Shift:=xlShiftToLeft
End If
Next iCol
End With
End Sub
I also recommend to declare all variables and always use Option Explicit.
The answer is already posted, you need to delete from the end, rather than from the begining. But why? Lets image you have five columns, and you want to delete columns 2 and 4. Before deleting, this is how your columns look like:
Column index in loop: | 1 | 2 | 3 | 4 | 5 |
Original column number: | 1 | 2 | 3 | 4 | 5 |
Now you start from the begining...
Index 1: Do nothing (as intended)
Index 2: Delete (as intended)
Let's pause for a minute. Now let us see how deleting column 2 changed the picture:
Loop direction ---->
Column index in loop: | 1 | 2 | 3 | 4 | 5 |
Original column number: | 1 | 3 | 4 | 5 |
Now you might see that something is wrong. But lets continue the loop for the sake of the example:
Index 3: Do nothing (but this was originally column 4, and should have been deleted)
Index 4: Delete (but this was originally column 5, and should NOT have been deleted)
Index 5: Do nothing (depending on your code, you might get and out of bounds error)
Lets try to go backwards instead, maybe that helps:
Index 5: Do nothing (as intended)
Index 4: Delete
Let us pause, once again, to see how it looks now
<---- Loop direction
Column index in loop: | 1 | 2 | 3 | 4 | 5 |
Original column number: | 1 | 2 | 3 | 5 |
Well, something did shift. But as the next step in the loop is 3 (and we are going backwards), it doesn't really matter. Hope that helps the understanding a bit :-)
Related
I have about 100 rows that include a random number generator in a column and in another column with calulations=A based on that random number and other things. I have set up a column with an if statement to examine if A is within an upper and lower range which returns a "1" if true (a hit, if you will) and "0" if not. I want to run a loop and count the number of hits over time in these cells. Essentially i need a column that acccumulates the results so in the end I can see what range has the most hits, etc.
To make it simpler, let's first say your data is stored as follows, before we start, on your worksheet "ws":
A | B | C
1 Random Numbers | Calculated Value | Accumulated Value
2 0 | 1 |
3 5 | 0 |
4 8 | 1 |
5 73 | 0 |
6 2 | 0 |
[...]
And let's say we want to run this process 10,000 times to see which rows get the highest accumulated values, represented as the sum of the calculated value from each of the previous iterations. I would suggest the following VBA solution:
Sub AccumulateValues
Dim MaxIterations As Long: MaxIterations = 10000
Dim curRow as Long, LastRow as Long, it as Long
Application.Calculation = xlCalculationManual 'Required to set it to manual
With ws
LastRow = .Range("A" & .Rows.Count).End(xlUp).Row
For it = 1 to MaxIterations
'Force the Recalculation of Cells in columns A and B:
Application.Calculate
For curRow = 2 To LastRow
'Sum the result of this iteration with the accumulated value
'until the previous iteration and stores it.
ws.Cells(curRow, 3) = ws.Cells(curRow, 3) + ws.Cells(curRow, 2)
Next curRow
Next it
End With
End Sub
I have a table that I would like to resize dynamically in VBA.
My current code is this:
Sub resizedata()
Dim ws As Worksheet
Dim ob As ListObject
Dim Lrow1 As Long
Lrow1 = Sheets("Sheet4").Cells(Rows.Count, "J").End(xlUp).Row
Set ws = ActiveWorkbook.Worksheets("Sheet4")
Set ob = ws.ListObjects("Table28")
ob.Resize ob.Range.Resize(Lrow1)
End Sub
I would like to add one condition onto this though...
The table should resize to before the first 0 in column J.
For instance:
+-------+--------+-------+
|Date(I)|Hours(J)| Sal(K)|
+-------+--------+-------+
| Aug | 150000 | 12356 |
| Sep | 82547 | 8755 |
| Oct | 92857 | 98765 |
| Nov | 10057 | 45321 |
| Dec | 0 | 0 |
| Jan | 0 | 0 |
+-------+--------+-------+
The above table's last row should be the November row because December is the first 0 value in column J.
Can anyone assist in revising my existing code?
Something like:
With Sheets("Sheet4")
Lrow1 = .Cells(.Rows.Count, "J").End(xlUp).Row
Do While .Cells(Lrow1, "J").Value=0
Lrow1 = Lrow1 - 1
Loop
End With
This should help you to automatically resize the table.
With Worksheets("sheet-name").ListObjects("table-name")
.Resize .Range(1, 1).CurrentRegion
End With
Is there a particular reason you want to do this in VBA? You can simply create a defined name and use the following to create a self-adjusting range:
OFFSET(Sheet!$A$1,0,0,COUNTA(Sheet!$A:$A),COUNTA(Sheet!$1:$1))
OFFSET(reference, rows, cols, [height], [width])
The top line shows typical usage, 2nd line is the official syntax.
Go into Name Manager on the Formulas tab, click new, give it a name and paste the code into Refers to:.
One caveat, it looks as though your data has 0's where there are no values. If that is truly the case, you'll have to do a different test to determine height.
One benefit of this method is that it is always calculated. If the data changes then the size definition of your named range adjusts as needed.
I hope this is helpful, I use this a lot...
I have a large data set which looks like this:
Employee ID |Job| Function| Level|Date of change
1 | x | a | A1 | 01/05/2014
1 | y | a | A1 | 02/04/2015
1 | y | a | A2 | 25/08/2015
1 | z | a | A3 | 27/12/2015
1 | z | c | A3 | 01/03/2016
2 | t | b | B1 | 12/05/2013
2 | v | b | B1 | 13/04/2014
2 | w | b | B3 | 12/01/2016
Each row contains a change in either job, function or level.
I need to create a table which puts together the latest change in level for each employee (so for employee 1, it would be row 4). So far I have used a combination of conditional formatting and pivots but I was wondering if there is a way to do this quicker in VBA.
Thanks!
Without VBA
This assumes that there are genuine dates in column E with format dd/mm/yyyy, In G1 enter the Array Formula:
=MAX(IF(A:A=1,E:E,""))
This gives the latest date for employee 1
Array formulas must be entered with Ctrl + Shift + Enter rather than just the Enter key.
Then in G2 enter:
=SUMPRODUCT(--(A1:A9=1)*(E1:E9=G1)*(ROW(1:9)))
This gives the row number of the record you are interested in.
From there you can use INDEX() to get any information from that row.
NOTE:
The formulas in G1 and G2 can be combined into a single cell if desired.
EDIT#1:
The same set of formulas should work with text values for the employee id as well as numbers:
Not sure this is the best solution, but since this is a one-off exercise and it did the trick I used this:
VBA to find all the rows where there was a change in level, and write a "yes" in column "F" where applicable:
Sub JLChange()
Dim Data As Worksheet
Dim i As Long
Dim lastrow As Long
Set Data = ThisWorkbook.Worksheets("Data")
lastrow = Data.Cells(Rows.Count, 1).End(xlUp).Row
For i = 1 To lastrow
If Cells(i + 1, 4).Value <> Cells(i, 4).Value And_
Cells(i + 1,1).Value = Cells(i, 1).Value Then
Cells(i + 1, 6).Value = "Yes"
Else
Cells(i + 1, 6).Value = "No"
End If
Next i
End Sub
For all the records in column "F", I used the formulas suggested by Gary's student to get the very last change.
Alternatively, you can copy-paste this database of changes in a new sheet, sort by ID and by date of change from newest to oldest, then use vlookup to get the first entry for each ID.
Language: Excel VBA
Scenario:
I have a source range (rngDTRef_AllRecord) that i need to insert the data into the destination range (rngRTDC_AllDetail)
for each of the row(rngCurrRow) in the source range (rngDTRef_AllRecord), it will filter the destination range (rngRTDC_AllDetail)
if the filter yield result, it will add some data to the result row (Note: each of the result is unique)
else it will add a new row to the destination range (rngRTDC_AllDetail)
below is the code:
For Each rngCurrRow In rngDTRef_AllRecord.Rows
intRTDC_RowBegin = 7
intRTDC_ColIdxTotal = 20
intRTDC_RowLast = fntGetNewLastRow 'this is some function get last row of rngRTDC_AllDetail due to might add in new row
Set rngRTDC_AllDetail = shtRTDC.Range(shtRTDC.Cells(intRTDC_RowBegin, 1), shtRTDC.Cells(intRTDC_RowLast, intRTDC_ColIdxTotal))
rngRTDC_AllDetail.AutoFilter
rngRTDC_AllDetail.AutoFilter Field:=intRTDC_ColIdxAcc, Criteria1:=rngCurrRow.Cells(1, intDTSource_ColIdxAccCode), Operator:=xlAnd
rngRTDC_AllDetail.AutoFilter Field:=intRTDC_ColIdxText, Criteria1:=rngCurrRow.Cells(1, strCurrAccCodeText), Operator:=xlAnd
Dim rngResult As Range
Set rngResult = rngRTDC_AllDetail.rows.SpecialCells(xlCellTypeVisible)'rngRTDC_AllDetail.SpecialCells(xlCellTypeVisible) also not work
'after filter, it will be only 1 result or none
If (rngResult.Rows.Count > 0) Then
'if the filter have result, do something here.
else
'add new row
End If
Next
My problem is after the filter, from the excelworksheet, i can see that have only 1 record, but
rngResult.Rows.Count = 2 'for the first filter record (that have 1 row only) in rngRTDC_AllDetail, i suspect due to it include the header, but i am not sure what wrong.
rngResult.Rows.Count = 1 'for the rest of the filter record that have 1 row
even worse is when there is no record after the filter, rngResult.Rows.Count = 1
Any advice will be appreciate. TQ.
Ok. After spent some time on it, I found out the solution already.
Below is some note for who facing similar problem.
Objective:
To insert "value" to columnC, when
columnA = "a" AND columnB ="b" AND the row is between 1 to 10 only
A B C
1 columnA | columnB | ColumnC
2 a | b | value
3 a | x |
4 x | x |
5 x | x |
6 c | b |
7 a | b | value
8 x | x |
9 x | x |
10 a | b | value
11 a | b |
12 a | b |
...
'insert value at columnC
ActiveSheet.Range("A1:B10").AutoFilter Field:=1, Criteria1:="a", Operator:=xlAnd
ActiveSheet.Range("A1:B10").AutoFilter Field:=2, Criteria1:="b", Operator:=xlAnd
Dim rng As Range
For Each rng In ActiveSheet.AutoFilter.Range.Range("A1:B10").SpecialCells(xlCellTypeVisible).Rows
If (rng.Row <> 1) Then 'no the header
ActiveSheet.Cells(rng.Row, "c") = "value" 'set value at C2,C7,C10
End If
Next rng
'count the total row visible
Dim rngA As Range
Set rngA = ActiveSheet.AutoFilter.Range.Range("A1:B10")
Debug.Print rngA.Columns(1).SpecialCells(xlCellTypeVisible).Count - 1 'result 3
'Reference:http://www.contextures.com/xlautofilter03.html
Note1**: "ActiveSheet.AutoFilter.Range" will always include the header and all below row as visible row.
Note2**: "ActiveSheet.AutoFilter.Range.Offset(1, 0).SpecialCells(xlCellTypeVisible).Rows" will offset the range 1 row below only, not suitable if you need to set the value at the result row.
I'm a newbie. I have an excel file with 10 sheets, 6 sheets named after 6 employee names the next 3 with some information (irrelevant to my code) and the 10th sheet named Temp.
The employee sheets have the following data in each column (D&E are blank):
| A | B | C | D | E | F |
| 17-Sep-13 | ProjectA | 6 | | | Report updated on this day |
| 18-Sep-13 | CBL Ideas - HMF | 7 | | | |
| 18-Sep-13 | CBL Ideas - HMF | 1 | | | |
I want to have all these data collated in the sheet named Temp as follows:
| A | B | C | D |
| 17-Sep-13 | Project A | 6 | foo |
| 18-Sep-13 | Project A | 7 | foo |
| 18-Sep-13 | Project B | 1 | foo |
| 17-Sep-13 | Project A | 6 | bar |
| 18-Sep-13 | Project A | 7 | bar |
| 18-Sep-13 | Project B | 1 | bar |
Below is my code:
Sub ListRelevantEntries()
Dim s As Integer
Dim C As Range
For s = 1 To Worksheets.Count - 4
If Sheets(s).Cells(Rows.Count, "F").End(xlUp) _
.Value = "Report last updated on this day" Then
'Execution stops on the below line with an
' "Application-defined or object defined error"
Sheets(s).Range(Cells(Rows.Count, "F").End(xlUp) _
.Offset(1, 0), Cells(Rows.Count, _ "A").End(xlUp)) _
.Copy(Sheets("Temp").Cells(Rows.Count, "A").End(xlUp).Offset(1, 0))
Sheets("Temp").Select
Sheets("Temp").Cells(Rows.Count, "C").End(xlUp).Offset(0, 1).Select
For Each C In Sheets("Temp").Range(Cells(Rows.Count,"C").End(xlUp). _
Offset(0, 1), Cells(Rows.Count, "A").End(xlUp).Offset(1, 0)).Cells
C = Sheets(s).Name
Next
ElseIf Not Sheets(s).Cells(Rows.Count, "F").End(xlUp) _
.Value = "Report last updated on this day" _
And Not Sheets(s).Cells(Rows.Count, "F").End(xlUp).Value = "" Then
MsgBox "Extra Words entered " & ActiveSheet.Cells(Rows.Count, "F") _
.End(xlUp).Offset(1, 0).Value & " in " & Sheets(s).Name
End If
Next
Sheets("Temp").Range("1:1").Delete
End Sub
Sorry for such a long question. I couldn't think of any other way to explain!
The error will be a lot easier if you clean up your code. Add a worksheet variable at the start
Dim ws As Worksheet
Then after the first For statement assign the target sheet to it
Set ws = Worksheets(s)
Notice I used Worksheets(s) not Sheets(s). They are different, don't mix them. The Sheets collection can include Charts as well as Worksheets, it represents every tab. The Worksheets collection only contains Worksheet objects. In your For loop you used Worksheets, then you used Sheets within the loop. This will break if any charts are in the workbook.
Okay, so now that you have this ws variable containing a reference to the worksheet, go ahead and replace all your Sheets(s) with ws inside the for-loop's body. While you are at it, fix all your calls to Cells and Rows. Anywhere you write something like ws.Range(Cells(1, "F")) you are making a mistake. Cells alone points to ActiveSheet.Cells where as you want ws.Cells. Otherwise you are trying to create a cross-worksheet range any time ws is not ActiveSheet. The same goes for other properties of a range, such as Rows. So now the line of code you're stopping on should be more like this:
ws.Range(ws.Cells(ws.Rows.Count, "F").End(xlUp) _
.Offset(1, 0), ws.Cells(ws.Rows.Count, "A").End(xlUp)) _
.Copy Sheets("Temp").Cells(Sheets("Temp").Rows.Count, "A") _
.End(xlUp).Offset(1, 0)
Notice, I also removed the parentheses around the parameter you're passing to .Copy. In VBA you call a method without parentheses unless it returns a value. So MyMethod "Value to pass" or result = MyMethod("Value to pass") but not MyMethod("Value to pass").
After fixing all that, if it still is producing the error, I'd recommend using the debugger. Open the watch window and start breaking the offending line into bits, examine them, and find the problem.
So first you might create a watch for ws and make sure it's the correct sheet. Then maybe edit that watch to be ws.Cells(ws.Rows.Count, "F").End(xlUp) and see if that is getting the correct cell.
Oh, also, if you're doing it properly you shouldn't need to call anything like Worksheet.Select or .Activate. In fact, it's recommended you avoid those the vast majority of the time, unless you are interacting with the user by changing their view.
Replace with something like
With Sheets(s)
.Range(.Cells(.Rows.Count, "F").End(xlUp).Offset(1, 0), .Cells(.Rows.Count, "A").End(xlUp)).Copy Sheets("Temp").Cells(Sheets("Temp").Rows.Count, "A").End(xlUp).Offset(1, 0)
End With
you get the error since you did not return the reference to the sheet(s).
A quick fix is to add this:
Sheets(s).Select
prior line 6 or after line 4.
But for better coding, try using whats seen in This Thread.
That link discusses how you can avoid using select by declaring and setting all your objects.