For loop terminates after the first iteration -Excel VBA - vba

I am trying to do a simple loop, where I have declared some variables as array entries. I originally used them as variables to be overwritten, but changed it when I read that these variables will not automatically overwrite each time through the loop.
My problem is that this loop terminates after the first iteration (with no error). I can't seem to figure out why...
The code is essentially to find cons_sum(i,2) for each i, or row in the Pre-Summary sheet and sum some data in another sheet BOPE, then insert that sum into Pre-Summary.
This is my first post and I am teaching myself vba so please excuse any code fails.
This is my code:
Option Explicit
Sub Create_GAR080()
Consmonth = Sheets("GAR080").Range("B2").Value
Sheets("Pre-Summary").Select
LastRow_summary = Cells(Rows.Count, "A").End(xlUp).Row
LastRow = 156
LastCol = 16
Dim cons_sum() As Variant
ReDim cons_sum(LastRow_summary, 4)
For i = 1 To LastRow_summary Step 1
cons_sum(i, 1) = Cells(i, 2).Value & "" 'pulls participant
cons_sum(i, 2) = cons_sum(i, 1) & Cells(i, 1) ' participant and gas gate concatenated
If cons_sum(i, 1) = "BOPE" Then
Sheets(cons_sum(i, 1)).Select
cons_sum(i, 3) = WorksheetFunction.Match(cons_sum(i, 2), Sheets(cons_sum(i, 1)).Range("A:A")) ' find participant gas gate combo
cons_sum(i, 4) = Application.Sum(Sheets(cons_sum(i, 1)).Range(Cells(cons_sum(i, 3), 5), Cells(cons_sum(i, 3), 16)))
If cons_sum(i, 4) > 0 Then
Sheets("Pre-Summary").Cells(i, 4).Value = cons_sum(i, 4)
End If
End If
Next i
On Error Resume Next
End Sub

As Siddharth points out, your changing of sheets will result in cons_sum(i, 1) = "BOPE"to always be negative. Therefore, while the loop will run 1,848 times, it will not change anything.
In addition, a few more remarks:
You are using a 1848x4 array to do multiple operations in each line - but only stored the value of each operation and not use the array afterwards. Thus, you do not need 1848x4, but only 1x4, as you can reuse the variables
Instead of using an array, it is much better to use speaking variables. This will make your code much easier to understand
You have a lot of assumptions about the workbook structure hidden in the VBA formulas/statements, e.g. the number of rows, the name "BOPE", etc.. It is better to store them in constants in the beginning of the macro - or even better to store them somewhere on a settings sheets and refer to them with named ranges
You most likely forgot FALSE (or 0) as the third parameter for your match function. Therefore the function might return the wrong values if the column is not sorted
Instead of Range(Cells(x1,y1),Cells(x2,y2)) you can use Range.Resize and Range.Offset (and combinations thereof). This makes the code much easier to read!
do not use On Error Resume Next unless you know exactly what error you are happy to neglect! Even if you use it, use another On Error statement right after the operation that is allowed to produce an error.
Taking this into account, I reworked your code to the following:
Sub Create_GAR080_reworked()
Const cStrTerm As String = "BOPE"
Dim wsData As Worksheet
Dim lngRowCount As Long, i As Long
Dim strParticipantGasID As String
Dim lngParticipantGasCombo As Long
Dim dblSum As Double
Set wsData = Sheets(cStrTerm)
With Sheets("Pre-Summary")
lngRowCount = .Cells(Rows.Count, 1).End(xlUp).Row
For i = 1 To lngRowCount
If .Cells(i, 2) = cStrTerm Then
strParticipantGasID = cStrTerm & .Cells(i, 1) ' participant and gas gate concatenated
lngParticipantGasCombo = WorksheetFunction.Match( _
strParticipantGasID, wsData.Range("A:A"), 0) ' find participant gas gate combo
dblSum = Application.Sum( _
wsData.Range("E1:P1").Offset(lngParticipantGasCombo - 1))
If dblSum > 0 Then
.Cells(i, 4).Value = dblSum
End If
End If
Next i
End With
End Sub
As I do not have your worksheet, I couldn't debug it. Also, not sure if I hit the right names as I have no idea what each variable is really refer to. But it should give you a start.

