Write a formula into a cell depending on another cell value - vba

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)

Related

Search for a word with Prefix in the column and copy the entire word with prefix to another column on the same sheet

I Download an excel file which has multiple Row's and endless columns.
In a particular Row we have data where every Cell is containing details of a specific product, Separated by Alt+Enter.
I have to pull the data of 2-3 such descriptions by copy paste to separate the product from the huge list.
Eg:-
_A_______B____________C____D___E___F___G______H_________________________________
| Product | Range |A | B | C |D |description|....
________________________________________________________________________________
1 | Apple | R 1 |A1| B1| C1|D1| Description1
Description2
Description3
Description4
________________________________________________________________________________
2 | ball | R 1 |A1| B1| C1|D1| Description1
Description2
Description3
Description4
From the above example My Requirment is to copy Description Details with Prefix of Say DLL:123456 or LLM: 654321 and copy the same to the next Row.
This will help to seperate the product with perticular Description.
If this what you looking for :
APPLE A B C D E F G H 2460 APPLE : 2460
6521 APPLE : 6521
4532 APPLE : 4532
3021 APPLE : 3021
1234 APPLE : 1234
BALL 6521 BALL : 6521
4532 BALL : 4532
3021 BALL : 3021
1234 BALL : 1234
Then create a column and use this formula. You can also use your own prefix
=IF(ISBLANK(a2),LEFT(a1,(SEARCH(":",a1)) & " " &l2),a2&" : "&l2)
Based on what I'm guessing you need: This might get you started:
Sub Example1()
Dim rowArray() As Variant, rowArrayCounter As Long
Dim myStringArray, itemThatIwant As String, rowItIsIn As Long
' The following code will find all instances of "LLM: 654321" in column "F"
' It places the row number of each one into an array called rowArray()
' and places the value of that item into a variable called itemThatIwant
' The "split" function assumes that you enttered each list into a single cell
' by using alt-enter to put them on individual lines within that cell. If so, then
' the split delimiter would be chr(10), as below. Otherwise it will probably be one space
' but you will need to find the correct delimiter for this to work.
rowArrayCounter = 1
ReDim rowArray(1 To 1)
With Worksheets(1).Range("F1:F250")
Set c = .Find("LLM: 654321", LookIn:=xlValues)' this text is what you change
If Not c Is Nothing Then
firstAddress = c.Address
Do
myString = Split(c.Value, Chr(10)) ' split cell list into separate items
For i = LBound(myString) To UBound(myString)
If Left(myString(i), 11) = "LLM: 654321" Then
itemThatIwant = myString(i)
rowItIsIn = c.Row
ReDim Preserve rowArray(1 To rowArrayCounter)
rowArray(rowArrayCounter) = c.Row
' do your events with data
' the entire item (if found) is in the variable itemThatIwant
Exit For
End If
Next i
Set c = .FindNext(c)
Loop While Not c Is Nothing And c.Address <> firstAddress
End If
End With
End Sub

VBA fill cells by random numbers depending on other cells

I am working with Excel 2013 and I need to fill the range of cells by random numbers. To be exact, Range(B1:B4) fill by numbers in order to value of cells in previous column, I mean in Range(A1:A4). I really have no idea how to fill that using VBA if there is that condition, otherwise it's simple.
Here is a scetch of cells
# | A | B |
----------------------
1 | Yes | 1 |
----------------------
2 | No | 2 |
----------------------
3 | Maybe | 3 |
----------------------
4 | No | 2 |
----------------------
If all you need is random numbers, you don't need VBA. Just set your cell formula equal to:
"=RANDBETWEEN(1,3)"
However, your random numbers will change every time your worksheet is calculated. To avoid this, you can define the following sub and associate it, for example, with an action button:
Sub makeRand()
Dim targetRange As Range
Dim xlCell As Range
Dim upperBound As Integer
Dim lowerBound As Integer
Set targetRange = Range("B1:B4")
upperBound = 3
lowerBound = 1
Randomize
For Each xlCell In targetRange
xlCell.Value = Int((upperBound - lowerBound + 1) * Rnd + lowerBound)
Next xlCell
End Sub
If all you need to do is set this with vba, this should give you values 1, 2, or 3:
Range("B1:B4").Formula = "=RANDBETWEEN(1,3)"
If you only need an Excel formula, you can always just paste =RANDBETWEEN(1,3) into the formula bar.
If you're trying to define column B values based on column A values, just use:
Range("B1:B4").Formula = "=IF(A1 = ""Yes"", 1, IF(""No"", 2, If(""Maybe"", 3, ""ERROR"")))"
If neither of those are what you want, you're going to have to clarify better.

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.

Creating separate line entries for multi-value fields

