Embedded "IF" formula breaks occasionally, VBA alternative? - vba

I have a very large embedded IF formula that appears to occasionally break for no reason. Opening and closing the page a few times eventually gets it working again. I am wondering if there is a VBA alternative for it. Here is the IF formula I am running.
=IF(ISNUMBER(SEARCH("76210",E125)),"_012_00762_10",IF(ISNUMBER(SEARCH("76220",E125)),"_012_00762_20",IF(ISNUMBER(SEARCH("76900",E125)),"_012_00769_00",IF(ISNUMBER(SEARCH("76901",E125)),"_012_00769_01",IF(ISNUMBER(SEARCH("85702",E125)),"_012_00857_02",IF(ISNUMBER(SEARCH("85710",E125)),"_012_00857_10",IF(ISNUMBER(SEARCH("100800",E125)),"_012_01008_00",IF(ISNUMBER(SEARCH("100900",E125)),"_012_01009_00",IF(ISNUMBER(SEARCH("123100",E125)),"_012_01231_00",IF(ISNUMBER(SEARCH("124600",E125)),"_012_01246_00",IF(ISNUMBER(SEARCH("124601",E125)),"_012_01246_01",IF(ISNUMBER(SEARCH("124640",E125)),"_012_01246_40",IF(ISNUMBER(SEARCH("124641",E125)),"_012_01246_41",IF(ISNUMBER(SEARCH("142301",E125)),"_012_01423_01",IF(ISNUMBER(SEARCH("158801",E125)),"_012_01588_01",IF(ISNUMBER(SEARCH("158900",E125)),"_012_01589_00",IF(ISNUMBER(SEARCH("159203",E125)),"_012_01592_03",IF(ISNUMBER(SEARCH("159303",E125)),"_012_01593_03",IF(ISNUMBER(SEARCH("159401",E125)),"_012_01594_01",IF(ISNUMBER(SEARCH("159410",E125)),"_012_01594_10",IF(ISNUMBER(SEARCH("159420",E125)),"_012_01594_20",IF(ISNUMBER(SEARCH("159501",E125)),"_012_01595_01",IF(ISNUMBER(SEARCH("169000",E125)),"_012_01690_00",IF(ISNUMBER(SEARCH("186900",E125)),"_012_01869_00",IF(ISNUMBER(SEARCH("213200",E125)),"_012_02132_00",IF(ISNUMBER(SEARCH("213300",E125)),"_012_02133_00",IF(ISNUMBER(SEARCH("215400",E125)),"_012_02154_00",IF(ISNUMBER(SEARCH("220100",E125)),"_012_02201_00",IF(ISNUMBER(SEARCH("223800",E125)),"_012_02238_00",IF(ISNUMBER(SEARCH("225600",E125)),"_012_02256_00",IF(ISNUMBER(SEARCH("230700",E125)),"_012_02307_00",IF(ISNUMBER(SEARCH("230701",E125)),"_012_02307_01",IF(ISNUMBER(SEARCH("231800",E125)),"_012_02318_00",IF(ISNUMBER(SEARCH("235000",E125)),"_012_02350_00",IF(ISNUMBER(SEARCH("235020",E125)),"_012_02350_20",IF(ISNUMBER(SEARCH("242000",E125)),"_012_02420_00",IF(ISNUMBER(SEARCH("246400",E125)),"_012_02464_00",IF(ISNUMBER(SEARCH("292900",E125)),"_012_02929_00",""))))))))))))))))))))))))))))))))))))))
Basically it is built so a serial number is scanned and it populates a cell for the users who use this sheet with its results from the search. I am already running one macro in this sheet as well. Here is that...
Option Explicit
Private Sub Worksheet_Change(ByVal Target As Range)
Dim rng As Range
Set rng = Intersect(Range("A2:A500, J2:J500"), Target) ' define range of interest
If Not rng Is Nothing Then ' check it's not "nothing"
If WorksheetFunction.CountA(rng) = rng.Count Then 'check for all of its cells being not empty
On Error GoTo safe_exit 'add error control
Application.EnableEvents = False 'don't do anything until you know something has to be done
rng.Offset(, 1).Value = Date 'write Date next to all relevant changed cells
End If
End If
safe_exit:
Application.EnableEvents = True
End Sub
Maybe there is a better way to build this search using a formula that isn't using embedded IF statements, but i couldn't think of another way to do it. Thanks in advance.