If the first itiration the code Sheets(cons_sum(i, 1)).Select is hit, you'll never get back to the Pre-Summary sheet.
Try:
Option Explicit
Sub Create_GAR080()
Consmonth = Sheets("GAR080").Range("B2").Value
Sheets("Pre-Summary").Select
LastRow_summary = Cells(Rows.Count, "A").End(xlUp).Row
LastRow = 156
LastCol = 16
Dim cons_sum() As Variant
ReDim cons_sum(LastRow_summary, 4)
For i = 1 To LastRow_summary Step 1
Sheets("Pre-Summary").Select
cons_sum(i, 1) = Cells(i, 2).Value & "" 'pulls participant
cons_sum(i, 2) = cons_sum(i, 1) & Cells(i, 1) ' participant and gas gate concatenated
If cons_sum(i, 1) = "BOPE" Then
Sheets(cons_sum(i, 1)).Select
cons_sum(i, 3) = WorksheetFunction.Match(cons_sum(i, 2), Sheets(cons_sum(i, 1)).Range("A:A")) ' find participant gas gate combo
cons_sum(i, 4) = Application.Sum(Sheets(cons_sum(i, 1)).Range(Cells(cons_sum(i, 3), 5), Cells(cons_sum(i, 3), 16)))
If cons_sum(i, 4) > 0 Then
Sheets("Pre-Summary").Cells(i, 4).Value = cons_sum(i, 4)
End If
End If
Next i
On Error Resume Next
End Sub

Related

Use match formula in vba to return a row number

I have been trying this for quite sometime and although I get the correct answer but I get an Application-defined or Object defined error.
I have two sheets: Sheet2 and Sheet3. Both the sheets have a column "url". What I want is to get the row number of the url in Sheet2 and get the urls row position printed in
Column C ("Match Row") of Sheet3.
This is the example of the data I am working on.
I get the error in this line
Matchvalue.Formula = "=Match(Worksheets("Sheet3").Cells(i, 2), Worksheets("Sheet2").Range("B:B"), 0)
This is what I've tried:
Dim i As Integer
i = 2
Do While Worksheets("Sheet3").Cells(i, 2) <> ""
Worksheets("Sheet3").Cells(i, 14) =
WorksheetFunction.Match(Worksheets("Sheet3").Cells(i, 2),
Worksheets("Sheet2").Range("B:B"), 0)
i = i + 1
Loop
Try the code below, explanations inside the code's comments:
Option Explicit
Sub MatchUrl()
Dim i As Long
Dim MatchRng As Range
With Worksheets("Sheet2")
' set the match range is "Sheet2"
Set MatchRng = .Range("B1:B" & .Cells(.Rows.Count, "B").End(xlUp).Row)
End With
With Worksheets("Sheet3")
For i = 2 To .Cells(.Rows.Count, "B").End(xlUp).Row
' check if successful match
If Not IsError(Application.Match(.Cells(i, 2), MatchRng, 0)) Then
.Cells(i, 2) = Application.Match(.Cells(i, 2), MatchRng, 0)
Else ' Match failed, raise some kind of error
.Cells(i, 2) = "Url not found in Sheet2!"
End If
Next i
End With
End Sub
i don't use Match so I don't know the Signature of it but :
By escaping Quotes perhaps ... like
Matchvalue.Formula = "=Match(" & Worksheets("Sheet3").Cells(i, 2) & ";B:B;0)"
To escape quotes just double them.
for example Debug.print "Hey ""You"" how are you ?"

Excel VBA : assign formula to multiples dynamic range table in same sheet

