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.
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 256 columns in a excel sheet. I need to bring them to 128 columns by merging pairs of columns. How can I repeat the process of concatenating two cells throughout the sheet
Eg:
Col | Col2 | Col3 | Col4
1: | 45 | 2: | 556
1: | 34 | 2: | 567
Now my expected result is
Col1 | Col2
1:45 | 2:556
1:34 | 2:567
How can I perform this in Excel?
Can I use VBA?
Add two columns with the simple formula =A2&B2. Copy down. then copy resulting formula columns and paste special values to get rid of formula.
Assuming that range is "A1:IV1000" (change to what you need). In the end the original range is cleared and resulting array is dumped.
Sub ConcatColumns()
Dim rng As Range
Dim x As Long, c As Integer, z As Integer
Dim arr, arrResult
Const COLS As Integer = 256
Set rng = Range("A1:IV100") '//256 columns
arr = rng.Value
ReDim arrResult(1 To UBound(arr, 1), 1 To COLS / 2)
For x = 1 To UBound(arr, 1)
z = 1
For c = 1 To COLS - 1 Step 2
arrResult(x, z) = arr(x, c) & arr(x, c + 1)
z = z + 1
Next
Next
rng.ClearContents '//Clear all previous data
Range("A1").Resize(UBound(arrResult, 1), UBound(arrResult, 2)).Value = arrResult '//Dump result
End Sub
Using the formula (in cell A4 in image below)
=OFFSET(A1, , COLUMN()-1)&OFFSET(B1,,COLUMN()-1)
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 :-)
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.
I have a spreadsheet like this:
Item Category | Customer Category
--------------|-------------------
A | Z
A | Z
A | Y
B | Y
B | Z
B | Z
etc.. on to > 35K rows.
I am trying to write a VBA module to count and merge rows which are the same in both columns, but keep coming up short.
I want output like:
Item Category | Customer Category | Count
--------------|--------------------|------
A | Z | 2
A | Y | 1
B | Y | 1
B | Z | 2
And so on...
It sounds so simple, and I found numerous examples on here sorting and counting much more complicated spreadsheets, but can't get mine to work.
Try this easy way using Advance Filter.
Sub Test()
Dim rng As Range, lrow As Long
With Sheet1
.Columns("E:G").ClearContents: .Range("G1") = "Count"
Set rng = .Range("A1", .Range("B" & .Rows.Count).End(xlUp))
rng.AdvancedFilter Action:=xlFilterCopy, _
CopyToRange:=.Range("E1"), Unique:=True
lrow = .Range("E" & .Rows.Count).End(xlUp).Row
With .Range("G2", "G" & lrow)
.Formula = "=COUNTIFS(A:A,E2,B:B,F2)"
.Value = .Value
End With
End With
End Sub
I think this is ok with 35k. So everytime you run the code, it will summarize items in Columns A and B to Columns E to G. Just something to get you started. And of course the easiest would be a Pivot Table as commented by Tim. HTH