How to validate dynamic sub list using VBA? - excel

I have an excel file with 2 sheets
In the first one, I have two columns:
In the second, I have an SQL query that loads into Excel that looks something like this:
I have validation on the first column, limiting the value to column A, so the user can either start typing which item he wants, or use the dropdown to pick.
My issue is that I want to create validation on the second column, so that only the options applicable to that Item are available (ex. if the first column is Item 1, only options A, B, and C are available to be selected.) The SQL data is dynamic (possibly different options in Column B for each A), and new items are added and removed. The solutions I saw (ex. https://trumpexcel.com/dependent-drop-down-list-in-excel/) involved creating the two filters on two axis, but I'm not sure how that would work in my situation.

If you can create a list of unique values from the first column to use as the source for the first drop-down (e.g. like in ColD below), you can configure the second drop-down to use a "Source" formula like:
=OFFSET($B$1,MATCH(I2,$A:$A,0)-1,0,COUNTIF($A:$A,I2),1)
Where I2 here is the corresponding drop-down pointed at ColA values (see example screenshot)
Assuming your source data is sorted on ColA.
EDIT: adding a different (perhaps more flexible) approach:
Define a workbook name like "validation" which points to a VBA function which returns a range:
Corresponding function in a regular code module:
Function getOptions() As Range
Dim m As Long, c As Range, v
Set c = Application.Caller 'getOptions is called from the cell
' where the drop-down is clicked
v = c.Offset(0, -1).Value 'read cell (e.g.) to left of the one being filled
'Debug.Print v
If Len(v) > 0 Then
'do the same thing we did with the DV list Source formula above
m = Application.Match(v, c.Parent.Columns("A"), 0)
If Not IsError(m) Then
'return a range (must be contiguous)
Set getOptions = c.Parent.Cells(m, "B").Resize( _
Application.CountIf(c.Parent.Columns("A"), v), 1)
End If
End If
End Function
Finally, set up your cell validation list to point to =validation
The advantage of this approach (despite being a little more set-up and requiring a macro-enabled workbook) is that the VBA in the function can look at the context of the calling cell (via Application.Caller) and decide what range of cells to return to be shown in the drop-down.

Related

How do I print a specific value in one column based on the value in another using VBA?

I have Excel data in column B (cells B2:B60) that contains email addresses (such as xxx#ahcptcare.com). In column D, I need to print "APC" if the value in B contains ahcptcare.com, or UDP if the value in B contains #upschyd.com, and so on for more email addresses.
I don't know how to look for the text after the # symbol in column B and return another value in column D. I started researching on RIGHT and FIND functions in VBA but I'm at loss on this one. I think I can use and IFS and RIGHT but I'm not sure how to.
How can I do this?
You can do this with a loop and two lines of code. If you are just checking for those 2 domains then
Dim r As Range
Set r = Sheets(1).Range("B2:B60")
Dim c As Range
'Loop through each cell in the range
For Each c In r
'Check for the first domain then check for the second.
If InStr(1, UCase(c.Value), "AHCPTCARE.COM", vbTextCompare) > 0 Then c.Offset(0, 2).Value = "APC"
If InStr(1, UCase(c.Value), "UPSCHYD.COM", vbTextCompare) > 0 Then c.Offset(0, 2).Value = "UDP"
Next
I'll go over how this would work for the following example data in Excel and hopefully it will teach you how to apply similar VBA code to your own Excel data. It's been a while since I've written VBA, so there may be more efficient ways to do this.
A
B
C
D
Email
Result
john1#abc.com
john2#xyx.com
john3#qrf.com
john4#wtf.com
john5#omg.com
john6#bbq.com
First you need to determine your business logic: what is it you are trying to do? Well, you have a range of data that you want to loop over, and, based on part of the data, print a specific string/value to another column in the same row as each data point, respectively.
In my case, I have data in cells B2:B7 of column B (Email), and I want to print a value to column D (Result).
The first thing we need to do in VBA is declare some variables for the data we are going to work with:
Dim rng As Range, cell As Range 'We are running through a range of cells, so the datatype of Range is appropriate here
Dim cellVal As Variant, comparisonVal As Variant 'Each cell will have text and special characters, so I just used the catch-all datatype of Variant here
Set rng = Range("B2:B7") 'This is just my range of data
Now that we've got some variables declared, you can start on your loop now. You need to loop through each cell in your range, so that's first... and there's not much better for looping through a set range of cells than a for each ... in loop in VBA.
I typically write the entire loop 'wrapper' first and then click inside it to start writing what will happen for each iteration.
For Each cell In rng
Next cell
Next, we'll jump inside that loop and write the steps we want to loop through. After you move to be inside the loop, we need to determine the value of the cell (but only the part after the # symbol) and store that value so that it can be compared against later. It's also good practice to check whether there is an # symbol in the first place... otherwise your code may go off the rails.
cellVal = cell.Value 'We're assigning this here rather than in the original variable declaration section because its value needs to change with each cell we loop through
If InStr(1, cellVal, "#") > 0 Then 'The InStr() function checks whether a specified value exists in a string
comparisonVal = Split(cellVal, "#")(1) 'Here we assign the value we want to use for comparison by using the Split() function to give us the value after the # symbol. Note that this may not work if the # symbol appears multiple times in a cell in column B. Lookup the Split() function for further information.
Else
MgBox ("No # symbol found! Exiting loop.") 'If we don't find an # symbol in one of the rows, we should probably alert the user. Dirty source data plagues even the best of us.
End If
OK! We're almost there. Now we just need to... actually apply the right Result value in column D based on the value in our comparisonVal variable. I don't know the extent of your data or business logic, but from what you've shared in your question, it sounds like you have some predetermined Result values to put into the Result column depending on the value of the Email column (column B).
When you have several predetermined routes you want to follow based on several possible scenarios, the best way to achieve that is often a switch statement (sometimes called a case statement). The syntax for that in VBA is:
Select Case [comparison]
Case [case value 1]
Case [case value 2]
...
Case Else
End Select
In our case (no pun intended...), we'll use comparisonVal (which contains just the part of each email address after the # symbol) and assign the appropriate value to column D of the current row. To do that assignment in a relative way, we'll use the Offset() function; you can look that up later for further reading if necessary. Remember, this bit of code also goes inside the foreach loop.
Select Case comparisonVal
Case "abc.com"
cell.Offset(0, 2).Value = "APC"
Case "xyz.com"
cell.Offset(0, 2).Value = "UDP"
Case "qrf.com"
cell.Offset(0, 2).Value = "THC"
Case "wtf.com"
cell.Offset(0, 2).Value = "IDK"
Case "omg.com"
cell.Offset(0, 2).Value = "LOL"
Case Else
cell.Offset(0, 2).Value = "Unknown Value"
End Select
Run all of that code inside your subroutine or function and you'll end up with the following data result (when you start with the data in the table above):
A
B
C
D
Email
Result
john1#abc.com
APC
john2#xyx.com
UDP
john3#qrf.com
THC
john4#wtf.com
IDK
john5#omg.com
LOL
john6#bbq.com
Unknown Value
Which should get you to 100% task completion!

How to sum data from two ranges if another range contains particular text

I have two lists in excel and each of them contains a column of text + another column with points assigned to it. It looks something like that:
As you can see both text and points can change.
Additionally, I would like to add more lists to it (marked C, D, etc.)
On another sheet, I would like to prepare a summary that calculates the sum of the points assigned to text. I will a summary list with text entries:
and I am looking for a formula which will give me sum of the points from all lists IF text from Summary cell matches any text from any list on previous sheet. The end results for these two lists will look like that:
I tried some SUMIF variations but never got what I wanted. Can someone help me find correct formula?
Here is a more general case.
Say Sheet1 has many arbitrary data sets between column A and column Z like:
First put the following User Defined Function in a standard module:
Public Function Gsum(rng As Range, patrn As String) As Long
Dim r As Range
Gsum = 0
For Each r In rng
If r.Text = patrn Then
Gsum = Gsum + r.Offset(0, 1)
End If
Next r
End Function
Put the data abc in Sheet2 cell B2 and in Sheet2 cell C2 enter:
=gsUM(Sheet1!A1:Z27,B2)
You have to take all the range, like that :
French Version = =SOMME.SI($A$2:$C$5;A8;$B$2:$D$5)
English Version = =Sumif($A$2:$C$5,A8,$B$2:$D$5)
Assuming your data is on sheet1 starting in A1 and the second sheet also starts in A1 on that sheet, put this in B2 and copy down:
=SUMIF(Sheet1!A:C,A2,Sheet1!B:D)
Try this and see if it works. I would test it but I am away from my desk at the moment.
Note, that this formula should be placed on a separate worksheet (just in case).
Sheet2!B2 = SUMPRODUCT(--(Sheet1!$A$1:$AZ$100=Sheet2!$A1), OFFSET(Sheet1!$A$1:$A$Z100,0,1))
Sheet1!A:AZ100 = The Range in which the data is contained
Sheet2!$A1 = The Cell that contains the criteria
You could of course use Friendly names for the ranges if you wish.

Excel Match Numbers in 2 Columns to a Number in a 3rd

I've run into a bit of a road block. I get a .PDF output from an accounting program and copy/paste the data into excel, then convert text to columns. I am trying to match the GL code with the totals for that specific account. Columns A, B, and C show the state of my data prior to sorting it, and the lines under Intended Output show how I would like the data to output.
I am trying to automate this process, so I can paste data into columns A, B, & C in the raw format and have it automatically spit out the required numbers in the format of the Intended Output. The GL codes remain the same, but the numbers and the number of rows will change. I've color coded them for ease of review.
Thank you very much in advance!
Using a combination of the following formulas you can create a list of filtered results. It works on the principal that you Data1 text that you want to pull is the only text with a "-" in it, and that the totals you are pulling from Data2 and Data3 are the only numbers in the column. Any change to that pattern will most likely break the system. Note the formulas will not copy formatting.
IFERROR
INDEX
AGGREGATE
ROW
ISNUMBER
FIND
Lets assume the output will be place in a small table with E2 being the upper left data location.
In E2 use the following formula and copy down as needed:
=IFERROR(INDEX(A:A,AGGREGATE(15,6,ROW($A$1:$A$30)/ISNUMBER(FIND("-",$A$1:$A$30)),ROW(A1))),"")
In F2 use the following formula and copy to the right 1 column and down as needed:
=IFERROR(INDEX(B:B,AGGREGATE(15,6,ROW($A$1:$A$30)/ISNUMBER(B$1:B$30),ROW(A1))),"")
AGGREGATE performs array like calculations. As such, do not use full column references such as A:A in it as it can lead to excess calculations. Be sure to limit it to the range you are looking at.
Try this procedure:
Public Sub bruce_wayne()
'Assumptions
'1. Data spreadsheet will ALWAYS have the structure shown in the question
'2. The key word "Total" (or whatever else it might be) is otherwise NOT found
' anywhere else in the 1st data column
'3. output is written to the same sheet as the data
'4. As written, invoked when data sheet is the active sheet
'5. set the 1st 3 constants to the appropriate values
Const sData2ReadTopLeft = "A1" 'Top left cell of data to process
Const sData2WriteTopLeft = "J2" 'Top left cell of where to write output
Const sSearchText = "Total" 'Keyword for summary data
'*******************
Const sReplaceText = "Wakanda"
Dim r2Search As Range
Dim sAccountCode As String
Dim rSearchText As Range
Dim iRowsProcessed As Integer
Set r2Search = Range(sData2ReadTopLeft).EntireColumn
sAccountCode = Range(sData2ReadTopLeft).Offset(1, 0).Value
iRowsProcessed = 0
Do While Application.WorksheetFunction.CountIf(r2Search, sSearchText) > 0
Set rSearchText = r2Search.Find(sSearchText)
Range(sData2WriteTopLeft).Offset(iRowsProcessed, 0) = sAccountCode
Range(sData2WriteTopLeft).Offset(iRowsProcessed, 1) = rSearchText.Offset(0, 1).Value
Range(sData2WriteTopLeft).Offset(iRowsProcessed, 2) = rSearchText.Offset(0, 2).Value ' add this if there are more summary columns to return
'last two lines could be collapsed into a single line; at the expense of readability..
rSearchText.Value = sReplaceText 'so that next search will find the next instance of the trigger text
iRowsProcessed = iRowsProcessed + 1
sAccountCode = rSearchText.Offset(1, 0).Value
Loop
r2Search.Replace what:=sReplaceText, Replacement:=sSearchText
End Sub

How can I make this Excel macro loop through only visible filtered data?

I have very little experience with VBA and I'm now stumped by what I'm trying to accomplish with a macro. Excel 2010.
I have 3 relevant columns. B, C, and AD. (Columns 2, 3, and 30)
My data is filtered down to about 30 rows, almost none of which are contiguous (about 500 rows total).
I want the following to happen:
A formula is entered in ONLY THE VISIBLE ROWS in column AD, which will look at the value in column B of that same row and check for that value in all of the VISIBLE CELLS in column C. It cannot look at all of the cells in column C, only the visible ones.
If the value from column B in that row is found anywhere in the VISIBLE CELLS in column C, then "True" should be returned in column AD. I don't care about what is returned when the value is not found, as I will be filtering for the "True" values only.
As an added requirement, if the first 3 characters of the value in column B are "010" I need it to return a value of "True" in column AD. I then need this formula copied down column AD for each VISIBLE row.
Right now, I have a formula that will conduct the search in column C for the value in column B. (found on stackoverflow)
=NOT(ISNA(VLOOKUP(B4,C:C,1,0))))
This provides a "True" in column AD when the value from column B is found somewhere in column C. With the "010" constraint, the formula looks like this:
=IF(LEFT(B4,3)="010","True",NOT(ISNA(VLOOKUP(B4,C:C,1,0))))
I am having a problem in that this looks at even the hidden (filtered out) rows. Every one of my values in column B will appear in column C at some point, so I'm only getting "True" for all my entries.
I think there must be a better way to do this than just having a macro paste the formula down (even considering I can't get the formula to work). So, 2 questions:
Is the formula the right way to go in this case, and, if so, can anyone tell me how to get it to only search the visible cells in column C?
If code is the best way (I'm guessing it is), can anyone show me an example of code that might work?
You're almost there. On its own, COUNTIF does not have this capability, but if you throw user-defined functions (UDF functions) into the mix, you have an elegant solution.
Add the following code to a module in your code-behind.
Function Vis(Rin As Range) As Range
'Returns the subset of Rin that is visible
Dim Cell As Range
Application.Volatile
Set Vis = Nothing
For Each Cell In Rin
If Not (Cell.EntireRow.Hidden Or Cell.EntireColumn.Hidden) Then
If Vis Is Nothing Then
Set Vis = Cell
Else
Set Vis = Union(Vis, Cell)
End If
End If
Next Cell
End Function
Function COUNTIFv(Rin As Range, Condition As Variant) As Long
'Same as Excel COUNTIF worksheet function, except does not count
'cells that are hidden
Dim A As Range
Dim Csum As Long
Csum = 0
For Each A In Vis(Rin).Areas
Csum = Csum + WorksheetFunction.CountIf(A, Condition)
Next A
COUNTIFv = Csum
End Function
Now you can use the new COUNTIFv() function to do the same as count, but only include visible cells. This code example was sampled from Damon Ostrander's answer to a similar question, so you will probably need to tweak it slightly. You can either use the COUNTIFv function in the macro itself, or modify the VLOOKUP function in a similar fashion to use the worksheet function example you have already. Neither method is really better than the other, so either should work for you.

Excel 2003 Combobox/Validation Data List Values and Labels

I'm unable to find out this information: Is it possible to show labels in these two type of lists rather than the real values ? of course when a label is selected, the value of the cell or the combobox (as a Form control) gets the real value.
Example:
* Data
Product A <------> 10
Product B <------> 11
Product C <------> 22
Combobox shows: Product A, Product B and Product C
If I select A I get the 10 value, B the 11 value and C the 22 value
Thank you in advance
Miloud B.
The typical way of doing this is to have a lookup table. There's a function named something like VLOOKUP (that's a partial name) that you can use in formulas to retrieve the value. I did something like this at my job, and I created a lookup table in a separate spreadsheet, and then called the lookup function in my main one.
You can also use macros, but that is too much bother in my opinion for the current problem.
Data Validation: You can't have two values per row. You can use a lookup table to convert one value to another. Or you can combine the values like "ProductA_10" and use a formula to extract the "10" where you need it.
Form Combobox: This also has limited options and doesn't really offer anything more than DV does for what you want.
Toolbox Combobox: This can do just about anything you want. You would set the ColumnCount property to 2, the BoundColumn property to 2, and the ColumnWidths property to something like "1;0" to hide the second column. If I had ProductA-C in A1:A3 and 10-12 in B1:B3, then I would use code like this in a standard module to fill the combobox
Sub LoadCombobox()
Dim rCell As Range
For Each rCell In Sheet1.Range("A1:A3").Cells
Sheet1.ComboBox1.AddItem rCell.Value
Sheet1.ComboBox1.List(Sheet1.ComboBox1.ListCount - 1, 1) = rCell.Offset(0, 1).Value
Next rCell
End Sub
And code like this in the sheet's module to put a value in a cell (F1 in this example)
Private Sub ComboBox1_Change()
Me.Range("F1").Value = Me.ComboBox1.Value
End Sub
Whenever a new value in the combobox is selected, F1 is updated. We can use the Value property of the combobox because we set the BoundColumn property to the column with the value we want.

Resources