I have a formula made of components and a table of components that translates each component to several strings.
I want to substitute each component with the expression of the sum of the respective strings in the table.
Example:
Formula = FOO/BAR
Table:
+-----------+---------+
| Component | Strings |
+-----------+---------+
| FOO | 1.1 |
| FOO | 1.2 |
| FOO | 1.3 |
| FOO | 5.1 |
| BAR | 101.10 |
| BAR | 101.15 |
| BAR | 5.10 |
+-----------+---------+
Result: (1.1+1.2+1.3+5.1)/(101.10+101.15+5.10)
Edit: I need the expression printed not the result of the calculation.
I think the solution probably envolves the function substitute.
I have written a quick dirty UDF to give you the idea how it can be done.
Put this code inside module and write FTEXT in any cell and give parameters.
Function test(Ref As Range, para1 As String) As String
Dim Cell As Range
Dim temp1 As String
Dim temp2 As String
For Each Cell In Ref
If Cell.Value = para1 Then
temp1 = temp1 & Cell.Offset(, 1).Value & "+"
Else
temp2 = temp2 & Cell.Offset(, 1).Value & "+"
End If
Next Cell
test = "(" + Left(temp1, Len(temp1) - 1) + ")" + "/" + "(" + Left(temp2, Len(temp2) - 1) + ")"
End Function
Assuming your data is in A2:B8 then following formula can do this.
=SUMIF(A2:A8,"FOO",B2:B8)/SUMIF(A2:A8,"BAR",B2:B8)
Is that what you want?
Related
I have a very large "work request" data set that I need to clean up. The data set has some consistent elements, a series of numbers that are a set length this changes about about half way through the data set but the change is predictable. One issue with the data set is that there are multiple deliminators in places, sometimes no deliminator, sometimes text in front etc. I pulled a sample of the variables that I am dealing with and separated them manually to show the desired result.
+----+--------------------------------+------------+--------+----------------------+
| | A | B | C | D |
+----+--------------------------------+------------+--------+----------------------+
| 1 | Work Request | Cell 1 | Cell 2 | Cell 3 |
| 2 | 2097947.A | 2097947 | A | |
| 3 | 2590082.A/4900 REPLACE DXAC | 2590082 | A | 4900 Replace DXAC |
| 4 | 2679314.C | 2679314 | C | |
| 5 | 2864142B/DEMOLISH STRUCTURES | 2864142 | B | DEMOLISH STRUCTURES |
| 6 | 3173618 | 3173618 | | |
| 7 | 3251628/4800 REPLACE ASPHALT | 3251628 | | 4800 REPLACE ASPHALT |
| 8 | 4109066A | 4109066 | A | |
| 9 | 4374312D | 4374312 | D | |
| 10 | 4465402, Building 4100 | 4465402 | | Building 4100 |
| 11 | 4881715 DESIGN | 4881715 | | DESIGN |
| 12 | 4998608\ | 4998608 | | |
| 13 | ADMIN | ADMIN | | |
| 14 | PGM MGMT | PGM MGMT | | |
| 15 | FWR # 4958989 /Bldg 4000 | 4958989 | | Bldg 4000 |
| 16 | NICC FEDISR000744416/4000 UPS | R000744416 | | 4000 UPS |
| 17 | R000451086/4300 MODS TO RM5006 | R000451086 | | 4300 MODS TO RM5006 |
+----+--------------------------------+------------+--------+----------------------+
As you can see there are a few predictable variables and some that are user input errors.
Notice that in some cases the numbers have a single character behind the 7 digit work request number most of the time separated by a "." but sometimes no separation as in A8 and A9. Sometime there are deliminators, "/" or "space", or "," but this isn't consistent. I am currently working with a VBA that manages to strip the numbers for some but fails when it encounters no numbers or extra numbers. Eventual the work request numbers were changed to add the R00 this is the "new" number and over half of the data uses this in some form.
The VBA that I am using:
Option Explicit
Public Function Strip(ByVal x As String, LeaveNums As Boolean) As Variant
Dim y As String, z As String, n As Long
For n = 1 To Len(x)
y = Mid(x, n, 1)
If LeaveNums = False Then
If y Like "[A-Za-z ]" Then z = z & y 'False keeps Letters and spaces only
Else
If y Like "[0-9. ]" Then z = z & y 'True keeps Numbers and decimal points
End If
Next n
Strip = Trim(z)
End Function
=NUMBERVALUE(Strip(A1,TRUE))
=Strip(A1,FALSE)
This works in some places but not others. It also doesn't separate out C and D respectively. The most important issue is stripping out the work request number as seen in B.
Thanks for any help.
Here's a function using Regular Expressions that returns an array of the results.
Option Explicit
'Set reference to Microsoft VBScript Regular Expressions 5.5
' or use late binding
Function Splitter(S As String) As String()
Dim re As RegExp, MC As MatchCollection
Const sPat As String = "^(?:\D*?(?=R?\d)(R?\d+)[,.]?([A-Z])?\s*[/\\]?\s*(.*\S)?)|\s*(.*\S)"
Dim sTemp(2) As String
Set re = New RegExp
With re
.Global = True
.MultiLine = True
.Pattern = sPat
If .Test(S) = True Then
Set MC = .Execute(S)
With MC(0)
sTemp(0) = .SubMatches(0) & .SubMatches(3)
sTemp(1) = .SubMatches(1)
sTemp(2) = .SubMatches(2)
End With
Splitter = sTemp
End If
End With
End Function
With the data in A2:An, if you have Excel O365 with dynamic arrays, you can enter:
B2: =Splitter(A2)
and fill down. The results of the array will spill right to columns C & D.
If you do not have dynamic arrays, then:
B2: =INDEX(Splitter($A2),COLUMNS($A:A))
Fill Right to D2. Then select B2:D2 and fill down as far as necessary.
Try this code
Private Sub UserForm_Click()
Dim Sp() As String: Sp = Split(Strip("2590082.A/4900 REPLACE DXAC"), "|")
Sheet1.Range("B2", Sheet1.Cells(RowIndex:=2, ColumnIndex:=UBound(Sp) + 2)).Value = Sp
End Sub
Function Strip(s As String) As String
If s = "" Then Exit Function
Dim tmp As String
tmp = s
Dim Sp() As String: Sp = Split("0,1,2,3,4,5,6,7,8,9,.", ",")
For i = 0 To 10
tmp = Replace(tmp, Sp(i), "|")
Next
Dim words As String
Sp = Split(tmp, "|")
For i = 0 To UBound(Sp)
If Sp(i) <> "" Then words = words & Sp(i) & "|"
Next
If Right$(words, 1) = "|" Then words = Mid(words, 1, Len(words) - 1)
tmp = s
Sp = Split(words, "|")
For i = 0 To UBound(Sp)
tmp = Replace(tmp, Sp(i), "|" & Sp(i) & "|")
Next
If Right$(tmp, 1) = "|" Then tmp = Mid(tmp, 1, Len(tmp) - 1)
Strip = tmp
End Function
Here's an example using a regular expression.
Sub WorkRequests()
Dim re As Object, allMatches, m, rv, sep, c As Range
Set re = CreateObject("VBScript.RegExp")
re.Pattern = "(((R00)?\d{7})[\.]?([A-Z])?)"
re.ignorecase = True
re.MultiLine = True
re.Global = True
For Each c In Range("B5:B20").Cells 'for example
c.Offset(0, 1).Resize(1, 3).ClearContents 'clear output cells
If re.test(c.Value) Then
Set allMatches = re.Execute(c.Value)
For Each m In allMatches
c.Offset(0, 1).Value = m 'order#+letter
c.Offset(0, 2).Value = m.submatches(1) 'order #
c.Offset(0, 3).Value = m.submatches(3) 'letter
Next m
End If
Next c
End Sub
Regular expressions reference: https://learn.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/scripting-articles/ms974570(v=msdn.10)?redirectedfrom=MSDN
I have this table and the VBA code has to fetch Value from Product where the Primary value is Yes. But, Value has to be the First Value in the Sequence.
+---------+---------+---------+
| Product | Results | Primary |
+---------+---------+---------+
| A | | |
| B | | Yes |
| C | | Yes |
| D | | |
| E | | Yes |
| F | | |
| G | | Yes |
| H | | Yes |
| I | | |
+---------+---------+---------+
Expecting results:
+---------+---------+---------+
| Product | Results | Primary |
+---------+---------+---------+
| A | | |
| B | A | Yes |
| C | A | Yes |
| D | | |
| E | D | Yes |
| F | | |
| G | F | Yes |
| H | F | Yes |
| I | | |
+---------+---------+---------+
I have tried this below vba code, but doesn't work as i expected.
Sub test()
Dim i As Long
Dim lr As Long
lr = ActiveSheet.UsedRange.Rows.Count
For i = 2 To lr
If Range("D" & i).Value = "Yes" Then
Range("C" & i).Value = Range("B" & i - 1).Value
End If
Next
End Sub
Excel Formula
Here is an example of how your formula can look. This first looks to see if the primary field is "Yes". If so, then it checks if the previous result also was a yes, and grabs it's the result if so. Otherwise, it grabs the first value based on your example.
=IF($C2="Yes", IF($C1="Yes", $B1, $A1),"")
Adjust this as needed!
VBA Code
I would suggest creating a way of finding your headers, that way it is easier to update down the road in case it changes or there are more fields added. Below I added an example of what I would attempt to do.
I used a helper function (Inject) to build the actual formula and make it easier to read/debug if there are issues.
Feel free to add your own custom error handling as well.
Just note this is one of many ways you could tackle this. I could even break this formula into even smaller components to abstract as much of it as possible.
Option Explicit
Private Sub AddResultsToTable()
Dim Ws As Worksheet
Set Ws = ActiveSheet
'FIND COLUMN HEADERS TO USE IN FORMULA REFERENCES
With Ws.UsedRange
On Error GoTo Catch
Dim Product As Range
Set Product = .Find("Product")
Dim Results As Range
Set Results = .Find("Results")
Dim Primary As Range
Set Primary = .Find("Primary")
End With
'CREATE FORMULA. Example: =IF($C2="Yes", IF($C1="Yes", $B1, $A1),"")
Dim CustomFormula As String
CustomFormula = Inject("=IF(${0}='Yes', IF(${1}='Yes', ${2}, ${3}),'')", _
Primary.Offset(1).Address(False, True), _
Primary.Address(False, True), _
Results.Address(False, True), _
Product.Address(False, True) _
)
'SET FIRST RANGE EQUAL TO FORMULA & AUTOFILL FORMULA DOWN
With Results.Offset(1)
.Value = CustomFormula
.AutoFill Range(.Address, Ws.Cells(Ws.Rows.Count, Product.Column).End(xlUp).Offset(, 1))
End With
Exit Sub
Catch:
'You can do your error handling here.
MsgBox Err.Description, vbCritical
End Sub
'METHOD THAT ALLOWS A STRING TO BE REPLACED WITH VARIABLES AND SPECIAL CHARACTERS
Public Function Inject(ByVal Source As String, ParamArray Args() As Variant) As String
'#AUTHOR: ROBERT TODAR
'#EXAMPLE: Inject("${0}, ${1}!", "Hello", "Robert") --> Hello, Robert!
'REPLACE SINGLE QUOTES WITH DOUBLE QUOTES
Inject = Source
Inject = Replace(Inject, "'", """")
'REPLACE ${#} WITH VALUES STORED IN THE VALUE IN THAT INDEX.
Dim Index As Integer
For Index = LBound(Args, 1) To UBound(Args, 1)
Inject = Replace(Inject, "${" & Index & "}", Args(Index), , , vbTextCompare)
Next Index
End Function
Assuming cols A B C use the below formula in B2
=If(C2="yes", if(B1="",A1,B1),"")
Copy this to all the linea below.
This will work as long as the first item is not primary.
U can even include this formula in vba and do the copy paste in vba
I have got an Excel file with a column named Comments in a sheet called Resources (accessible as Resources[#Comments]), the data looks something similar to,
+=============================================+
| Comments |
+=============================================+
| [7/2] Level changed from 10 to 9 |
| [14/2] Alignment changed from ABC to XYZ |
| [21/2] Location changed from US to UK |
| [28/2] Chapter changed from [blank] to ABCD |
+---------------------------------------------+
| [14/2] Level changed from 5 to 4 |
| [21/2] Location changed from US to UK |
| [21/2] Chapter changed from JKLM to ABCD |
+---------------------------------------------+
| [28/2] Chapter changed from EFGH to MNOP |
+---------------------------------------------+
| [21/2] Location changed from IN to JP |
+---------------------------------------------+
The output I'm looking for should look something like (essentially extracting text between Chapter changed from <SOURCE> to <DESTINATION>),
+=============================================+==============+==============+
| Comments | Old Chapter | New Chapter |
+=============================================+==============+==============+
| [7/2] Level changed from 10 to 9 | [blank] | ABCD |
| [14/2] Alignment changed from ABC to XYZ | | |
| [21/2] Location changed from US to UK | | |
| [28/2] Chapter changed from [blank] to ABCD | | |
+---------------------------------------------+--------------+--------------+
| [14/2] Level changed from 5 to 4 | | |
| [21/2] Location changed from US to UK | | |
| [21/2] Chapter changed from JKLM to ABCD | | |
+---------------------------------------------+--------------+--------------+
| [28/2] Chapter changed from EFGH to MNOP | EFGH | MNOP |
+---------------------------------------------+--------------+--------------+
| [21/2] Location changed from IN to JP | | |
+---------------------------------------------+--------------+--------------+
Notes:
Cell might not have any "Chapter changed" text in which case no processing is required.
"Chapter changed" text is always the last line.
Only want to track the change if it was today (e.g. [28/2] =TEXT(today(), "dd/m")
I think Excel wraps text within "" (double quotes).
I am happy with either Excel formula or VBA script. Already tried stuff like KuTools, =MID(Resources[#Comments],SEARCH("Chapter changed from",Resources[#Comments])+20,SEARCH("to", Resources[#Comments]) - SEARCH("Chapter changed from",Resources[#Comments])-21).
To check the date part I am using, =IF(ISNUMBER(SEARCH("["&TEXT(TODAY(), "dd/m")&"] Chapter changed", Resources[#Comments])), "Yes", "")
Thanks.
It is pretty simple actually...
Spilt the cell contents using vbnewline or Chr(10)
Split on "["
Split on "]"
Check Date
Split on "from"
Split on "to"
Code: Is this what you are trying?
Sub Sample()
Dim cellValue As String
Dim tmpAr As Variant
Dim Dt As String, lastLine As String
Dim OLDc As String, NEWc As String
Dim rng As Range
'~~> Set the range
Set rng = Sheet1.Range("A2")
'~~> Split on Linefeed. It could be Chr(13) as well
tmpAr = Split(rng.Value, Chr(10))
'~~> Get the last line
lastLine = tmpAr(UBound(tmpAr))
'~~> Get the date part
Dt = Split(lastLine, "[")(1)
Dt = Split(Dt, "]")(0)
'~~> Check if it is same as today
If Format(Date, "D/M") = Dt Then
lastLine = Split(lastLine, "from")(1)
OLDc = Trim(Split(lastLine, "to")(0))
NEWc = Trim(Split(lastLine, "to")(1))
rng.Offset(, 1).Value = OLDc
rng.Offset(, 2).Value = NEWc
End If
End Sub
you could use Replace() method of Range object:
Sub DoThat()
Dim cell As Range
With Range("A1", Cells(Rows.Count, 1).End(xlUp))
.Offset(, 1).Resize(, 2).Value = .Value
For Each cell In .Offset(, 1).Cells
If InStr(cell.Value2, Format(Date, "D/M")) > 0 Then
cell.Replace "*from ", ""
cell.Replace " to *", ""
cell.Offset(, 1).Replace "*to ", ""
Else
cell.Resize(, 2).ClearContents
End If
Next
End With
End Sub
I've got a Dashboard which uses COUNTIFS and SUMIFS for the # and $ for transactions over multiple countries (columns) and that match certain criteria (rows).
Each row is split into different Fiscal Quarters and then breaks down into varies additional detail and months. The first row is 'everything', so the FQ Relative is ' * ', which works for strings in sum/countifs, but not for numerical, how can I use sum/countifs as a single formulae that can be copied down where the FQ can be zero, positive/negative numbers or a ' * ' (or something else) to represent anything.
The Dashboard is large so will need to repeat this multiple times, so it can't be a different formula (must be the same for each row).
Hopefully the below gives you a simplied view of what I'm working with.
ID | Caption | FQ Relative | FM | Other Criteria | Country A | Country B | Country C
1 | All Trans | * | * | <other criteria> | =sumif() | =sumif() | =sumif()
2 | Prev QTR | -1 | * | <other criteria> | =sumif() | =sumif() | =sumif()
3 | Current QTR | 0 | * | <other criteria> | =sumif() | =sumif() | =sumif()
4 | Next QTR | 1 | * | <other criteria> | =sumif() | =sumif() | =sumif()
Use a pair of < and > operators to provide a does not equal blank criteria for SUMIF/SUMIFS.
=sumifs(f:f, b:b, "<>")
This works against truly blank cells as well as cells that may contain a zero-length string (e.g. "") returned by a formula.
You can turn the numbers into text, and this should solve your issue.
If you're trying to do so in a more automated context, then this subroutine should work:
Sub TextifyNumbers(Block As range)
'This turns numbers into text so that they will work with the "*" wildcard
x = UBound(Block.Value, 1)
y = UBound(Block.Value, 2)
For i = 1 To x
For j = 1 To y
Block(i, j).Formula = Evaluate(Chr(34) & Block(i, j).Value & Chr(34))
Next j
Next i
End Sub
This question already has an answer here:
Concatenate column headers if value in rows below is non-blank
(1 answer)
Closed 5 years ago.
I have one table with parts as following;
---------------
| Part number |
---------------
| 123456 |
| 16D345 |
| 16E099 |
| 490586 |
| 970884 |
---------------
And another one like so;
---------------------------------------------------
| Part number | 940822 | 940922 | 170345 | 940222 |
---------------------------------------------------
| 123456 | X | | X | X |
| 16D345 | X | | X | |
| 16E099 | | X | | X |
| 490586 | X | | X | X |
| 970884 | | | X | |
---------------------------------------------------
The numbers in columns of the second table are 'units'.
I'm trying to figure out how to get all the unit numbers where a part has X. Basically I want to end up with the following;
----------------------------------------
| Part number | Used in |
----------------------------------------
| 123456 | 940822, 170345, 940222 |
| 16D345 | 940822, 170345 |
| 16E099 | 940922, 940222 |
| 490586 | 940822, 170345, 940222 |
| 970884 | 170345 |
----------------------------------------
Now I've just recently learned how to use INDEX and MATCH but haven't been able to get the result I want. I've tried using array formulas but I don't understand them yet.
If you have a subscription to Office 365 Excel then you can use the following array formula:
=TEXTJOIN(", ",TRUE,IF(($E$2:$H$6 = "X")*($D$2:$D$6=A2),$E$1:$H$1,""))
Being an array formula it needs to be confirmed with Ctrl-Shift-Enter instead of Enter when exiting edit mode. If done correctly then Excel will put {} around the formula.
If you do not have Office 365 then you can put this code in a module attached to the workbook and use the formula as described above:
Function TEXTJOIN(delim As String, skipblank As Boolean, arr)
Dim d As Long
Dim c As Long
Dim arr2()
Dim t As Long, y As Long
t = -1
y = -1
If TypeName(arr) = "Range" Then
arr2 = arr.Value
Else
arr2 = arr
End If
On Error Resume Next
t = UBound(arr2, 2)
y = UBound(arr2, 1)
On Error GoTo 0
If t >= 0 And y >= 0 Then
For c = LBound(arr2, 1) To UBound(arr2, 1)
For d = LBound(arr2, 1) To UBound(arr2, 2)
If arr2(c, d) <> "" Or Not skipblank Then
TEXTJOIN = TEXTJOIN & arr2(c, d) & delim
End If
Next d
Next c
Else
For c = LBound(arr2) To UBound(arr2)
If arr2(c) <> "" Or Not skipblank Then
TEXTJOIN = TEXTJOIN & arr2(c) & delim
End If
Next c
End If
TEXTJOIN = Left(TEXTJOIN, Len(TEXTJOIN) - Len(delim))
End Function