Excel VBA ElseIf query - vba

The following little piece of vba returns the required value in cell B2. I thought by changing the Range to ("A:A") and ("B:B") I could get it to run through an entire spreadsheet. However it fails to run when I try this. The full list has 15 countries so an IF function copied down column B won't work.
Any help would be much appreciated.
Thanks in advance.
Sub test()
Dim Country As String, ID As Integer
Country = Range("A2").Value
If Country = "France" Then
ID = 1
ElseIf Country = "Germany" Then
ID = 2
ElseIf Country = "Spain" Then
ID = 3
ElseIf Country = "Italy" Then
ID = 4
Else
Code = 0
End If
Range("B2").Value = ID
End sub

Commenter above is correct, you should look into using the select statement, but I'd be a liar if I said I didn't use a bunch of ElseIf statements when I'm feeling lazy... Anyway, I'm assuming you want this to check the value in column A and return the ID code for each cell in A in column B. What you need is a for each loop... try this:
Sub test()
Dim Country As String, ID As Integer
Dim Rng As Range
Dim Cel As Range
Set Rng = Range("A:A")
For Each Cel In Rng
Country = Cel.Value
If Country = "France" Then
ID = 1
ElseIf Country = "Germany" Then
ID = 2
ElseIf Country = "Spain" Then
ID = 3
ElseIf Country = "Italy" Then
ID = 4
End If
Cel.Offset(0, 1).Value = ID
Next Cel
End Sub
So what you're doing is looping through each Cell (Cel) in the Range (Rng) of column A. When you return your ID, you're just offseting the Cell (Cel) from column A to the Cell in column B (Cel.Offset(0, 1)).
This will work, but it might be kinda slow because it will cycle through all ranges. What you could also do is find the last row somehow. There are more interesting ways to do it, but if I were you I'd just loop through using an integer counter:
Sub test()
Dim Country As String, ID As Integer
Dim intCounter As Integer
intCounter = 1
Do Until Cells(intCounter, 1).Value = ""
Country = Cells(intCounter, 1).Value
If Country = "France" Then
ID = 1
ElseIf Country = "Germany" Then
ID = 2
ElseIf Country = "Spain" Then
ID = 3
ElseIf Country = "Italy" Then
ID = 4
End If
Cells(intCounter, 2).Value = ID
intCounter = intCounter + 1
Loop
End Sub
The above would have the exact same results, but you wouldn't have to go through every cell in column A, only the cells that are populated.
Edit:
Since it was mentioned bellow, I thought I'd toss in an example of how to do this with a collection:
Sub test()
Dim intCounter As Integer
Dim countries As Collection
Set countries = New Collection
'Here is where you'd add all your country codes:
countries.Add 1, "France"
countries.Add 2, "Germany"
countries.Add 3, "Spain"
countries.Add 4, "Italy"
countries.Add 232, "Ireland"
'Now just loop through
intCounter = 1
Do Until Cells(intCounter, 1).Value = ""
'Next line returns the numeric value (the item) from countries based
'on the value contained in the cell to the left in column A and writes
'it to column B.
Cells(intCounter, 2).Value = countries(Cells(intCounter, 1).Value)
intCounter = intCounter + 1
Loop
End Sub
That is actually how I'd perform this kind of task. I'd also consider placing On Error Resume Next before the Cells(intCounter, 2).value = countries... line, and On Error GoTo 0 after that line. This will skip any cells that had a country string not contained in the collection. The on error resume next statement simply tells the compiler to ignore the next error it finds. Since we are only looking for an error on that next line, we turn error handling off using the on error goto 0 line on the next line. It is handy when using collections. Based on these fundamentals you can effectively "test" a collection for an item using some creativity. Depending on how reliable I thoguht the input on the worksheet was, I might also use Trim() on the string returned from the cell in column A to remove any accidental extra spaces.

You should probably use a For loop. Take a look at Select Case statement as well, it's better than multiple If.

Something like this should be easier for you to maintain
Sub Test()
Dim ID As Integer
Dim Country As Object
Set Country = CreateObject("Scripting.Dictionary")
Country.Add "France", 1
Country.Add "Germany", 2
Country.Add "Spain", 3
Country.Add "Italy", 4
For Each Cell In Range("A2:A7")
If Country.Exists(Cell.Value) Then
Cell.Offset(0, 1).Value = Country(Cell.Value)
Else
Cell.Offset(0, 1).Value = 0
End If
Next
Country.RemoveAll
Set Country = Nothing
End Sub

Related

VBA match 6 Criteria

