Too many IF statements Excel / VBA - vba

I have added some sample data in this file to explain my problem a bit better. I have blended out the columns which have no relevance to the formula: https://docs.google.com/spreadsheets/d/1_lq0mYPF2ICiFgehQz_YkOa28JUyamG4G8ObV957onY/edit?usp=sharing
My initial file has one row per contract. I want to end up with one row per customer ID. I need to only keep the most recent contract, so the contract with the end date closest to Now(). If one customer ID has two contracts ending at the same time, I would like to keep the row with the higher monthly invoice amount.
I have been doing this with IF statements but there must be a more efficient way. At the moment the highest number of contracts a customer has with 3, but this will increase in the coming months and my if statement method will no longer be viable.
Here is my current formula.
Range("P2").Select
ActiveCell.FormulaR1C1 = _
"=IF(OR(Status=""in renewing process"",Status=""first contract still running""),""Not Eligable"",IF(Count_of_ID=1,""keep"",IF(Count_of_ID=2,(IF(AND(RC[-15]=R[1]C[-15],MIN(RC[-1],R[1]C[-1])=RC[-1]),""keep"",""delete"")),IF(Count_of_ID=3,IF(AND(RC[-15]=R[1]C[-15],RC[-15]=R[2]C[-15],MIN(RC[-1],R[1]C[-1],R[2]C[-1])=RC[-1]),""keep"",""delete"")))))"
Range("P3").Select
ActiveCell.FormulaR1C1 = _
"=IF(OR(Status=""in renewing process"",Status=""first contract still running""),""Not Eligable"",IF(Count_of_ID=1,""keep"",IF(Count_of_ID=2,(IF(AND(RC[-15]=R[1]C[-15],MIN(RC[-15],R[1]C[-15])=RC[-15]),""keep"",IF(Count_of_ID=2,(IF(AND(RC[-15]=R[-1]C[-15],MIN(RC[-15],R[-1]C[-15])=RC[-15]),""keep"",""delete"")))),IF(Count_of_ID=3,IF(AND(RC[-15]=R[1]C[-15],RC[-15]=R[2]C[" & _
"-15],MIN(RC[-15],R[1]C[-15],R[2]C[-15])=RC[-15]),""keep"",IF(Count_of_ID=3,IF(AND(RC[-15]=R[1]C[-15],RC[-15]=R[-1]C[-15],MIN(RC[-15],R[1]C[-15],R[-1]C[-15])=RC[-15]),""keep"",""delete""))))))))" & _
""
Range("P4").Select
ActiveCell.FormulaR1C1 = _
"=IF(OR(Status=""in renewing process"",Status=""first contract still running""),""Not Eligable"",IF(Count_of_ID=1,""keep"",(IF(AND(Count_of_ID=2,RC[-15]=R[1]C[-15],MIN(RC[-1],R[1]C[-1])=RC[-1]),""keep"",IF(AND(Count_of_ID=2,RC[-15]=R[-1]C[-15],MIN(RC[-1],R[-1]C[-1])=RC[-1]),""keep"",IF(AND(Count_of_ID=3,RC[-15]=R[1]C[-15],RC[-15]=R[2]C[-15],MIN(RC[-1],R[1]C[-1],R[2]C" & _
"[-1])=RC[-1]),""keep"",IF(AND(Count_of_ID=3,RC[-15]=R[1]C[-15],RC[-15]=R[-1]C[-15],MIN(RC[-1],R[1]C[-1],R[-1]C[-1])=RC[-1]),""keep"",IF(AND(Count_of_ID=3,RC[-15]=R[-1]C[-15],RC[-15]=R[-2]C[-15],MIN(RC[-1],R[-1]C[-1],R[-2]C[-1])=RC[-1]),""keep"",""delete""))))))))" & _
""
Range("P4").Select
Selection.AutoFill Destination:=Range("P4:P" & Lastrow)
Any help much appreciated!