I have a table with a list of items. Essentially it's an export from an issue tracking tool. One of the columns of that table contains comma-separated values. I am looking for a way to create separate entries for the individual values of multi-value entries.
Example: (this is a simplified example, the real case contains around a dozen columns)
Source data:
ID | Title | Areas Affected |
1 | Issue title A | Area X, Area Y |
2 | Issue title B | Area Y, Area Z |
3 | Issue title C | Area X, Area Z |
What I am trying to get to:
ID | Title | Areas Affected |
1 | Issue title A | Area X |
1 | Issue title A | Area Y |
2 | Issue title B | Area Y |
2 | Issue title B | Area Z |
3 | Issue title C | Area X |
3 | Issue title C | Area Z |
It is OK that there are now duplicate entries for IDs and Titles?
Is there a formula, macro, or VBA script to achieve this?
You need to split rows on that column using the comma as a separator. In VBA you have the Split() function that you can use to return an array. For the first element just put it back in the cell where the list was. For the others, insert a new line for each element in the array (meaning you can have n elements in that comma-separated list), copy the entire row on that new line and put the i-th value in there.
After some reading/going through sample code, here's the answer if anyone needs. This is the actual working code, which doesn't fit 1:1 the examples I posted in the question.
Sub DataLobs()
Application.ScreenUpdating = False 'Nice to have to increase the script speed.
Dim wsSrc As Worksheet
Dim wsDst As Worksheet
Dim curRowSrc As Integer
Dim curRowDst As Integer
Dim ttlRows As Integer
Dim splitLob() As String
' Setting initial values to start rows in source and destination
' tables, as well as the total number of rows
curRowSrc = 5
curRowDst = 5
ttlRows = 10000
Set wsSrc = Worksheets("Source") 'whatever you worksheet is
Set wsDst = Worksheets("Destination") 'or whatever your worksheet is called
wsDst.Range("A5:F" & ttlRows).Clear
' Goes through column D in the source table
' and copies rows where the D cell is not blank
' into the destination table
For curRowSrc = 5 To ttlRows
If wsSrc.Range("D" & curRowSrc).Value <> "" Then ' There are some blank cells in the source table, so we are eliminating them.
' Split the cell value against the comma
splitLob = Split(wsSrc.Range("D" & curRowSrc).Value, ", ") 'THIS IS WHERE #AlexandreP.Levasseur's HINT COMES INTO PLAY!
For i = LBound(splitLob) To UBound(splitLob)
wsDst.Range("A" & curRowDst).Value = splitLob(i)
wsDst.Range("B" & curRowDst).Value = wsSrc.Range("A" & curRowSrc)
wsDst.Range("C" & curRowDst).Value = wsSrc.Range("C" & curRowSrc)
wsDst.Range("D" & curRowDst).Value = wsSrc.Range("AC" & curRowSrc)
wsDst.Range("E" & curRowDst).Value = wsSrc.Range("AE" & curRowSrc)
wsDst.Range("F" & curRowDst).Value = wsSrc.Range("AD" & curRowSrc)
curRowDst = curRowDst + 1
Next
End If
Next curRowSrc
End Sub

Read Value from a merged Range (Excel)

I have a column whose header is a range of B1:C1. the data is more like
+----+----+
| Header |
+----+----+
| H2 | H3 |
+----+----+
| 1 | 2 |
| 3 | 4 |
+----+----+
I have a variable named rng such as
Set rng = Range("B1:C1")
now using rng variable, I want to select value "H3" or "2", or "4". I used the following syntax
rng.Offset(1,1).value
and isntead of giving me "H3" it gave me value from next colum i.e d2.
There is not a straight forward way to fix this unexpected behaviour. My understanding is that, regarding merged cells, the reference for the offset is based on the whole of the cell and that the merged cells are treated as one cell instead of many cells.
In your instance, if Range("B1:C1") is merged then the next column (i.e Offset(0,1)) is column D. Excel views the merged range as single cell and so from a visual standpoint the next column along is column D and not column C. This can be confusing in my view.
The best way to work around this is to avoid using merged cells as headers but instead use Centre Across Selection formatting:
1) De-merge Range("B1:C1")
2) Select Range("B1:C1") > Format > Cells
3) In Horizontal dialog box select 'Center Across Selection'
If you do that then the following code will work:
Sub GetOffsets()
Dim rng As Range
Set rng = Range("B1")
Debug.Print rng.Offset(1, 0) // H2
Debug.Print rng.Offset(2, 0) // 1
Debug.Print rng.Offset(3, 0) // 3
Debug.Print rng.Offset(1, 1) // H3
Debug.Print rng.Offset(2, 1) // 2
Debug.Print rng.Offset(3, 1) // 4
End Sub
If you really need to use merged cells, Excel has this "issue" when it comes to offsets from merged cells. In your case above, it does make sense that cell D2 becomes offset(1,1) from B1.
One solution is to use nested offsets:
You have your variable:
Set rng = Range("B1:C1")
Then use the following adjustment to your code:
rng.Offset(1,0).offset(0,1).value
This way, you first offset by row, to move down to B2, then offset by column to get to C2.