The script fills an array from a sheet called "Tigers" with 6 strings. Then it is supposed to compare that array to a differnt sheet titled "Elephants" and tell me if it finds an exact match. The troublesome code is found at the Application.Match method
Any help understanding how to correctly script a match with multiple values would be appreciated.
Sub matchData()
Dim arrCompare(5) As Variant
Dim intRow As Integer
Dim varRes As Variant
Set sht = ActiveSheet
Set shtTigers = Worksheets("Tigers").Range("A2:A100")
Set shtElephants = Worksheets("Elephants").Range("A2:A100")
Sheets("Elephants").Activate
For intRow = 2 To 100
arrCompare(0) = Worksheets("Elephants").Cells(intRow, 1).Value
arrCompare(1) = Worksheets("Elephants").Cells(intRow, 2).Value
arrCompare(2) = Worksheets("Elephants").Cells(intRow, 4).Value
arrCompare(3) = Worksheets("Elephants").Cells(intRow, 5).Value
arrCompare(4) = Worksheets("Elephants").Cells(intRow, 7).Value
arrCompare(5) = Worksheets("Elephants").Cells(intRow, 9).Value
'compare all 6 strings in array against Elephant sheet rows for a match
varRes = Application.Match(arrCompare(), shtTigers, 0)
'also tried
'varRes = Application.Match(((arrCompare(0))*((arrCompare(1))*((arrCompare(2)) * ((arrCompare(3)) * ((arrCompare(4)) * ((arrCompare(5))*((arrCompare(6)),shtTigers, 0)
'messagebox just gives a Error 13 or 2042 for varRes
MsgBox ("varRes = " & varRes)
Next
End Sub
Match requires a single lookup value but you're trying to pass the whole array. Iterate one element at at time instead:
Dim counter as Integer
For x = 0 to 5
If Not IsError(Application.Match(arrCompare(x), shtTigers, 0)) Then
counter = counter + 1
End If
Next x
If counter = 6 Then Debug.Print "Matches found"

VLookup in Excel vba not working

