Passing data from column - vba

I have userform with three fields (COMNAME; COMCOMPANY; TXTVALUE) I tried to pass value from column F to TXTVALUE (textbox) when COMNAME and COMCOMPANY (combo boxes) values are matched row values in columns C and D.
Dim var1 As Integer
Dim var2 As Integer
With Application.WorksheetFunction
var1 = .Match(Me.COMCOMPANY.Value, sheet.range("C7:C10"), 0)
var2 = .Match(Me.COMNAME.Value, sheet.range("D7:D10"), 0)
TXTVALUE.Value = .Index(sheet.range("F7:F10"), var1, var2)
End With
Table looks like this
column C |column D |Column F
PA | CT | 750
RS | HA | 550
PA | CS | 358
When i execute this macro the result in txtvalue is good if is select PA (COMCOMPANY) and CT (COMNAME) - the TXTVALUE is 750 but when I select PA (COMCOMPany) and CS (COMNAME) - the macro return error "Unable to get index property of WorksheetFunction Class".
How to make this code or code like this to work. Thank you

You seem to have some confusion as to how the index function (and possibly the match function) works.
The match function works like this:
MATCH(x, rng, intExact)
means to find the FIRST value x in the range rng and the intExact parameter is defining whether you want the first value less than, exactly equal to, or more than the value x. In your case intExact = 0 and you are searching for values exactly equal. This function will return the row number that the first match is found, beginning with the top of the range.
Assuming that you have displayed cells D6:F9 in your case var1 will return 1 for PA. Var2 will return 1 for CT and 3 for CS. There is no relation between the two variables. Var1 is searching in the D column for your input, and Var2 is searching in the F column for your second input.
The INDEX function works like this:
INDEX(rng, lngRow, lngCol)
means to go in the rng and get the specified row, lngrow and column lngcol starting from the upper left part of the range. In your first case, you select row 1 and column 1 in range F7:F10, which happens to be 750. It is a coincidence that this works, because the match in the E column happens to be in the first row.
In your second case, you try to select the value in row 1 and column 3, but there is only 1 column in your range, so you get an error.
You need to combine this with a lookup column.
column C |column D |Column F | Column Z
PA | CT | 750 | PACT
RS | HA | 550 | RSHA
PA | CS | 358 | PACS
And use the function:
Var1 = .Match(Me.COMCOMPANY.Value & Me.COMNAME.Value, _
sheet.range("Z7:Z10"), 0)
TXTVALUE.Value = .Index(sheet.range("F7:F10"), var1)
You can omit the column parameter of the Index function.
If you are unsure what is happening, it is best to step through the code, noting what the variables are. In the case of the WorksheetFunction function you can then actually put the function in a cell and supply the values that the variables are to troubleshoot the issue.

Use Application.Match function to avoid possible errors being thrown and handle them in the returned variable of Variant type
Dim var1 As Variant
Dim var2 As Variant
With Application
var1 = .Match(Me.COMCOMPANY.Value, sheet.range("C7:C10"), 0)
If Not IsError(var1) Then
var2 = .Match(Me.COMNAME.Value, sheet.range("D7:D10"), 0)
If Not IsError(var2) And var1 = var2 Then Me.TXTVALUE.Value = sheet.range("F7:F10").Rows(var1)
End If
End With

Related

Excel - Auto-fill/number with custom pattern across columns and rows, while allowing duplication?