I am new and learning Excel VBA. I am now having this problem
There is more than 10 tables in a worksheet (number of tables is not consistent)
The number of columns are consistent but not the rows in each tables
I would like to apply a total row to the end of every table
After that, I will apply the same formula to every table and put the results on the right side of each table
This could be easy but the core problem is that the range is unknown.
- As it is not an actual table in Excel, so I tried to first define the range of the data by creating table for it, then again, I don't have idea on how to create the table without knowing the range.
Below is something I came up with (which is not very "dynamic")
Sub plsWork()
Set u = ThisWorkbook.Worksheets("Sheet2")
Set f = u.Range("A").Find(what:="Name", lookat:=xlPart)
a = f.Address
Set sht = u.Range(a)
'trying to insert this at the end of the table
Total = Sum(u.Offset(2, 1) + u.Offset(3, 1) + u.Offset(4, 1))
If Cells(i, 2) = vbNullString Then 'this is already not applicable as the top 2 row in colB has null string
u.Offset(i, 1).Value = Total
'putting the table name at F2
u.Offset(-2, 5).Value = u.Offset(-3, 0).Value
u.Offset(-2, 6).Value = Total
u.Offset(-1, 5).Value = u.Offset(2, 0).Value
u.Offset(-1, 6).Value = Sum(u.Offset(2, 1) + u.Offset(2, 2) + u.Offset(2, 3))
u.Offset(0, 5).Value = u.Offset(3, 0).Value
u.Offset(0, 6).Value = Sum(u.Offset(3, 1) + u.Offset(3, 2) + u.Offset(3, 3))
u.Offset(1, 5).Value = u.Offset(4, 0).Value
u.Offset(1, 6).Value = Sum(u.Offset(4, 1) + u.Offset(4, 2) + u.Offset(4, 3))
End Sub
Oh, and when I run above code, I got error "Sub or Function not defined" on "SUM"
Here is the image of the tables in a sheet
yellow highlighted is what going to be there after executing the sub.
It was quite easy applying formula in Excel sheet and copy paste the formula to each tables,
but it was tedious, so I try to come out with a vba code to help so that the macro could run based on schedule.
I'm scratching my head and searching to and fro for the past two days,
I still haven't got a clue on how to code this.
So can any expert tell me if this is possible? like without knowing the range?
If so, could you guys shed me with some info on how to achieve this?
Thank you. I really want to know if this can be done or not.
Here is an image of my attempt using provided answer
You may try something like this...
The code below will insert a Total Row for each table which has more than one row and four columns in it.
Sub InsertTotalInEachTable()
Dim ws As Worksheet
Dim rng As Range
Dim i As Integer, r As Long, j As Long
Application.ScreenUpdating = False
Set ws = ActiveSheet
For Each rng In ws.UsedRange.SpecialCells(xlCellTypeConstants, 3).Areas
If rng.Rows.Count > 1 And rng.Columns.Count = 4 Then
j = 2
r = rng.Cells(rng.Rows.Count, 1).Row + 1
Cells(r, rng.Columns(1).Column).Value = "Total"
For i = rng.Columns(2).Column To rng.Columns(2).Column + 2
Cells(r, i).Formula = "=SUM(" & rng.Columns(j).Address & ")"
j = j + 1
Next i
End If
Next rng
Application.ScreenUpdating = True
End Sub

VBA to past certain cell values on different worksheet in predetermined columns