Excel File Format
I have master data table with attribute ID which is unique and unit.
I have another table where I add the product values associated with the attributes.
If the units match based on the attribute ID, then the cells are highlighted in green else red.
To achieve the above, I have written some basic code, but it doesn't seem to work. The vlookup works normally but using vba it just exits. Please see image for example and code below. Column a and b contains attribute master data and column d to g contain product attribute values.
Eg. For product p1, value 'IN' is valid for attribute 'A1' but 'm' is not valid.
Also, there can be multiple units separated by comma for each attribute. Need help to solve issue.
Code:
Sub UnitCheck()
Dim AttrIDrange As range, AttrIDcell As range
Dim attrID
Dim Lookup_Range As range
Dim I, J As Variant
Dim UNIT As Variant
Set Lookup_Range = range("A2:B4")
Set AttrIDrange = range("E1:G1")
For Each AttrIDcell In AttrIDrange
attrID = AttrIDcell.Value
For I = 2 To 3
For J = 5 To 7
If Application.WorksheetFunction.VLookup(attrID, Lookup_Range, 2, False) = UNIT Then
Worksheets("Sheet4").Cells(I, J).Font.Color = vbGreen
Else
Worksheets("Sheet4").Cells(I, J).Font.Color = vbRed
End If
Next
Next
Next
End Sub
New Scenario:
If there are multiple units defined for any attribute, then even if only 1 of the unit is present in the product value, it should be highlighed in green. If 1 is correct and rest are incorrect then since it is partially correct, it should be highlighted in red or even yellow. Please see image. Need help for this scenario.
Scenario 2 Image
When cycling through the I and J loops, you need to set UNIT to something before you can compare it to the results passed back from the worksheet VLOOKUP function.
Sub UnitCheck()
Dim AttrIDrange As Range, AttrIDcell As Range
Dim attrID
Dim Lookup_Range As Range
Dim I As Long, J As Long
Dim UNIT As Variant
Dim bCHECK_P1
With Worksheets("Sheet4")
bCHECK_P1 = False
Set Lookup_Range = .Range("A2:B4")
Set AttrIDrange = .Range("E1:G1")
For Each AttrIDcell In AttrIDrange '.Range("E1:G1")
attrID = AttrIDcell.Value
For I = 2 To 3
UNIT = AttrIDcell.Offset(I - 1, 0).Value '<~~ set UNIT here!
For J = 5 To 7
.Cells(I, J).Font.Color = xlAutomatic
If Application.WorksheetFunction.VLookup(attrID, Lookup_Range, 2, False) = UNIT Then
.Cells(I, J).Font.Color = vbGreen
Else
.Cells(I, J).Font.Color = vbRed
End If
Next
Next
Next
End With
End Sub
Here's something that I think does what the OP is asking. Also, this replaces the triple For/next and VLookUp with a dictionary which is hopefully more amenable.
In regard to the OP, I see two fundamental problems, UNIT is never assigned, but also, it sounds like the = operator isn't correct... instead, it sounds like a lookup value must be confirmed present in a comma separated string. The code below uses InStr to check if the lookup value is present.
Sub UnitCheck()
Application.ScreenUpdating = False
Dim UNIT As String
Dim R, c, AttrID As Integer
Dim ProdRange, ProdCell As Range
Set ProdRange = Range("E2:G4")
'Assign LookUp values to array
Dim LookUpArray(), ProdAttrIDArray() As Variant
LookUpArray = Range("A2:B4").Value2
'Create dictionary from Lookup values
Set D = CreateObject("Scripting.Dictionary")
For R = 1 To UBound(LookUpArray)
D.Add LookUpArray(R, 1), LookUpArray(R, 2)
Next
'Loop through product table
For Each ProdCell In ProdRange
'Get attribute ID from row 1 of corresponding column
AttrID = Cells(1, ProdCell.Column).Value2
If D(AttrID) <> Empty Then
'If AttrID found in LookUp Dictionary then get UNIT from it
UNIT = D(AttrID)
'If UNIT found in product cell then color cell green, else red
If (InStr(1, ProdCell.Value2, UNIT) > 0) Then
ProdCell.Interior.Color = vbGreen
Else
ProdCell.Interior.Color = vbRed
End If
End If
Next
End Sub
The result is this
New Scenario
Ok, I think this will cover your new scenario. Notice that I also added Trim() and VBTextCompare so that spaces are ignored and the comparison is case-insensitive. I'm not sure if you want that behavior or not. Also, notice that the order of units does not matter. For example, "IN, km" matches "KM, IN" because the spaces are ignored, capitalization is ignored, and the order is ignored.
Sub UnitCheck()
Application.ScreenUpdating = False
Dim UNIT, PUnits() As String
Dim r, c, AttrID, i, n As Integer
Dim ProdRange, ProdCell As Range
Set ProdRange = Range("E2:G3")
'Assign LookUp values to array
Dim LookUpArray(), ProdAttrIDArray() As Variant
LookUpArray = Range("A2:B4").Value2
'Create dictionary from Lookup values
Set D = CreateObject("Scripting.Dictionary")
For r = 1 To UBound(LookUpArray)
D.Add LookUpArray(r, 1), LookUpArray(r, 2)
Next
'Loop through product table
For Each ProdCell In ProdRange
'Get attribute ID from row 1 of correspdniong column
AttrID = Cells(1, ProdCell.Column).Value2
If D(AttrID) <> Empty Then
'If AttrID found in LoopUp Dictionary then get UNIT from it
UNIT = D(AttrID)
PUnits = Split(ProdCell.Value2, ",")
'reset counter
n = 0
'Count the number of product units found in the lookup value
For i = 0 To UBound(PUnits)
If InStr(1, Trim(UNIT), Trim(PUnits(i)), vbTextCompare) > 0 Then
n = n + 1
End If
Next
'prevent division by zero
If i = 0 Then i = 1
'select action based on percent matched
Select Case n / i
Case Is >= 1
ProdCell.Interior.Color = vbGreen
Case Is > 0
ProdCell.Interior.Color = vbYellow
Case Else
ProdCell.Interior.Color = vbRed
End Select
End If
Next
End Sub

Excel vba executing crash