This may be what you're looking for:
=IF(ISNA(MATCH(1,IF(ISERR(SEARCH($A$5:$A$42,$E$125)),0,1),0)),"",INDEX($B$5:$B$42,MATCH(1,IF(ISERR(SEARCH($A$5:$A$42,$E$125)),0,1),0)))
entered as an array formula (CTRL-SHIFT-ENTER).
Here $A$5:$A$42 contains 76210, 76220, ... , 292900 (entered as text, not numbers); and $B$5:$B$42 contains _012_00762_10, _012_00762_20, ... , _012_02929_00.
Hope that helps.

Any time you have to go more than 2 deep on an IF you may want to rethink the usage.
What you can do is build a table from your values. Then reference that table as part of your lookup. Assuming your list of value is in range D8:E45 you could use the formula =VLOOKUP(E125,$D$8:$E$45,2).
The beginning of your table would look like what's seen below. The input result cell is referencing your input value and pulling the match of the second column.
To get your table you can take your source formula and replace (Find and Replace - Ctrl+H) some characters with unique delimiting characters. Then use Text To Columns Alt+D+E and delimit and Copy>Paste special>Transpose to quickly have it close to the format you need.

Related

How to assign macro to a table column in Excel?

Currently I'm creating a macro that reads data from a table column, Asset No. The data is obtained through drop-down menu via data validation from another table in another sheet, DieMaster.
It will then perform Index and Match to find the matching data from DieMaster and insert it into Description column.
In addition, Upon obtaining data, the table column will then copy and paste as value to get the data only. That way the formula won't slow down filter searches.
This is what I have come up with.
Sub convert()
Dim osh As Worksheet
Set osh = ActiveSheet
osh.Range("ProjectEntry[Description]").Formula = "=IF(ISNA(INDEX(DieMaster,MATCH(B4,DieMaster[Asset No],FALSE),2)),"""",INDEX(DieMaster,MATCH(B4,DieMaster[Asset No],FALSE),2))"
osh.Range("ProjectEntry[Description]").Copy
osh.Range("ProjectEntry[Description]").PasteSpecial xlPasteValues
End Sub
I have tested the macro and it works perfectly. However the issue I have now is trying to assign the macro to Asset No column. My plan is to have it so that when a value is selected in a cell within the Asset No column via aforementioned drop-down menu, it will automatically show me the data for description.
Since I cannot assign macro to column using the traditional method, is there an alternative?
You can use the Worksheet_Change Event to accomplish this:
Private Sub Worksheet_Change(Target as Range)
If Not Intersect(Target, Me.ListObjects("ProjectEntry").ListColumns("AssetNo").DataBodyRange) Is Nothing Then
With Me.Range("ProjectEntry[Description]")
.Formula = "=IF(ISNA(INDEX(DieMaster,MATCH(B4,DieMaster[Asset No],FALSE),2)),"""",INDEX(DieMaster,MATCH(B4,DieMaster[Asset No],FALSE),2))"
.Value = .Value
End With
End If
End Sub
My syntax may be slightly off (since I am not on my normal computer), but it will get you close enough to tweak on your own.

Insert variable string into formula

Been working on this for a while, and can't seem to figure it out. I feel like I'm almost there, thanks to different posts in here. Here is what I am trying to do:
I have a dependent list that I would like to change to it's first value based on the selection of the first list. I found a code here, that I modified to work for my sheet, but I had to change all the names in the first list to conform to the Define Name rules(ie. no spaces). This works, and I can leave it at that, but I would prefer to have the spaces in my list. Whenever I use the formulas I was using to change the name shown in the list, the VBA script no longer works. I feel that it is because it is not seeing a change in the target cell, although the value is changing, the formula in the cell is not. Is that an accurate statement? If so, what can I change to make it work the way I want it to? Right now, the dependent list is running off =INDIRECT("$B$5) but I would like it to run off =INDIRECT('Store Data'!$A$23) which contains a VLOOKUP formula.
Here is the VBA script I am using now:
Private Sub Worksheet_Change(ByVal Target As Range)
Dim rng(1) As Range, rng1 As Range
Set rng(0) = Range("B5") 'your primary selection
Set rng(1) = Range("G5") 'your secondary selection range
Application.EnableEvents = False
If Not Intersect(Target, rng(0)) Is Nothing Then 'if you have changed your primary selection
For Each rng1 In rng(1) 'each cell in your secondary selection
i = i + 1
rng1 = Worksheets("SD Names").Range("" & rng(0).Value2)(i, 1) 'gets changed to the nth value in the indirect reference of the primary selection ("TeamA" 's second row is "MemberA2" for example)
Next
End If
Application.EnableEvents = True
End Sub
Hope this is enough detail, thank you for any help!

Range SpecialCells ClearContents clears whole sheet instead

