I'd like to use something like the EVALUATE-Function in Excel for if-statements.
I've got the following issue: I'd like to use Excel to validate my data. I've got three sheets:
the real data I'd like to check. Each row represents a customer and each column some data. The columns have specific names like “age”, “name”, …
the description of the checks I’d like to perform. Each row represents one check and I’ve got 3 columns: 1 check_id – an identifier of each check; 2 check_desc – a description of the check that every normal person can understand like “Age below 18”; 3 rule – the Excel Formula as a string like If(age<18, “error”, “no error”)
the place where sheet 1 and 2 should come together. Each row should represent one customer and each column one check.
Now, if I’ve got for example check_1 “If(age<18, “error”, “no error”)” and the customer data 10 and 20, then the check for the first customer should fire and the check for the second shouldn’t.
If the data is changed, and the age is set from 10 to 18, then everything should be fine, or if the rule is changes to “If(age<21, “error”, “no error”)” then the new condition should be applied to all data.
Is something like this possible?
With the evaluate function only ‘simple’ formulas work.
Thanks in advance,
Martin
Attached you can find the
Excel-Sample File
You will definitely need some VBA here. Make a custom EVAL function:
Public Function EVAL(ByRef Rng As Range, Formula As String) As Variant
Dim RngAddress As String
RngAddress = "'" & Rng.Parent.Name & "'!" & Rng.Address(External:=False)
EVAL = Evaluate(Replace(Formula, "$", RngAddress))
End Function
Then you can easily evaluate your values with formulas passed as text ($ is for parameter):
=EVAL(A1, "IF($<21,""error"",""no error"")")
(note the escaped double quotes). But you would rather pass formula from another cell - then you can specify formula in cell with single quotes:
IF($<21,"error","no error")
=EVAL(A1, B1)
I personally would rename check_desc!B2 to "Check_1" (named range) and then refer to that. You can use the INDIRECT function as well once you've renamed your column header "Check_1" as well.
Note: the value in this case should be "18" instead of "age below 18". You can of course change the number format to "age below "0.
If the relations are changing too I would insert a table that would have uniform formulae changing in each cell when changed in one cell.
The only issue you would then face is the non-expanding nature of your table. You can use VBA for this, but maintaining formulae increases your accountability even if it would be slightly easier not to deal with nasty nested functions.
Related
I am trying to store all the values of an excel column in an array.
set rangeDate to {value of range "A14:A100"}
repeat with date in rangeDate
if (date as string is equal to "01/01/2001") then
log "It works"
end if
end repeat
In my Excel I do have an exact date of 01/01/2001 formatted in the specified columns. When I remove the range and it is just cell A14 (where the date is) it works. But when I include the range A14:A100 it doesn't work.
I am new to applescript, I guess that it doesn't store the values as array values and instead a string object? Any help would be appreciated
You have 4 issues :
1) value of range should not be between {}, but between ()
2) 'Date' is a reserved word in Applescript, so you should not use it as the variable in the loop. I replaced it with 'myDate'.
3) instead of converting your date to string to compare with "01/01/2001", it is quicker to keep comparing 2 dates, and then, compare with the date "01/01/2001"
4) I think it is a bug (at least with my Excel version), but the rangeDate variable is not a list of dates as expected, but for me a list of list : {{01/02/01},{02/02/01},………} Therefore, each member of 'rangeDate' is not a date, but a list made on one item which is a date ! I am not sure, but it could also be that range definition could be a list of ranges... So I am using item 1 of sub list.
Anyway, script bellow is working :
tell application "Microsoft Excel"
activate
tell active sheet of document 1
set rangeDate to (value of range "A14:A100")
repeat with mydate in rangeDate
set TheDate to item 1 of mydate
if TheDate = (date "lundi 1 janvier 2001 00:00:00") then
log "It works"
end if
end repeat
end tell
end tell
Quickly getting the values of a range of cells is great news! But even better is that you can fill in the values of a range by defining the value of that range. This is SO MUCH FASTER than doing it one cell at a time.
When I tried getting the value of a column (a range of cells), I received a list of lists. Each item in the list had only one value - that is the value of the cell.
To speed up complex operations, once you've got the list of values, take the process out of the "tell Excel" block and let AppleScript do the calculations. Then turn the result back into a list of lists and define the value of the range in Excel.
I had a problem reading ranges with some cells containing #VALUE! (failed formulas). I didn't find a solution on the Internet, so I thought it would be a good idea to share my solution here. Comments & improvement are surely welcome. I'm inclined to think there is a more straightforward solution to the problem than this. :)
Getting all values with value of range can lead to a problem messing up the output of the script. AppleScript doesn't consider a cell's content "#VALUE!" (= missing values) a value since it is, well, missing. Therefore the script doesn't include the cell's content in the list of values. This obviously messes up the cell order in the values list, since it has less items than the actual range has cells. In this situation it is quite impossible to return each value to its original cell in the workbook. Adding ”of ranges” to the code includes all cells with missing values solving the problem.
N.B. The values will be displayed as a one-dimensional array. Handling multi-column ranges requires more work. Nonetheless the missing values are included.
set celVals to (value of ranges of range "A1:A4")
E.g. {2.2.2022, 1.1.2011, missing value, 3.3.2033}
In order to return the values back to the workbook it is required to build back the list of lists. A missing value will be written to its cell as an empty string. Of course the original (failed) formula can be written instead, if needed.
N.B. again. This code applies to one column situation only. A little more is needed to put back a multi-column range. I'm sure you'll manage. :D
set returningCelVals to {}
repeat with i from 1 to count of celVals
set end of returningCelVals to {item i of celVals}
end repeat
set value of range ("A1:A4") to returningCelVals
EDIT: I knew there is a better solution. Here it is:
set celVals to string value of range "A1:A4"
String value gives a two-dimensional array of values and error messages of the range. String value gives also e.g. cell's currency symbols, so it is perhaps not suitable to all situations.
I have a list and I want to display certain data based on another cell value. Let's call it "cell A1".
In my logic, every value of "cell A1" means different list items.
The problem is when I added other values to the possible data that "cell A1" could contain, the formula becomes very long and couldn't fit in the source field!
For example, if "cell A1" possible values are Tigre, Dog, Cat, Lion, Horse, Sheep and Turkey, the condition is:
=IF(D4="Tigre";'Sheet1'!$B$2:$B$1000;IF(D4="Dog";'Sheet2'!$B$2:$B$1000;IF(D4="Cat";'Sheet3'!$B$2:$B$1000;IF(D4="Lion";'Sheet4'!$B$2:$B$1000; IF(D4=" Horse";'Sheet5'!$B$2:$B$1000;IF(D4="Sheep";'Sheet6'!$B$2:$B$1000;IF(D4=" Turkey";'Sheet7'!$B$2:$B$1000;IF(D4="Val8";'Sheet8'!$B$2:$B$1000;""))))))))))
Check this image, the source field is already full! (before the end of the condition)
have you an idea how work around this issue or how to optimise the conditional formula?
PS: I'm a french user, so I apologize of my bad english! And, I know that I need to use "SI" instead of "IF" :)
Thanks.
I think you may be after something called dependent data validation.
Look here for a step by step tutorial http://www.contextures.com/xlDataVal02.html
As I see from your example, Val1 Val2 etc. are keys to redirect to specific worksheets. You can write the "mapping" data betweens these "Vals" and sheet names somewhere (possibly in a hidden worksheet or any available/hidden columns) and use it in the validation formula using a combination of INDIRECT and VLOOKUP. Say in this example I use Columns G:H in worksheet "mapping":
' Worksheet mapping
G H
Val 1 Sheet number 1
Value 2 Sheet2
Val3 My worksheet number 3
Now your data validation formula can be like this:
=INDIRECT("'"&VLOOKUP($D$4,mapping!$G$1:$H$3,2,0) &"'!$B2:$B1000")
p.s. you can then make the column mapping!G1:G3 your data validation list for the cell $D$4 that holds the keys.
I also notice here that if the ranges are not always the same (unlike in your example where they all are B2:B1000), you can also make the mapping "complete" by including the ranges in the redirection column (column H in my example).
I would like to put the below coding into a vba like a function. There is a bunch of data created already by VBA, and when the VBA does its work, then the following function should be run, but i dont know how to add to my vba so that the function always runs as long as data contains. The macro i created already puts the datasheet together, now instead of creating the below with lenthy codings, i just want my macro to run the below, like a man who clicks on the below right hand corner of the cell which contains the below function.
It should be something: Activesheet.ForulaR1C1 = "=RIGHT(AY4,LEN(AY4)-FIND(".",AY4))" something. Can someone help me? Thanks
ORIGINAL FUNCTION TO BE RUN "=RIGHT(AY4,LEN(AY4)-FIND(".",AY4))"
This is where I am at now:
Sub Project_numbers()
Dim j As Integer
Zorro = Range("AY" & Rows.Count).End(xlUp).Row
o = 4
Worksheets("MJE").Range("AF" & o).FormulaR1C1 = "=RIGHT(AE4,LEN(AE4)-FIND(".",AE4))"
o = o + 1
End Sub
You have a couple of problems here. The biggest is that you've got quotation marks in your formula. VBA reads these as the end of the string, so it's interpreting your formula as two separate text strings: =Right(AE4,LEN(AE4)-FIND( and ,AE4)), separated by a .. This isn't a structure VBA can do anything with, so it's going to fail at that point.
When you're inserting a formula with VBA that contains quotation marks, you need to use two quotes together to indicate that it's a literal quote mark that's part of the string, rather than the end of the string:
"=RIGHT(AE4,LEN(AE4)-FIND(""."",AE4))"
The second problem is that you're using the FormulaR1C1 method, which expects cell references to be given in R1C1 (row#column#) notation, rather than A1 notation, but then passing it a formula that uses A1 notation. Again, this is going to confuse the issue and produce errors.
I'm guessing you used the macro recorder to get the syntax, then inserted your own formula? The macro recorder, for some weird reason, loves to use the R1C1 reference style, but we can use a different method for written code.
The full line you need is:
Worksheets("MJE").Range("AF" & o).Formula = "=RIGHT(AE4,LEN(AE4)-FIND(""."",AE4))"
EDITED TO ADD:
With further information, specifically that you need the range referenced to change as you loop, you have some options on how to do it.
1. Use the R1C1 reference style
This allows you to include relative references in formulae easily. You'll use R to designate the formula's row, and C to designate its column; so a cell that referred to itself would simply be =RC. You can follow the R and C with numbers to designate specific rows and columns, so cell B2 would be =R2C2 - row 2, column 2. More usefully, you can use =R[#]C[#] to offset your formula by a certain amount.
In your formula, assuming it's always going to be looking at column AE but whichever row the formula is entered into, your line would be:
Worksheets("MJE").Range("AF" & o).FormulaR1C1 = "=RIGHT(RC31,LEN(RC31)-Find(""."",RC31))"
2. Build your formula from variables.
You already have a variable you can use, o, so we can combine that with the rest of the string to get the appropriate references. It's harder to read, though...
Worksheets("MJE").Range("AF" & o).Formula = "=RIGHT(AE" & o & ",LEN(AE" & o & ") - FIND(""."",AE" & o & "))"
Personally, I find this method rather cumbersome to work with, but it's an option.
3. Assign the formula to your entire range as a single operation
Personally, I prefer this option; I find it to be the neatest one. I'm assuming, from your formula, that your data starts on row 4, and you want the formula to go into every cell between AE4 and the end of your data, which is stored in Zorro. You can use this line to add the formula in one go:
Worksheets("MJE").Range("AF4","AF" & Zorro).Formula = "=RIGHT(AE4,LEN(AE4)-FIND(""."",AE4))"
The cell references will update automatically for each row. There's no need for a loop with this method - of course, if you're looping anyway, that may be no great saving.
I have a situation where I am referencing cells in a different worksheet and returning the values of cells from that worksheet. Although it works, I find my current method inefficient because I have to repeat the formula in the logical test part of the IF statement:
=IF(**EXTREMELY LONG COMPLICATED FORMULA** <> "", **EXTREMELY LONG COMPLICATED FORMULA**, "")
As you can see, I must repeat the main part of the formula just to check if it is blank first. If I do not do this, I get a zero in the cell (for blank values in the referenced worksheet). I'm looking for something more like:
=IF(**EXTREMELY LONG COMPLICATED FORMULA** <> "", **RETURN VALUE**, "")
This looks cleaner to me because I won't have to repeat myself. Also, if we ever have to update the formula, I won't have to duplicate my changes to the repeated parts. Is there a way to do this?
The above is actually a simplified version of my problem, but the answer should get me where I need to go. My actual formula has nested IF statements checking along the way for blanks. For reference, here it is:
=IFERROR(IF(SMALL(IF(ImportedData!$H$2:$H$1000>=DataFilters!$A$1,IF(ImportedData!$G$2:$G$1000=DataFilters!$A$15,ROW(ImportedData!A$2:A$1000)-ROW(ImportedData!A$2)+1)),ROWS(ImportedData!A$2:ImportedData!A2))<>"",IF(INDEX(ImportedData!A$2:A$1000,SMALL(IF(ImportedData!$H$2:$H$1000>=DataFilters!$A$1,IF(ImportedData!$G$2:$G$1000=DataFilters!$A$15,ROW(ImportedData!A$2:A$1000)-ROW(ImportedData!A$2)+1)),ROWS(ImportedData!A$2:ImportedData!A2)))<>"",INDEX(ImportedData!A$2:A$1000,SMALL(IF(ImportedData!$H$2:$H$1000>=DataFilters!$A$1,IF(ImportedData!$G$2:$G$1000=DataFilters!$A$15,ROW(ImportedData!A$2:A$1000)-ROW(ImportedData!A$2)+1)),ROWS(ImportedData!A$2:ImportedData!A2))),""),""),"")
The most obvious solution is to use a helper column or cell. Just put EXTREMELY LONG COMPLICATED FORMULA somewhere in your spreadsheet, then refer to that cell in your IF formula.
Edit
To avoid a helper column, here is a trick I've used on occasion:
=IFERROR(VALUE(long_formula&""),"")
What this does is, concatenate the result of long formula with an empty string (which converts it to a string), then take the value of all that (which converts it back to a number if possible), then substitute any errors with a blank. (An error would occur if you attempt to take the value of something that's not numerical.)
This will only work if you either have a numerical result or an empty result. It will fail if you have a text result.
As of March 2020, Excel includes the LET function. You can write:
=LET(ELCF,**EXTREMELY LONG COMPLICATED FORMULA**,IF(ELCF <> "", ELCF, ""))
Where the three parameters are:
the name you will use to refer to your calculation,
the calculation itself, and
the final formula using the calculation.
The function also allows for multiple names to be defined. The general syntax is:
=LET(name1, name_value1, calculation_or_name2, [name_value2, calculation_or_name3...])
https://support.microsoft.com/en-us/office/let-function-34842dd8-b92b-4d3f-b325-b8b8f9908999
Do you need the blank in the cell for further calulations or if-functions or Do you just dont want to see the 0s?
Second case:
Just use a number format for the column like
0,00;-0,00;"";#
First case:
Put the following code in a module:
Option Explicit
Public Function IfEmpty(LongFormula As String) As String
If LongFormula = "" Then
IfEmpty = ""
Else
IfEmpty = LongFormula
End If
End Function
And use it in your worksheet like
=IfEmpty(**EXTREMELY LONG COMPLICATED FORMULA**)
I have data in Excel in following format
Employee Company
John A
George A
Bob A
Peter B
Luke B
and I would need:
Company Employees
A John,George,Bob
B Peter,Luke
Is there an easy way of doing it in Excel?
I posted an answer to something similar a while ago. The same principle could apply here, though it's easier in your case.
First, make sure that the list is sorted by column B.
In cell C2, you can put:
=IF(B2=B3,0,1)
We'll use this later on. In cell D2, put:
=IF(B1=B2,CONCATENATE(D1,", ",A2),A2)
Drag/Fill the two formulae down and you should get a full list on each cell where you have 1 in column C. Copy/Paste values on the formulae, then apply a filter. Select all the 0 in column C and delete all the records (in columns A through D). After that remove the filter and sort by any column.
I think you want the concatenate function:
http://office.microsoft.com/en-ca/excel-help/concatenate-HP005209020.aspx
So! You'd like to do a concatenate if match style comparison/lookup. Unfortunately there's nothing out-of-the-box in Excel for this, but this slightly more complex Q&A thread outlines two potential approaches:
Using the MoreFunctions add-in for Excel
Using a custom "MultiLookup" VBA function
http://answers.microsoft.com/en-us/office/forum/office_2003-excel/if-vlookup-is-match-then-concatenate/26f6794d-8663-4e59-9a1b-a598c78f3bed
Both approaches require a little advanced knowledge of Excel (VLOOKUP, compound functions, etc). If you're not familiar with these things then VLOOKUP is a good place to start!
String concatenation over more than a few cells is best left to a VBA User Defined Function (aka UDF) even without setting criteria. Your situation of an unknown number of strings and applying revolving criteria would certainly fit this category.
Tap Alt+F11 and when the VBE opens, immediately use the pull-down menus to Insert ► Module (Alt+I,M). Paste the following into the new pane titled something like Book1 - Module1 (Code).
Public Function conditional_concat(rSTRs As Range, rCRITs As Range, rCRIT As Range, Optional sDELIM As String = ", ")
Dim c As Long, sTMP As String
For c = 1 To Application.Min(rSTRs.Cells.Count, rCRITs.Cells.Count)
If rCRITs(c).Value2 = rCRIT.Value2 Then _
sTMP = sTMP & rSTRs(c).Value & sDELIM
Next c
conditional_concat = Left(sTMP, Application.Max(Len(sTMP) - Len(sDELIM), 0))
End Function
Tap Alt+Q to return to your worksheet. Use this UDF like any native Excel worksheet function. The syntax is,
conditional_concat(<range of strings>, <range of conditions>, <cell with condition>, [optional] <delimiter as string>)
The formula in E2 is,
=conditional_concat(A$2:A$99, B$2:B$99, D2)
Fill down as necessary. I've used the optional sDELIM parameter to provide semi-colon delimination in E3 with,
=conditional_concat(A$2:A$99, B$2:B$99, D3, "; ")