I have the following function to run on a large excel ark with 60k rows:
Private Sub mySub()
Dim intRowA As Long
Dim intRowB As Long
Application.ScreenUpdating = False
Range("W1").EntireColumn.Insert
For intRowA = 2 To ActiveSheet.UsedRange.Rows.Count
If Cells(intRowA, 6).Value = "C" Then
For intRowB = 2 To ActiveSheet.UsedRange.Rows.Count
If Cells(intRowB, 6).Value = "P" Then
If Cells(intRowA, 4).Value = Cells(intRowB, 4).Value And Cells(intRowA, 7).Value = Cells(intRowB, 7).Value Then
Cells(intRowA, 23).Value = "Matched"
Cells(intRowB, 23).Value = "Matched"
End If
End If
DoEvents
Next
End If
Next
For intRowA = ActiveSheet.UsedRange.Rows.Count To 2 Step -1
If Cells(intRowA, 23).Value <> "Matched" Then
Rows(intRowA).Delete shift:=xlShiftUp
End If
Next
Range("W1").EntireColumn.Delete
Application.ScreenUpdating = True
End Sub
The idea to check where F columns are C and match them up with all F
Rows that are value P Then at the end Delete all that does not match
The problem with this code as far as i can see is that it runs the 60k rows 60K times. which makes my script crash. i am unsure how to improve it and thought that you guys might be able to see through this?
You're coming at this problem from the wrong direction - what makes a row distinct isn't whether column F has a 'C' or a 'P', it's whether the values in columns 'D' and 'G' match.
The way to approach this is to collect 2 lists of rows with every distinct combination of 'D' and 'G' - one for rows with a 'C' in column F and one for rows with a 'P' in column F. Then, go through all of the distinct values for the 'C's and match based on the distinct combination. Something like this (requires a reference to Microsoft Scripting Runtime):
Private Sub mySub()
Dim sheet As Worksheet
Dim c_rows As Dictionary
Dim p_rows As Dictionary
Set sheet = ActiveSheet
Set c_rows = New Dictionary
Set p_rows = New Dictionary
Dim current As Long
Dim key As Variant
'Collect all of the data based on keys of columns 'D' and 'G'
For current = 2 To sheet.UsedRange.Rows.Count
key = sheet.Cells(current, 4) & vbTab & sheet.Cells(current, 7)
'Stuff the row in the appropriate dictionary based on column 'F'
If sheet.Cells(current, 6).Value = "C" Then
If Not c_rows.Exists(key) Then
c_rows.Add key, New Collection
End If
c_rows.Item(key).Add current
ElseIf sheet.Cells(current, 6).Value = "P" Then
If Not p_rows.Exists(key) Then
p_rows.Add key, New Collection
End If
p_rows.Item(key).Add current
End If
Next current
sheet.Range("W1").EntireColumn.Insert
'Now filter out the matching Ps that have keys in the C Dictionary:
For Each key In c_rows.Keys
If p_rows.Exists(key) Then
Dim match As Variant
For Each match In p_rows(key)
sheet.Cells(match, 23).Value = "Matched"
Next
End If
Next key
For current = sheet.UsedRange.Rows.Count To 2 Step -1
If sheet.Cells(current, 23).Value = "Matched" Then
sheet.Rows(current).Delete xlShiftUp
End If
Next
sheet.Range("W1").EntireColumn.Delete
End Sub
I agree it is the 60k x 60k loop causing the issue. You can make the loop more efficient a few different ways:
1) Run through the loop and delete all rows where column F doesn't equal C or P beforehand. This may solve the issue outright if there aren't that many rows that contain C or P.
2) Loop through all the rows once and store the necessary row numbers in an array or collection. Then do whatever you need done with the rows separately. For example:
Dim intRow As Long
Dim cCollection As New Collection
Dim pCollection As New Collection
For intRow = 2 To ActiveSheet.UsedRange.Rows.Count
If Cells(intRow, 6).Value = "C" Then
cCollection.Add (intRow)
ElseIf Cells(intRow, 6).Value = "P" Then
pCollection.Add (intRow)
End If
Next
Dim i As Integer
For i = 1 To cCollection.Count
' do something with cCollection(i)
Next
' multiple ways to loop through the collection...
Dim r As Variant
For Each r In pCollection
'do something with pCollection(r)
Next r

Need help improving my VBA loop