I have data similar to this, where my values in Columns B, D and F may repeat occasionally:
Column A Column B Column C Column D Column E Column F
-------- -------- -------- -------- -------- --------
Data1 Data2 Data3
Data4 Data1
Data5 Data1
Data3
What I need to do is auto-fill/number Columns A, C and E, first going horizontally on the sheet then vertically, using a custom pattern - essentially giving the data in Columns B, D and F an unique ID.
In doing so, I need to keep the following in mind:
A repeating value in Columns B, D and F does not get assigned a new ID, but receives the previously allocated ID.
Column B will always have data, thus I expect all rows of Column A to be filled.
Columns D and F may not always have data.
So, I only expect the rows in Column C to be filled if Column D contains data.
Similarly, I only expect the rows in Column E to be filled if Column F contains data.
What I expect to achieve as final result is:
Column A Column B Column C Column D Column E Column F
-------- -------- -------- -------- -------- --------
UR001 Data1 UR002 Data2 UR003 Data3
UR004 Data4 UR001 Data1
UR005 Data5 UR001 Data1
UR003 Data3
Unfortunately my sheet is very long, spanning to nearly a thousand which is making it difficult to do the numbering manually. And if I need to introduce extra rows in the middle, I essentially end up breaking the sequence from that point and have to start numbering till the end, again and again.
I have tried searching for in-built formulas and vba code but I'm unable to find something suitable for my problem. Help, please!
please try this out. It makes use of a Dictionary which stores previously identified Data Strings within columns B,D,F.
Sub BreadthSearchFirst()
Dim dict
Set dict = CreateObject("Scripting.Dictionary")
Dim sht1 As Worksheet
Set sht1 = ActiveWorkbook.ActiveSheet
Dim ID, newID, key As String
Dim j, i, count As Integer
ID = "UR"
count = 1
'include the starting and last row number
startrow = 1
totalrow = 100
'loop over each row
For j = startrow To totalrow
'loop over each column
For i = 1 To 6
'looks only in even columns
If i Mod 2 = 0 Then
'Checks if a Value is Present
If sht1.Cells(j, i) <> "" Then
'assigns the value as a key if the cell is not empty
key = sht1.Cells(j, i)
'checks if the key is present in the dictionary
If dict.Exists(key) Then
'call the previously stored data if it exists
sht1.Cells(j, i - 1) = dict.Item(key)
Else
'places the newID in the odd column
newID = ID & count
sht1.Cells(j, i - 1) = (ID & count)
'creates a new key within the dictionary
dict.Add key, newID
'increment the ID count
count = count + 1
End If
End If
End If
Next i
Next j
End Sub
A few things to note
you'll need to make changes if the columns are not the first 6 columns (i.e. A to F)
Modify the starting and end rows, there are methods available to locate the last row in a sheet and can be replaced with this accordingly. There shouldn't be any empty spaces in the even columns (B,D,F), if so it will recognise this as a data.
Good luck!

VBA Average Function Error - 1004

I am getting an error 'Unable to get average property of Worksheetfunction class' when attempting to calculate the average of 2 ranges.
My table can be found below.
When I calculate the average of the % column, I have no problems. It provides me with the correct output, however, I have problems when I try and calculate the average for the $ column.
Col | % | $
1 | 2.33% | $2.33
2 | 3.64% | $3.64
3 | 10.83% | $10.83
4 | 6.07% | $6.07
5 | - | -
6 | 12.99% | $12.99
7 | 18.99% | $18.99
Dim myRange As Range
Dim myAverage As Variant
'The user selects the range
Set myRange = Application.InputBox( _
prompt:="Please select the range", Title:="My Range", Type:=8)
'This splits the range into two areas because the user typically does not select the row with the "-" in it.
'myRange would typically look something like (B1:B4,B6:B7) OR (C1:C4,C6:C7)
Area1 = myRange.Areas(1)
Area2 = myRange.Areas(2)
myAverage = Application.WorksheetFunction.Average(Area1, Area2)
The error I receive is 'Unable to get average property of Worksheetfunction class' and it happens with the myAverage calculation.
Any ideas as to why it calculates the % column without problem, but doesn't calculate the $ column?
Thanks in advance!
Declare as the proper type (Range object):
Dim Area1 As Range
Dim Area2 As Range
Use the Set keyword to assign to Object variables.
Set Area1 = myRange.Areas(1)
Set Area2 = myRange.Areas(2)
Then, you should be able to use the Application.Average or Application.WorksheetFunction.Average to get the mean.
myAverage Application.Average(Area1, Area2)
Otherwise, you're passing a variant array to the function, which is not supported, hence it raises the error. The Average function requires either a contiguous range of multiple cells, or itemized list of individual cells or values. You can't pass it multiple ranges of multiple cells each.
Or, omit the Area1/Area2 steps entirely and do simply:
myAverage = Application.Average(myRange.Areas(1), myRange.Areas(2))

Concatenate Rows with Matching IDs