I have a sheet in Excel 2010 which is setup as a pseudo form (I didn't create it, I'm just trying to fix it) so formatting suggests that the user can only enter in certain cells. Depending on certain functionality these areas need to be reset, i.e. cleared although formulae and standard/conditional formatting need to be kept. I have defined each of these cells/ranges as named ranges so I can easily loop through them using the following code: -
Public Sub ResetDetailSheet()
Dim nm As Name
With ThisWorkbook
For Each nm In .Names
If Left(nm.Name, 9) = "nmrDetail" Then
Range(nm.Name).SpecialCells(xlCellTypeConstants).ClearContents
End If
Next
End With
End Sub
For some reason instead of clearing the constants from the specific range it is clearing constants from the entire sheet so I am losing all titles/headings. Formulae and standard/conditional formatting are staying as expected.
What am I doing wrong?!?!
As a test using the immediate window I tried clearing a specific cell, e.g.
Range("G7").SpecialCells(xlCellTypeConstants).ClearContents
But this still cleared all constants from the entire sheet.
What am I missing? I don't understand. Maybe I'm being dumb.
Sorry, I can't upload an example. This place is pretty locked down.
Range({any single cell}).SpecialCells({whatever}) seems to work off the entire sheet.
Range({more than one cell}).SpecialCells({whatever}) seems to work off the specified cells.
So, make sure your range has more than a single cell before you clear it - if the range is only a single cell, then check if it .HasFormula; if that's the case then its .Value isn't a constant:
With ThisWorkbook
For Each nm In .Names
If Left(nm.Name, 9) = "nmrDetail" Then
If nm.RefersToRange.Count > 1 Then
nm.RefersToRange.SpecialCells(xlCellTypeConstants).ClearContents
ElseIf Not nm.RefersToRange.HasFormula Then
nm.RefersToRange.ClearContents
End If
End If
Next
End With
Note that I'm using Name.RefersToRange instead of fetching the range by name off the active sheet.

Intersect not working if target range gets bigger

I am relatively new to VBA and I need help with this please.
I have a private sub within a sheet and I want it to autofill formulas adjacent to a dynamic named range, if the size of the range changes.
(edit) I am pasting data from another worksheet into this one columns A-M. My dynamic range is defined as =OFFSET($A$1,1,0,COUNTA($A:$A)-1,13). The first If statement should exit the sub if there is no data in column M and I had the destination calculating the last row of column M because I want to fill the formulas in N:O so that they cover the same number of rows as column M.
This is my code and it works if the size of the range gets smaller (i.e. if I delete rows from the bottom), but not if it gets bigger and I can't work out why!
Private Sub Worksheet_Change(ByVal Target As Range)
If Me.Range("M2").Value = "" Then
MsgBox "No Data!"
Exit Sub
Else
If Intersect(Target, Me.Range("rngOracleInvoices")) Is Nothing Then
Application.EnableEvents = False
Dim Lrows As Long
Lrows = Me.Cells(Me.Rows.Count, "N").End(xlUp).Row
Me.Range(Me.Cells(3, 14), Me.Cells(Lrows, "O")).ClearContents
Me.Range("N2:O2").AutoFill Destination:=Me.Range("N2:O" & Me.Range("M" & Me.Rows.Count).End(xlUp).Row)
End If
End If
Application.EnableEvents = True
End Sub
I put the last bit into a separate macro to test if it works on its own and for some reason, when I run it, the autofill goes all the way up to row 1 and overwrites the formulas, which is weird because I use that code a lot and it's never done that before. What have I done??!!
Also, if there is a better way to do the autofill I'd appreciate if someone could let me know what it is because I just cobbled that together from bits I found on forums :)
Thanks,
Soph
In this line Me.Range("N2:O2").AutoFill Destination:=Me.Range("N2:O" & Me.Range("M" & Me.Rows.Count).End(xlUp).Row) you calculate your last row on the column M so if it is empty it'll give you 1 and autofill your formula on row 1.
So start by calculating it on the good column (my guess is O)
You can also simply define an Integer variable to test it and if it is inferior to 2, change it back to 2, 3, 4 or whatever you want.
For your dynamic range, we might need some precision.
And for the AutoFill, you could just select manually the range N2:02 and then double-click on the bottom right square (the one you drag to autofill) and it'll autofill as long as there data in adjacent cells! (give it a try ;) )

Get the cell reference of the value found by Excel INDEX function