I have an Excel Worksheet consisting of two columns, one of which is filled with strings and the other is emtpy. I would like to use VBA to assign the value of the cells in the empty column based on the value of the adjacent string in the other column.
I have the following code:
Dim regexAdmin As Object
Set regexAdmin = CreateObject("VBScript.RegExp")
regexAdmin.IgnoreCase = True
regexAdmin.Pattern = "Admin"
Dim i As Integer
For i = 1 To 10 'let's say there is 10 rows
Dim j As Integer
For j = 1 To 2
If regexAdmin.test(Cells(i, j).Value) Then
Cells(i, j + 1).Value = "Exploitation"
End If
Next j
Next i
The problem is that when using this loop for a big amount of data, it takes way too long to work and, most of the time, it simply crashes Excel.
Anyone knows a better way to this?
You have an unnecessary loop, where you test the just completed column (j) too. Dropping that should improve the speed by 10-50%
Dim regexAdmin As Object
Set regexAdmin = CreateObject("VBScript.RegExp")
regexAdmin.IgnoreCase = True
regexAdmin.Pattern = "Admin"
Dim i As Integer
For i = 1 To 10 'let's say there is 10 rows
If regexAdmin.test(Cells(i, 1).Value) Then
Cells(i, 1).offset(0,1).Value = "Exploitation"
End If
Next i
If the regex pattern really is simply "Admin", then you could also just use a worksheet formula for this, instead of writing a macro. The formula, which you'd place next to the text column (assuming your string/num col is A) would be:
=IF(NOT(ISERR(FIND("Admin",A1))),"Exploitation","")
In general, if it can be done with a formula, then you'd be better off doing it so. it's easier to maintain.
Try this:
Public Sub ProcessUsers()
Dim regexAdmin As Object
Set regexAdmin = CreateObject("VBScript.RegExp")
regexAdmin.IgnoreCase = True
regexAdmin.Pattern = "Admin"
Dim r As Range, N As Integer, i As Integer
Set r = Range("A1") '1st row is headers
N = CountRows(r) - 1 'Count data rows
Dim inputs() As Variant, outputs() As Variant
inputs = r.Offset(1, 0).Resize(N, 1) ' Get all rows and 1 columns
ReDim outputs(1 To N, 1 To 1)
For i = 1 To N
If regexAdmin.test(inputs(i, 1)) Then
outputs(i, 1) = "Exploitation"
End If
Next i
'Output values
r.Offset(1, 1).Resize(N, 1).Value = outputs
End Sub
Public Function CountRows(ByRef r As Range) As Long
If IsEmpty(r) Then
CountRows = 0
ElseIf IsEmpty(r.Offset(1, 0)) Then
CountRows = 1
Else
CountRows = r.Worksheet.Range(r, r.End(xlDown)).Rows.Count
End If
End Function

Excel VBA Loop on columns

when we are going to do a loop in the rows, we can use code like the following:
i = 1
Do
Range("E" & i & ":D" & i).Select
i = i + 1
Loop Until i > 10
but what if we want to do a loop on a column?
Can we use the same method as above?
while the columns in Excel is a complex such as A, B, C, ..., Y, Z, AA, AB, AC, ..., etc.
problems will arise between loop from the "Z" to the "AA".
how we do looping alphabet column from "A" to "Z" and then continued into "AA", "AB" and so on
is there anything that can help?
Yes, let's use Select as an example
sample code: Columns("A").select
How to loop through Columns:
Method 1: (You can use index to replace the Excel Address)
For i = 1 to 100
Columns(i).Select
next i
Method 2: (Using the address)
For i = 1 To 100
Columns(Columns(i).Address).Select
Next i
EDIT:
Strip the Column for OP
columnString = Replace(Split(Columns(27).Address, ":")(0), "$", "")
e.g. you want to get the 27th Column --> AA, you can get it this way
Another method to try out.
Also select could be replaced when you set the initial column into a Range object. Performance wise it helps.
Dim rng as Range
Set rng = WorkSheets(1).Range("A1") '-- you may change the sheet name according to yours.
'-- here is your loop
i = 1
Do
'-- do something: e.g. show the address of the column that you are currently in
Msgbox rng.offset(0,i).Address
i = i + 1
Loop Until i > 10
** Two methods to get the column name using column number**
Split()
code
colName = Split(Range.Offset(0,i).Address, "$")(1)
String manipulation:
code
Function myColName(colNum as Long) as String
myColName = Left(Range(0, colNum).Address(False, False), _
1 - (colNum > 10))
End Function
If you want to stick with the same sort of loop then this will work:
Option Explicit
Sub selectColumns()
Dim topSelection As Integer
Dim endSelection As Integer
topSelection = 2
endSelection = 10
Dim columnSelected As Integer
columnSelected = 1
Do
With Excel.ThisWorkbook.ActiveSheet
.Range(.Cells(columnSelected, columnSelected), .Cells(endSelection, columnSelected)).Select
End With
columnSelected = columnSelected + 1
Loop Until columnSelected > 10
End Sub
EDIT
If in reality you just want to loop through every cell in an area of the spreadsheet then use something like this:
Sub loopThroughCells()
'=============
'this is the starting point
Dim rwMin As Integer
Dim colMin As Integer
rwMin = 2
colMin = 2
'=============
'=============
'this is the ending point
Dim rwMax As Integer
Dim colMax As Integer
rwMax = 10
colMax = 5
'=============
'=============
'iterator
Dim rwIndex As Integer
Dim colIndex As Integer
'=============
For rwIndex = rwMin To rwMax
For colIndex = colMin To colMax
Cells(rwIndex, colIndex).Select
Next colIndex
Next rwIndex
End Sub
Just use the Cells function and loop thru columns.
Cells(Row,Column)