Gods of VBA,
I would like to request your help on some code i can't seem to get working straight.
Purpose,
When a row has a cell Value "x" on row A in sheet 'Dump', i would like to past certain values in Sheet 'test'.
The values that need to be posted on Sheet 'test', are in column B, D, F and L.
Value from column B, Sheet 'Dump' should go to D4, in sheet 'test'.
Value from column D, Sheet 'Dump' should go to C4, in Sheet 'test'.
Value from column F, Sheet 'Dump' should go to A4, in Sheet 'test'.
Value from column L, Sheet 'Dump' should go to E4, in Sheet 'test'.
Ofcourse i'm trying to make the VBA loop as that when multiple rows on Sheet 'Dump' contains the character 'x', it continues from D/C/A/E4 to the next row.
The code I already have working is posted here:
Sub test()
Dim i, LastRow
LastRow = Sheets("Dump").Range("A" & Rows.Count).End(xlUp).Row
Sheets("test").Range("A2:K200").ClearContents
For i = 2 To LastRow
If Sheets("Dump").Cells(i, "A").Value = "x" Then
Sheets("Dump").Range(Cells(i, "B"), Cells(i, "B")).Copy
Destination:=Sheets("test").Range("A" & Rows.Count).End(xlUp).Offset(1)
End If
Next i
End Sub
Have been trying with a lot of different sources of VBA, and some tweaking to it. If i started with a wrong source, or am making some n00b-mistakes, please direct me to what i did wrong. Just trying to learn, while coding.
Tim posted the better way to copy values only but here is what is the problem with your code:
The syntax for copying is
sourceRange.Copy Destination:=destinationRange
The := specifies an option/paramter to the .Copy method. It can be confusing because there are no parentheses around the arguments like you could expect from other languages.
someMethod(argument1, argument2)
would be
someMethod argument1, argument2
if there is nothing else in the line (otherwise you need parentheses).
You can specify what argument you use by naming it and using :=. This is especially useful for optional arguments or to keep your code readable (you might not remember what each argument is in a few months). Some people keep parameters empty but I think it's obvious why something like
someMethod paramName1:=True, paramName4:=False, paramName5:=True
is easier to read than
someMethod True, , , False, True
(I am assuming the parameter names are descriptive like Destination).
The parameters of a function need to be in the same row as the function. To concatenate the rows, remove the linebreak (duh) or place an _ at the end of the line (if it get's to long).
Example with parentheses and linebreaks:
Set someRange = rangeToSearch.Find( _
What:="abc", _
LookIn:=xlValues, _
MatchCase:=True)
Example without parenthesis and linebreaks:
destinationRange.PasteSpecial Paste:=xlPasteValues, skipblanks:=True
You could try the following.
Sub test()
Dim i, LastRow
LastRow = Sheets("Dump").Range("A" & Rows.Count).End(xlUp).Row
Sheets("test").Range("A2:K200").ClearContents
j = 4
For i = 2 To LastRow
If Sheets("Dump").Cells(i, "A").Value = "x" Then
Sheets("test").Cells(j, 4) = Sheets("Dump").Cells(i, 2).Value
Sheets("test").Cells(j, 3) = Sheets("Dump").Cells(i, 3).Value
Sheets("test").Cells(j, 1) = Sheets("Dump").Cells(i, 6).Value
Sheets("test").Cells(j, 5) = Sheets("Dump").Cells(i, 12).Value
j = j + 1
End If
Next i
End Sub
You need a separate way of tracking each row in the test sheet, hence adding j = 4 (because you want to start on row 4).
EDIT
I would define your sheets if you call them a a lot.
Sub test()
Dim i, LastRow, source as Worksheet, dest as Worksheet
Set source = ActiveWorkbook.Sheets("Dump")
Set dest = ActiveWorkbook.Sheets("test")
LastRow = source.Range("A" & Rows.Count).End(xlUp).Row
dest.Range("A2:K200").ClearContents
j = 4
For i = 2 To LastRow
With source
If .Cells(i, "A").Value = "x" Then
dest.Cells(j, 4) = .Cells(i, 2).Value
dest.Cells(j, 3) = .Cells(i, 3).Value
dest.Cells(j, 1) = .Cells(i, 6).Value
dest.Cells(j, 5) = .Cells(i, 12).Value
j = j + 1
End If
End With
Next i
End Sub

How to match strings in cells on Excel, with if/or operators, and delete the rows

Disclaimer: I've never used Visual Basic and I've never made a macro.
I am trying to create a macro in Microsoft Excel 2010 that will delete all rows where neither column G nor column I contain the string "Ohio", "Indiana", or "Kentucky". To clarify, the row should be deleted if the cell does not contain either of those three state names. I want the macro to start at row 6, as rows 1-5 contain vital information. The whole sheet contains over 14000 rows and only ~1.5% of those are actually helpful.
Also, I am looking to be able to reuse this macro, but for other terms (besides Ohio, Indiana, and Kentucky) in other columns (besides G and I).
It may also help me if you can, besides correcting this, explain what exactly I am saying in these lines. Perhaps in Java terms, or Python is okay too. Not necessary, but may help.
Sub DeleteIfNotKYINOH()
Dim i, LastRow
LastRow = Range("G" & Rows.Count).End(xlUp).Row
For i = LastRow To 6 Step -1
I get a type mismatch error on the next line.
If Cells(i, "G").Value = "Ohio" Or "Indiana" Or "Kentucky" Then
Cells(i, "G").Value = True
End If
If Cells(i, "I").Value = "Ohio" Or "Indiana" Or "Kentucky" Then
Cells(i, "I").Value = True
End If
If Cells(i, "G").Value Or Cells(i, "I").Value = False Then
Cells(i, "G").EntireRow.Delete
End If
Next
' DeleteIfNotKYINOH Macro
' Delete all rows that do not contain Ohio, Indiana, or Kentucky, as a state.
'
'
End Sub
There are a few things to consider, it looks like you are on the right track, though, you even got the backwards iteration over the collection (this stumps a lot of people!).
Make sure to declare your variables properly (i and LastRow should probably be Long integer, not unspecified Variant type).
If statements can include Or joins, but have to be like this:
If Cells(i, "G").Value = "Ohio" Or Cells(i, "G").Value = "Indiana" Or Cells(i, "G").Value = "Kentucky"
Since you want to be able to re-use the macro for other strings, of course you could go in and edit each instance of "Ohio" or "Indiana", etc., but that can be tedious and error-prone.
You could do something like this instead to re-use it for a list of any number of states, just change the assignment to the states variable.
Const states as String = "Ohio,Indiana,Kentucky"
Sub TestDeleteIfNot()
Dim i as Long, LastRow as Long
Dim cl as Range
LastRow = Range("G" & Rows.Count).End(xlUp).Row
For i = LastRow To 6 Step -1
With Cells(i, "G")
If Not(InList(.Value, states)) And Not(InList(.Offset(0,2).Value, states))
.EntireRow.Delete
End If
End With
Next
End Sub
This routine calls on a function InList which accepts as its arguments two strings. The first string should be the value being compared, and the second is a comma-delimited "list" of allowable values.
Function InList(strVal as String, list as String) As Boolean
Dim a as Variant
For each a in Split(list, ",")
If strVal = a Then
InList = True
Exit For
End If
Next
End Function
The function converts the list to an array and iterates that against the compare value. It should return False if the value is not found. So then the logic in the calling sub runs this on cells in COlumn G and also Column I, only deleting the row if BOTH tests return False.

vba copy cell values filters out numerical data

I have an issue. I am trying to copy all unique values (numerical and alphanumerical) from a dynamic sheet to another. I found a great script on a forum, which works quickly and have adapted this. The issue is that it seems to filter out all numerical values and for the life of me I cannot see why!?! Can you help?
Sub GetUniqueItems()
Dim vData As Variant, n&, lLastRow&, sMsg$
lLastRow = Worksheets(Worksheets("Summary").Range("A1").Value)._
Cells(Rows.Count, "H").End(xlUp).Row
If lLastRow = 1 Then Exit Sub '//no data
vData = Worksheets(Worksheets("Summary").Range("A1").Value)._
Range("H2:H" & lLastRow)
Dim oColl As New Collection
On Error Resume Next
For n = LBound(vData) To UBound(vData)
oColl.Add vData(n, 1), vData(n, 1)
Next 'n
For n = 1 To oColl.Count
sMsg = oColl(n)
Sheets("Summary").Cells(n + 3, 1).Value = Mid$(sMsg, 1)
Next 'n
End Sub
The key for a Collection item needs to be a string. So change this line:
oColl.Add vData(n, 1), vData(n, 1)
to this:
oColl.Add vData(n, 1), CStr(vData(n, 1))
Also, although you need the On Error Resume Next so the code will skip over any attempts to add duplicates to the collection, you should only use it for that one line. Otherwise you risk masking other errors in your code. (The reason your code didn't have a runtime error was because the On Error Resume Next, in addition to doing it's job of bypassing duplicates, was also skipping over any Adds with numeric Keys.
For that reason, I moved the line to just before the oColl.Add and added On Error Goto 0 just after:
Here's the full routine:
Sub GetUniqueItems()
Dim vData As Variant, n&, lLastRow&, sMsg$
Dim oColl As Collection
lLastRow = Worksheets(Worksheets("Summary").Range("A1").Value).Cells(Rows.Count, "H").End(xlUp).Row
If lLastRow = 1 Then Exit Sub
vData = Worksheets(Worksheets("Summary").Range("A1").Value).Range("H2:H" & lLastRow)
Set oColl = New Collection
For n = LBound(vData) To UBound(vData)
On Error Resume Next
oColl.Add vData(n, 1), CStr(vData(n, 1))
On Error GoTo 0
Next n
For n = 1 To oColl.Count
sMsg = oColl(n)
Sheets("Summary").Cells(n + 3, 1).Value = Mid$(sMsg, 1)
Next n
End Sub
One last thing: you want to avoid statements like Dim oColl As New Collection, and instead declare and set it in two steps as I did. For the reason see the Chip Pearson page and scroll down to "Don't Use Auto-Instancing Object Variables."
I am showing the code below as it may be of interest to the OP, or others, and is an efficient way to obtain a unique list from a column of data.
In Excel 2007 or above we can copy the column and make use of the Remove Duplicates feature to obtain our unique list.
Sub CreateUniqueList()
Dim lLastRow As Long
Dim wsSum As Worksheet
Dim rng As Range
Set wsSum = Worksheets("Summary")
lLastRow = wsSum.Cells(Rows.Count, "H").End(xlUp).Row
If lLastRow = 1 Then Exit Sub
wsSum.Range("H2:H" & lLastRow).Copy wsSum.Cells(4, 1)
wsSum.Range(wsSum.Cells(4, 1), wsSum.Cells(4 + lLastRow - 2, 1)). _
RemoveDuplicates Columns:=1, Header:=xlNo
End Sub
The only slight disadvantage is that we first have to copy the entire column, but this is minor in comparison to the performance increase for a large set of data.