I would use Named Formulae. They are not widely used in Excel but very powerful. As an example I reduced
=IF(OR(Status=""in renewing process"",Status=""first contract still running"")
to a single word returning true or false using a named formula. I have used relative addressing, excel handles this for you. The attached image should give you all the information you need to use it. I clicked on B2 and built the formula as normal, tested it then copied it, right clicked >DefineName, gave it a name and pasted the formula into 'refers to'.
To use it then just add the Name into the cell and Excel handles all the relative addressing stuff.

Related

1004 error when trying to run a macro that applies to a table

I have an existing table. I want to add a new column next to it, with a formula in it so that the formula adds a new column and fills in all the values.
This is a two-page worksheet. Table1 is on another page than the table we're working with, but has the master data it draws from.
I recorded a macro of the formula I used to produce this, but when I try to run it, I get
Run time error '1004': Application-defined or object-defined error.
The code I'm using is below:
Range("B2").Select
ActiveCell.FormulaR1C1 = "=IF(INDEX(Table1,MATCH([#Listing],Table1[Property],0)," & _
"MATCH(""Status"",Table1[#Headers],0))=""for sale"",""seller""," & _
"IF(INDEX(Table1,MATCH([#Listing],Table1[Property],0)," & _
"MATCH(""Status"",Table1[#Headers],0))=""for lease"",""landlord""," & _
"IF(INDEX(Table1,MATCH([#Listing],Table1[Property],0)," & _
"MATCH(""Status"",Table1[#Headers],0))=""for sale or lease"",""seller / landlord""" & _
"X(Table1,MATCH([#Listing],Table1[Property],0)," & _
"MATCH(""Base Rent/Mo"",Table1[#Headers],0))>0,""landlord"",""seller""))))"
Range("B1").Select
Things I've checked so far:
All the names match (i.e. Table1 is a valid name, and all the headers are correctly named)
The formula works exacly as I want to if I just type it in and hit 'enter'
I've looked for similar issues here, and the most similar seem to be some problems with Pivot tables, but the solutions don't seem to be applicable to my problem
(The reason I'm using VBA for this is that this is one part of a multi-step process that I'm trying to automate to make it simpler to run a complicated report from a large set of data.)
EDIT: Additional fixes I tried:
Moving the master data table to the same sheet as my 'target' table to see if it would work if the two were on the same worksheet. No go.
When you need to use quotes inside quotes like in "MATCH(""Status"", which I suppose you are trying to output Match("Status" try using "MATCH("&Chr(34)&Status&Chr(34)
Chr(34) outputs the " symbol. Otherwise it would return MATCH(Status (without quotes)
There were two separate issues.
First, I was recording the macro and apparently, Excel will not record formulas longer than a certain length properly. This limit is greater than 407 (the longest formula I got to auto capture), but shorter than 467 characters. A similar problem was discussed on this Mr. Excel post. This also was why part of the "INDEX" was missing, as BruceWayne pointed out.
In addition, it was necessary to switch from .FormulaR1C1 to .Formula to get it to work correctly, as R3uK had suggested.
The final code ended up as follows:
Range("B2").Select
ActiveCell.Formula = _
"=IF(INDEX(Table1,MATCH($A2,Table1[Property],0),MATCH(""Status"",Table1[#Headers],0))=""for sale"",""seller"",IF(INDEX(Table1,MATCH($A2,Table1[Property],0),MATCH(""Status"",Table1[#Headers],0))=""for lease"",""landlord"",IF(INDEX(Table1,MATCH($A2,Table1[Property],0),MATCH(""Status"",Table1[#Headers],0))=""for sale or lease"",""seller / landlord"",IF(INDEX(Table1,MATCH($A2,Table1[Property],0),MATCH(""Base Rent/Mo"",Table1[#Headers],0))>0,""landlord"",""seller""))))"
Range("B3").Select

Excel VBA reference to other workbook through "set" with generic variables, keeping that reference in formulas

Here's my problem. I'm running a central KPI file that has to be updated each week. It is refering to files that have a specific name + the current week that they're in. For example, the KPI refers to a DT1P and MPO file, both ending with the current week.
So, for week 2016-28, the central KPI file should look up the values in MPO2016-28.xlsx and DT1P2016-28.xlsx.
For week 2016-29, the central KPI file should look up the values in MPO2016-29.xlsx and DT1P2016-29.xlsx.
However, when I try to run formulas to do some calculations from those sheets, I have no clue how I can make the week generic (the week is being made in Range("E1") in the KPI file), without just refering to the entire location the file is. How can I solve this?
I have:
Set wb_MPO = Workbooks.Open("\\S007v\MPO" & Range("e1").Value & ".xlsx")
wb_KPI.Activate
Set wb_DT1P = Workbooks.Open("\\S007v\DT1P" & Range("e1").Value & ".xlsx")
wb_KPI.Activate
To define the files that need to be opened.
However, my formulas look like this (there's many more of them, but the principle stays the same, so I won't bother you with them)
Range("E4").FormulaR1C1 = _
"=INDEX('[MPO2016-28.xlsx]CSR MPO'!C10,MATCH(RC[-4],'[MPO2016-28.xlsx]CSR MPO'!C1,0),0)"
Could it be something like:
Range("E4").FormulaR1C1 = _
"=INDEX('[wb_MPO]CSR MPO'!C10,MATCH(RC[-4],'[wb_MPO]CSR MPO'!C1,0),0)"
Or am I totally off?
Thanks in advance for your support :)
You have the right idea, but it would be:
Range("E4").FormulaR1C1 = _
"=INDEX('[" & wb_MPO.Name & "]CSR MPO'!C10,MATCH(RC[-4],'[" & wb_MPO.Name & "]CSR MPO'!C1,0),0)"
This will only work if the workbook you are trying to reference is open though. I feel like there are probably better ways to extract this information than opening the workbook and writing a formula to a cell.

Declaring last row references in Excel formulas through VBA

In an attempt to take one of my rudimentary programs, and develop it more I am in need of help referencing last row references in Excel formulas as well as dragging formulas down consistently to the last row of a document.
To elaborate I am attempting to reference the last row in an ever expanding mapping table in my vlookup formula. The reason I need this is because as this mapping table expands when I am no longer coding this program, I need my vlookup formula which will be generated every time I run the program to adapt to the ever changing size.
Also my more pressing issue is in regard to taking that vlookup formula and being able to drag it down to the last row of a worksheet. The worksheet will be static and the last row will range anywhere from 70,000 rows to 90,000 rows. I am trying to avoid loops in this scenario as this will already be a very demanding formula and I would hate to loop through each row.
Currently my rudimentary code looks like this (this was built as a proof of concept, I understand the current method isn't the most ingenious but it served its initial purpose).
Ath.Cells(1, x) = "Business"
Ath.Cells(2, x).Select
ActiveCell.FormulaR1C1 = "=IFERROR(VLOOKUP(RC[1],Mapping!R2C4:R264C5,2,0),VLOOKUP(LEFT(RC[1],2),Mapping!R1C3:R264C5,3,0))"
Ath.Range("d2").Copy
Range("d2:d90000").Select
ActiveSheet.Paste
Calculate
I namely want to change this
Ath.Range("d2").Copy
Range("d2:d90000").Select
ActiveSheet.Paste
and
ActiveCell.FormulaR1C1 = "=IFERROR(VLOOKUP(RC[1],Mapping!R2C4:R264C5,2,0),VLOOKUP(LEFT(RC[1],2),Mapping!R1C3:R264C5,3,0))"
Perhaps these?
Ath.Range("d2").Copy
Range("d2:d" & Range("D" & Rows.Count).End(xlUp).Row).Paste
ActiveCell.FormulaR1C1 = "=IFERROR(VLOOKUP(RC[1],Mapping!R2C4:R" & Sheets("Mapping").Range(ActiveCell.Offset(Rows.Count, -1)).End(xlUp).Row & "C5,2,0),VLOOKUP(LEFT(RC[1],2),Mapping!R1C3:R" & Sheets("Mapping").Range(ActiveCell.Offset(Rows.Count, -1)).End(xlUp).Row & "C5,3,0))"
Combining Dans answer with work of my own I have developed a solution.
Sub Formulas()
Dim Map As Worksheet
Dim Ath As Worksheet
Dim last As Long
Set Ath = Sheets("Athena IBT TEST")
Set Map = Sheets("Mapping")
last = Map.Cells(Rows.Count, "D").End(xlUp).Row
ActiveCell.FormulaR1C1 = "=IFERROR(VLOOKUP(RC[1],Mapping!R1C1:R" & last & "C4,4,0),IFERROR(VLOOKUP(LEFT(RC[1],3),Mapping!R1C2:R" & last & "C4,3,0),VLOOKUP(LEFT(RC[1],3),Mapping!R1C3:R" & last & "C4,2,0)))"
End Sub

Excel VBA - AdvancedFilter

I am trying to filter a range dynamically in VBA and the VBA I am using is not working but I cannot see a logical reason as to why. To explain, I have a range of data in a sheet entitled "Full Stock Report" the size of which will change but I've set it statically in this example... And I'm trying to filter it by a list of criteria held in a range on a sheet initiated "Spitfire Aval Locations", again this is also dynamic but I've set as static again in this example. This sounds simple to me but the below line of code applies a filter but with no results (I have checked I know there are lots that should appear from this filter).
My second question is related, how does this VBA statement dictate which column in the range is being filtered ? (I fear this may be my issue ....)
Sheets("Full Stock Report").Range("A1:F20623").AdvancedFilter Action:=xlFilterInPlace,
CriteriaRange:=Sheets("Spitfire Aval Locations").range("A2:A228"), Unique:=False
Think I've solved this ... essentially AdvancedFilter requires the criteria to be the same format and same column titles as your data set. Not hugeley helpful to me, but I can bodge it to work.
I also have a hunch that AutoFilter with specified criteria might be a better option...
The column to filter on is the first .Range that you call .AdvancedFilter on. The code you posted filters columns A through F. If you wanted to only filter based on values in column A, it would look more like this:
Sheets("Full Stock Report").Range("A1:A20623").AdvancedFilter _
Action:=xlFilterInPlace, _
CriteriaRange:=Sheets("Spitfire Aval Locations").Range("A2:A228"), _
Unique:=False

Identifying and using Relative variable in excel VBA Macro?

Alright, so I have a spreadsheet, and I want to use this code (and more) but I'm getting problems. I want to take row A and Find the [-x] I should be using (see RC[StartColumn]) and autofill the rest. I then want to be able to repeat the steps for columns A-AF.
So essentially I want to perform the first calculation below, and the second one right next to that, and I want to be able to start it anywhere on the document. I then want to be able to copy that down each row for every column leading up to the first cell I chose.
Problem is I'm incredibly new to Macros, I put that together by observing my record macro results and playing around a little, but this is becoming too advanced for my current level and I want to finish this by today if possible. Could someone please show me the way, in a way that could teach me so I could do this on my own in the future?
Sub ConcatenateStep1()
Dim StartColumn As Integer
StartColumn = 1 - ActiveCell.Column
ActiveCell.FormulaR1C1 = _
"=IF(AND(RC14=R[1]C14),IF(OR(ISNUMBER(FIND(LOWER(RC[StartColumn]),LOWER(R[1]C[StartColumn]),1)),ISNUMBER(FIND(LOWER(R[1]C[StartColumn]),LOWER(RC[StartColumn]),1))),IF(LEN(RC[StartColumn])<LEN(R[1]C[StartColumn]),R[1]C[StartColumn],RC[StartColumn]),CONCATENATE(RC[StartColumn],"", "",R[1]C[StartColumn])),IF(AND(RC14<>R[1]C14,RC14<>R[-1]C14),RC[StartColumn],"" ""))"
Range("AK2").Select
Set SourceRange = Range("AK2")
Set fillRange = Range("AK2:AK22000")
SourceRange.AutoFill Destination:=fillRange
Range("AL2").Select
ActiveCell.FormulaR1C1 = _
"=IF(AND(RC14=R[1]C14,RC14<>R[-1]C14),IF(OR(R[1]C[-1]="" "",ISNUMBER(FIND(LOWER(RC[-1]),LOWER(R[1]C[-1]),1)),ISNUMBER(FIND(LOWER(R[1]C[-1]),LOWER(RC[-1]),1))),IF(LEN(RC[-1])<LEN(R[1]C[-1]),R[1]C[-1],RC[-1]),CONCATENATE(RC[-1],"", "",R[1]C[-1])),IF(AND(RC14<>R[1]C14,RC14<>R[-1]C14),RC[-1],"" ""))"
Set SourceRange = Range("AL2")
Set fillRange = Range("AL2:AL22000")
SourceRange.AutoFill Destination:=fillRange
Range("AL2").Select
End Sub
This is only a partial answer.
Are these incredibly long and complicated formulae the best way of achieving whatever you want to achieving?
There are two errors in the first formula that prevent it being accepted by Excel when the macro attempts to place it in a cell.
The formula is a long string containing StartColumn. But StartColumn is a variable. You need to replace every occurence of StartColumn with " & StartColumn & ".
Within a string each double quote required in the result must be replaced by two double quotes. Within CONCATENATE(RC[StartColumn],"", "",R[1]C[StartColumn]) the double quotes have not been doubled although those at the end of the formula have.
Excel will accept the following (which means it is syntactically correct) although I do not understand its purpose so it may be logically incorrect:
ActiveCell.FormulaR1C1 = _
"=IF(AND(RC14=R[1]C14),IF(OR(ISNUMBER(FIND(LOWER(RC[" & StartColumn & _
"]),LOWER(R[1]C[" & StartColumn & "]),1)),ISNUMBER(FIND(LOWER(R[1]C[" & _
StartColumn & "]),LOWER(RC[" & StartColumn & "]),1))),IF(LEN(RC[" & _
StartColumn & "])<LEN(R[1]C[" & StartColumn & "]),R[1]C[" & StartColumn & _
"],RC[" & StartColumn & "]),CONCATENATE(RC[" & StartColumn & _
"],"""", """",R[1]C[" & StartColumn & _
"])),IF(AND(RC14<>R[1]C14,RC14<>R[-1]C14),RC[" & StartColumn & "],"" ""))"
If I have parsed this formula correctly, CONCATENATE(RC[StartColumn],"", "",R[1]C[StartColumn]) has the same output as CONCATENATE(RC[StartColumn],R[1]C[StartColumn])
I agree with MP24, you must clarify your question before I or someone else can help you further. The Macro Recorder records syntactically correct VBA but it is not good VBA. It does not know your objective so records individual keyboard actions when a skilled programmer would combine them. It is almost never a good idea to use Select or to work with the ActiveCell but the Macro Recorder has no choice but to do so.
Part 2
Although Excel allowed the macro to place the first forumula, it later complained the formula contains a circular reference. Its gone midnight here, I will look at it tomorrow.
Part 3
Having looked at your first formula again, I believe I incorrectly parsed it last night. I now believe the concatenate function is correct. Also it appears that the circular reference depends on the location of the ActiveCell when the macro starts. I was able to clear this error by selecting a different start position.
My current attempt to parse your formula gives:
Outer: "=IF(X1,X2,X3)"
X1: AND(RC14=R[1]C14)
X2: IF(X4,X5,X6)
X3: IF(X7,RC[StartColumn],"" "")
X4: OR(X8,X9)
X5: IF(X10,R[1]C[StartColumn],RC[StartColumn])
X6: CONCATENATE(RC[StartColumn],"", "",R[1]C[StartColumn])
X7: AND(RC14<>R[1]C14,RC14<>R[-1]C14)
X8: ISNUMBER(X11)
X9: ISNUMBER(X12)
X10: LEN(RC[StartColumn])<LEN(R[1]C[StartColumn])
X11: FIND(LOWER(RC[StartColumn]),LOWER(R[1]C[StartColumn]),1)
X12: FIND(LOWER(R[1]C[StartColumn]),LOWER(RC[StartColumn]),1)
I have used macros to place formulae so they can later give changing results as the user enters values. Do you want changing results? If not, replacing this formula with VBA would almost certainly give more understandable and more maintainable code.
I do not approve of formulae like this. I am aware that many people use complex formulae successfully and are pleased with the results. However, I have spent too much of my life picking up the pieces left by over clever people who have moved on. How long does it take to get a formula like this correct? How long does it take to amend when six or twelve months later a slightly different result is required? Too often, in my experience, the person who has to amend it six or twelve months later is not the original author. With VBA the author can leave comments saying what the code is doing but with formulae the author cannot. Over clever authors don't always leave the comments they should but at least VBA code is usually easier to decode than a complex formula.