The Problem
Assume that the active cell contains a formula based on the INDEX function:
=INDEX(myrange, x,y)
I would like to build a macro that locates the value found value by INDEX and moves the focus there, that is a macro changing the active cell to:
Range("myrange").Cells(x,y)
Doing the job without macros (slow but it works)
Apart from trivially moving the selection to myrange and manually counting x rows y and columns, one can:
Copy and paste the formula in another cell as follows:
=CELL("address", INDEX(myrange, x,y))
(that shows the address of the cell matched by INDEX).
Copy the result of the formula above.
Hit F5, Ctrl-V, Enter (paste the copied address in the GoTo dialog).
You are now located on the very cell found by the INDEX function.
Now the challenge is to automate these steps (or similar ones) with a macro.
Tentative macros (not working)
Tentative 1
WorksheetFunction.CELL("address", ActiveCell.Formula)
It doesn't work since CELL for some reason is not part of the members of WorksheetFunction.
Tentative 2
This method involves parsing the INDEX-formula.
Sub GoToIndex()
Dim form As String, rng As String, row As String, col As String
form = ActiveCell.Formula
form = Split(form, "(")(1)
rng = Split(form, ",")(0)
row = Split(form, ",")(1)
col = Split(Split(form, ",")(2), ")")(0)
Range(rng).Cells(row, CInt(col)).Select
End Sub
This method actually works, but only for a simple case, where the main INDEX-formula has no nested subformulas.
Note
Obviously in a real case myrange, x and ycan be both simple values, such as =INDEX(A1:D10, 1,1), or values returned from complex expressions. Typically x, y are the results of a MATCH function.
EDIT
It was discovered that some solutions do not work when myrange is located on a sheet different from that hosting =INDEX(myrange ...).
They are common practice in financial reporting, where some sheets have the main statements whose entries are recalled from others via an INDEX+MATCH formula.
Unfortunately it is just when the found value is located on a "far" report out of sight that you need more the jump-to-the-cell function.
The task could be done in one line much simpler than any other method:
Sub GoToIndex()
Application.Evaluate(ActiveCell.Formula).Select
End Sub
Application.Evaluate(ActiveCell.Formula) returns a range object from which the CELL function gets properties when called from sheets.
EDIT
For navigating from another sheet you should first activate the target sheet:
Option Explicit
Sub GoToIndex()
Dim r As Range
Set r = Application.Evaluate(ActiveCell.Formula)
r.Worksheet.Activate
r.Select
End Sub
Add error handling for a general case:
Option Explicit
Sub GoToIndex()
Dim r As Range
On Error Resume Next ' errors off
Set r = Application.Evaluate(ActiveCell.Formula) ' will work only if the result is a range
On Error GoTo 0 ' errors on
If Not (r Is Nothing) Then
r.Worksheet.Activate
r.Select
End If
End Sub
There are several approaches to select the cell that a formula refers to...
Assume the active cell contains: =INDEX(myrange,x,y).
From the Worksheet, you could try any of these:
Copy the formula from the formula bar and paste into the name box (to the left of the formula bar)
Define the formula as a name, say A. Then type A into the Goto box or (name box)
Insert hyperlink > Existing File or Web page > Address: #INDEX(myrange,x,y)
Adapt the formula to make it a hyperlink: =HYPERLINK("#INDEX(myrange,x,y)")
Or from the VBA editor, either of these should do the trick:
Application.Goto Activecell.FormulaR1C1
Range(Activecell.Formula).Select
Additional Note:
If the cell contains a formula that refers to relative references such as =INDEX(A:A,ROW(),1) the last of these would need some tweaking. (Also see: Excel Evaluate formula error). To allow for this you could try:
Range(Evaluate("cell(""address""," & Mid(ActiveCell.Formula, 2) & ")")).Select
This problem doesn't seem to occur with R1C1 references used in Application.Goto or:
ThisWorkbook.FollowHyperlink "#" & mid(ActiveCell.FormulaR1C1,2)
You could use the MATCH() worksheet function or the VBA FIND() method.
EDIT#1
As you correctly pointed out, INDEX will return a value that may appear many times within the range, but INDEX will always return a value from some fixed spot, say
=INDEX(A1:K100,3,7)
will always give the value in cell G3 so the address is "builtin" to the formula
If, however, we have something like:
=INDEX(A1:K100,Z100,Z101)
Then we would require a macro to parse the formula and evaluate the arguments.
Both #lori_m and #V.B. gave brilliant solutions in their own way almost in parallel.
Very difficult for me to choose the closing answer, but V.B. even created Dropbox test file, so...
Here I just steal the best from parts from them.
'Move to cell found by Index()
Sub GoToIndex()
On Error GoTo ErrorHandler
Application.Goto ActiveCell.FormulaR1C1 ' will work only if the result is a range
Exit Sub
ErrorHandler:
MsgBox ("Active cell does not evaluate to a range")
End Sub
I associated this "jump" macro with CTRL-j and it works like a charm.
If you use balance sheet like worksheets (where INDEX-formulas, selecting entries from other sheets, are very common), I really suggest you to try it.