I have multiple rows of purchase details. Each purchase has a client ID. For presentation purposes I need to merge purchases with a similar client ID into a single cell so I can use a VLOOKUP to display this in another table that has client information. Any ideas?
In the example below, I'd like cell C2 to contain "1, 2", cell C3 to contain "3" and cell C4 to be empty (bill has made no purchases).
A B C
1 client_id name purchase_ids
2 1 jim
3 2 bob
4 3 bill
purchase_id purchase_client_id amount
1 1 100
2 1 500
3 2 50
You can create a Dynamic Pivot Table into new sheet, to summarize sales by ClientID, and then use that table with VLOOKUP (http://www.tips-for-excel.com/2011/06/how-to-make-a-pivot-table/).
Example data sheet
Pivot table summarized by ClientID
Here another suggestion, do a function that gathers the data in one cell with VBA. Done this some time ago, but you can use & edit it for your own purpose -
Option Explicit
Public Function STRINGCONCATENATEVLOOKUP(ByVal r As Range, ByVal criteria As Variant, Optional ByVal colnum As Long, Optional ByVal separator As String) As String
On Error GoTo err_hand
Dim n As Long
Dim result As String
If colnum = Empty Then colnum = r.Columns.Count
If colnum > r.Columns.Count Or colnum < 1 Then
STRINGCONCATENATEVLOOKUP = "#COLVALUE!"
Exit Function
End If
If separator = "" Then separator = ";"
For n = 1 To r.Rows.Count Step 1
If r.Cells(n, 1).Value = criteria Then result = result & r.Cells(n, colnum).Value & separator
Next
result = Left(result, Len(result) - Len(separator))
STRINGCONCATENATEVLOOKUP = result
Exit Function
err_hand:
STRINGCONCATENATEVLOOKUP = CVErr(xlErrValue)
End Function
Function works just like VLOOKUP, but with the difference it sums all data and returns a string separated by ";" or what you define.
I'm afraid you're going to have to get your hands dirty with VBA (macro programming) to do what you want to do.
There is no Excel function which can create a concenated list. The Excel function CONCATENATE doesn't do what you need:
=CONCATENATE(A1, "-", B1) # returns "foo-bar" if A1 has 'foo' and B1 has 'bar'
So VBA is what you'll need. Fortunately, others have been here before, including this SO answer.
my answer requires MOREFUNC addon*
Here I assume data in purchase "table" is in A9:C11. Adjust accordingly.
formula for C2:
{=MCONCAT(IF($B$9:$B$11=A2,$A$9:$A$11,""),",")}
notice the curly braces. This is an array formula you have to confirm using Ctrl+Shift+Enter, not just Enter
then copy the formula to C3 and C4
MOREFUNC ADDON
Morefunc Addon is a free library of 66 new worksheet functions.
HERE is some information (by original author)
here is the last working download link I found
here is a good installation walk-through video

Is there a coalesce-like function in Excel?

I need to fill a cell with the first non-empty entry in a set of columns (from left to right) in the same row - similar to coalesce() in SQL.
In the following example sheet
---------------------------------------
| | A | B | C | D |
---------------------------------------
| 1 | | x | y | z |
---------------------------------------
| 2 | | | y | |
---------------------------------------
| 3 | | | | z |
---------------------------------------
I want to put a cell function in each cell of row A such that I will get:
---------------------------------------
| | A | B | C | D |
---------------------------------------
| 1 | x | x | y | z |
---------------------------------------
| 2 | y | | y | |
---------------------------------------
| 3 | z | | | z |
---------------------------------------
I know I could do this with a cascade of IF functions, but in my real sheet, I have 30 columns to select from, so I would be happy if there were a simpler way.
=INDEX(B2:D2,MATCH(FALSE,ISBLANK(B2:D2),FALSE))
This is an Array Formula. After entering the formula, press CTRL + Shift + Enter to have Excel evaluate it as an Array Formula. This returns the first nonblank value of the given range of cells. For your example, the formula is entered in the column with the header "a"
A B C D
1 x x y z
2 y y
3 z z
I used:
=IF(ISBLANK(A1),B1,A1)
This tests the if the first field you want to use is blank then use the other. You can use a "nested if" when you have multiple fields.
Or if you want to compare individual cells, you can create a Coalesce function in VBA:
Public Function Coalesce(ParamArray Fields() As Variant) As Variant
Dim v As Variant
For Each v In Fields
If "" & v <> "" Then
Coalesce = v
Exit Function
End If
Next
Coalesce = ""
End Function
And then call it in Excel. In your example the formula in A1 would be:
=Coalesce(B1, C1, D1)
Taking the VBA approach a step further, I've re-written it to allow a combination of both (or either) individual cells and cell ranges:
Public Function Coalesce(ParamArray Cells() As Variant) As Variant
Dim Cell As Variant
Dim SubCell As Variant
For Each Cell In Cells
If VarType(Cell) > vbArray Then
For Each SubCell In Cell
If VarType(SubCell) <> vbEmpty Then
Coalesce = SubCell
Exit Function
End If
Next
Else
If VarType(Cell) <> vbEmpty Then
Coalesce = Cell
Exit Function
End If
End If
Next
Coalesce = ""
End Function
So now in Excel you could use any of the following formulas in A1:
=Coalesce(B1, C1, D1)
=Coalesce(B1, C1:D1)
=Coalesce(B1:C1, D1)
=Coalesce(B1:D1)
If you know there will not be any overlap across columns, or want the overlap, then this is a pretty fast way to solve for a coalesce. The below formula does not apply to your values and columns, but rather to my mock-up so you will need to adjust to make it relevant.
=LEFT(TRIM(CONCATENATE(Q38,R38,S38,T38,U38,V38,W38,X38,Y38)),1)
With the updated IFS function in excel you don't need to nest. You can try something like
Create a blank cell to the right
Then enter.
=IFS(ISBLANK(A1),B1,ISBLANK(A1),C1,ISBLANK(A1),D1)
Highlight column and paste as needed.
If you only want to coalesce to 0, which is a very common use case, you can simply use the SUM() function around a single value. As a convenience that one treats all blanks as zero, and it's extra convenient since it's so short.
Not a generic solution like the other answers, but a helpful shortcut in many cases where that's exactly what you want.
Inside the array enter the variables that are not allowed.
Function Coalesce(ParamArray Fields() As Variant) As Variant
Dim v As Variant
For Each v In Fields
If IsError(Application.Match(v, Array("", " ", 0), False)) Then
Coalesce = v
Exit Function
End If
Next
Coalesce = ""
End Function
Depending on how many cells you want to check, you can chain together multiple ISBLANK checks.
For instance, when checking columns A, B, then C:
=IF(ISBLANK(A1),IF(ISBLANK(B1),C1,B1),A1)
For columns A, B, C, and D:
=IF(ISBLANK(A1),IF(ISBLANK(B1),IF(ISBLANK(C1),D1,C1),B1),A1)
... and so on.
Late to the party and leveraging #AndyMC's answer you can use the following to evaluate vlookups, index(match()) etc. to coalesce your formula statements.
Public Function Coalesce(ParamArray Fields() As Variant) As Variant
Dim v As Variant
For Each v In Fields
If Not IsError(v) Then
Coalesce = v
Exit Function
End If
Next
Coalesce = CVErr(xlErrNA)
End Function
and use it in your Worksheet as followed: =Coalesce(INDEX(SHEET1!$A:$AF,MATCH(Main!$Q36,SHEET1!$I:$I,0),MATCH(Main!D$34,SHEET1!$1:$1,0)),INDEX(SHEET2!A:CR,MATCH(Main!$Q36,SHEET2!$M:$M,0),MATCH("SOMETHING",SHEET2!$1:$1,0)))
For the first statement that does not return #N/A it will return the actual matched value.

Write a formula into a cell depending on another cell value

I was hoping to write a Macro that does a very repetitive task for me but entering VBA is harder than expected. I will learn how to program macros for excel when I have some time because it seem extremely useful, but I can't spend 5 to 12 hours this week.
Maybe someone here can help!
I have a few excel files that follow this pattern:
Column C - Column D
--------------------
text | (empty)
number | (empty)
number | (empty)
text | (empty)
number | (empty)
text | (empty)
text | (empty)
number | (empty)
text | (empty)
number | (empty)
Where text and number alternate randomly for a few thousand cells. I need column D to hold, when column C is a number, the difference with previous number, otherwise it must stay blank:
Column C - Column D
--------------------
text | (empty)
3 | (empty)
14 | (=C3-C2) : 11
text | (empty)
16 | (=C5-C3) : 2
text | (empty)
text | (empty)
21 | (=C8-C5) : 5
22 | (=C9-C8) : 1
So the algorithm is:
var previousNumberCell
var i = 1
for all (selected) cells/rows
if (Row(i).column(C) holds number) {
Row(i).column(D).value = "=C"+i+"-"C"+previousNumberCell
previousNumberCell = i;
}
i++
End
I don't care if for the first or last cell it doesn't work.
Thank you so much for the help, or if you can point me to where I can find the answer to this.
EDIT: this is a simplified version of the problem, there are 2 things I don't know how do well with excel macros: select a cell, and tell if cell is a number... for the record, number cells have been converted from text to number format.
Give this a shot:
Sub MyMacro()
Dim rng as Range
Dim cl as Range
Dim lastNum as Range
Set rng = Range(Selection.Address) 'Make sure that your active SELECTION is correct before running the macro'
If Not rng.Columns.Count = 1 Then
MsgBox "Please select only 1 column of data at a time.",vbCritical
Exit SUb
Else:
For each cl in rng
If IsNumeric(cl.Value) Then
If lastNum Is Nothing Then
cl.Offset(0,1).Formula = "=" & cl.Address
Else:
cl.Offset(0,1).Formula = "=" & cl.Address & "-" & lastNum.Address
End If
set lastNum = cl
End If
Next
End If
End Sub
Do you require VBA?
Insert a new Column before column C
Column C with your values becomes column D
You might need columnheaders..
In cell C2 put: =IF(E2=0;0;SUM(E$2:$E2)) this identifies rows with number
In cell E2 put: =IF(ISNUMBER(D2);1;0) this sets order for each row with a number to use next in vlookup
in cell F2 put: =IF(ISNUMBER(D2);ABS(D2-VLOOKUP(MAX($C$1:C1);$C$1:D1;2;0));"")
Autofill columns C, E and F.
In column F you get your results, except first, which is "#VALUE"
Hi you can do this with an if formula and a named formula . if (isnumber ,named formula,0)
named formula (=lookup formula)