Working with variables in an Excel formula - excel

I am aware that
Cells(16, 2) = "=TEXT(42831,""dd-mmm-yy"")" 'DISPLAYS 06-Apr-17 IN CELL B16
The macro I'm working on needs the Date Serial Number, namely 42831, to be replaced with a variable:-
Dim lngDateSerialNo As Long
lngDateSerialNo = 42831
So now I have assigned 42831 to the variable, lngDateSerialNo.
It's not really relevant here but just to say that this variable will be updated during run time to give me a desired sequence of dates spaced one week apart.
I am also aware that
Cells(18, 2) = "=TEXT(" & lngDateSerialNo & ",""dd-mmm-yy"")" 'displays 06-Apr-17 IN cell B18
and to be fair I needed help with the above line of code.
OBJECTIVE
To try an understand what exactly the Ampersand is doing - apart from giving me the result I want!
Being relatively inexperienced in VBA, my understanding of the Ampersand is that it is a Concatenation operator and doesn't appear to have any other function in life.
Question 1
Could someone out there please give me an understanding as to why that line of code works so well?
Question 2
Purely academic, but if indeed the Ampersand is a concatenation operator, can the CONCATENATE formula be used in place of the ampersand?

my understanding of the Ampersand is that it is a Concatenation operator and doesn't appear to have any other function in life
Basically yes, unless you want to also consider the & being used as a type specifier for Long:
Dim a& ' declares a As Long
Could someone out there please give me an understanding as to why that line of code works so well?
Because that is the way to write this kind of code.
The only change you can potentially make is to replace the & with a + which works like & if both arguments are strings, but you shouldn't do that because & expresses the intent in the clearest way.
Purely academic, but if indeed the Ampersand is a concatenation operator, can the CONCATENATE formula be used in place of the ampersand
No, because that particular & is a part of the VBA syntax. You are building a string in VBA, it does not matter at this point that the string contains an Excel formula. VBA does not have a CONCATENATE function so you can't use it in place of the & when building a string in VBA.
However the & is also used for string concatenation in the Excel formula syntax, and if you were to build a formula with a & inside the double quotes, not outside, you could replace it with CONCATENATE, because now it's the Excel syntax where CONCATENATE exists.

Related

Inserted formula with cell reference does not recognize new input

I swear this just worked yesterday...
I have a program that creates formulas within certain cells that depend on subsequent data entries into other cells:
Cells(i, 40).Formula = "= (N" & i & ")/AP" & i
So, APi can be input and/or changed after the fact, and the formula should give you the result for whatever value is there. However, I get a #Div/0! error no matter what value is in that cell. When I evaluate the formula (within Excel) and step through the calculations, it shows this (for one particular cell):
= (N64)/AP64 = (47.35)/AP64 = 47.35/AP64 = 47.35/9 = #DIV/0!
So, the formula recognizes that there is a value in the cell AP64, but then does not use it to properly calculate the result.
As I said at the top, this worked as I expected yesterday afternoon when I was testing it. Now that I need other people to start using it, of course, it doesn't. I changed nothing within the program relative to these lines of code.
I apologize if this is answered elsewhere - I really have no idea how to create a search for this type of problem.
Two things to check:
1) Value of the cells used is formatted to a number (i've had #Value based on that a couple times) and
2) verify that modifying the code using fixed references provides the appropriate output ("A16" for some reason wasn't recognized as a cell reference in one I had show up). E.g., "$N" & i & "/$AP" & i
If those both look alright, you might try something like:
.Range(.Cells(3,40),.Cells(lr,40)).Formula = "=$N3/$AP3" 'ensure no random spaces
This should act like a fill-down in which the # 3 is iterated with the same row as the formula.

I need to Concatenate the values and separate with "|" but ignore blanks

I should start by saying I'm new at this, but I've been asked to get something done at work. I'm using Excel 2008 on a Mac.
I've created a data set that is roughly 3000 rows x 95 columns. the first column is a concatenation of product descriptions, manufacturers, etc. The rows are a list of keywords that I've used the following formula to identify and display in each of the 3000 rows:
=IF(ISNUMBER(SEARCH(C$3,$A3)),C$2,"") .
This has left me with data scattered throughout the sheet. I now need to combine the data from each row into one cell, with each discovered keyword separated by |, but I need to ignore blank cells so that I have a result like the following:
Keyword1|Keyword3|Keyword4|
and not like this:
Keyword1||Keyword3|Keyword4||||||||||||
Anyone have any ideas?
Thanks
You could use & like this:
="keyword1"&"|"&"keyword2"&... which does not skip blank cells
or if keyword1 is in cell A1 and keyword 2 in B1 then
=IF(OR(A1="",B1=""),"",A1&"|"&B1)
which will leave the result blank if either A1 or B1 are blank
I ran into this years ago when I was trying to create lists of potential racing picks from a list of horses in a race. I never found something I liked, but used several different methods as I changed my preferences over time. Here is one somewhat generic solution:
=SUBSTITUTE(SUBSTITUTE(TRIM(SUBSTITUTE(SUBSTITUTE(A7&"|"&B7&"|"&...&DM7," ","~"),"|"," "))," ","|"),"~"," ")
where the series starting with A7 and ending with DM7 would be replaced with the concatenation you have with multiple "|" symbols.
The formula looks for existing spaces and replaces them with an unused character (in this case, I used a "~"), then it replaces "|" with a " " and uses the TRIM command to eliminate leading and ending spaces as well as excessive spaces in between. Then it replaces the remaining spaces with "|" and puts back the initial spaces by substituting a space for any "~".
Obviously, this is a bit simpler if your keywords have no spaces, but that was not stated.
Addendum....
Thinking about this last night, I think getting to one concatenated string based on 3000 rows of data with up to 95 columns may be impossible. This is likely to easily run up against the string limit size of 32,767 since if even 5% of those cells have just a single character, you will have 14,250 characters plus a nearly equal amount of separators. I do not see how you would do this using the technique I posted. You could use this technique on a row by row basis and then cut and paste these to a plain text file, but I am beginning to think a native Excel solution would be nearly impossible, especially without VBA which we could have used to write the text out.
While my earlier suggestion might work well for relatively small ranges, it will not work for data approaching the size of the original request. Excel 2008 on a Mac will not likely be able to handle this, but if you have access to a version of Excel that can use VBA, this suggestion may help you since it uses the extended capabilities of VBA and will put the concatenated data into a text file rather than trying to leave it in the spreadsheet.
I am still trying to figure out a way to do this without using a string or VBA.
I am including some step-by-step information in case it is useful to you or others.
Open the spreadsheet with the 95 rows and 3000 columns of data.
Open the Visual Basic Editor and Choose Insert / Module from the menu.
Paste the following code into the editor window:
Option Explicit
Function ConcatNonblankWithSeparator(rngRange As Range, strSeparator As String) As String
Dim rngCell As Range
Dim strReturn As String
For Each rngCell In rngRange
' MsgBox rngCell.Address
If Len(rngCell.Value) > 0 Then
' MsgBox "before: " & strReturn
strReturn = strReturn & rngCell.Value & strSeparator
' MsgBox "after: " & strReturn
End If
Next rngCell
' MsgBox strReturn
' MsgBox Len(strReturn)
' MsgBox Len(strSeparator)
ConcatNonblankWithSeparator = Left(strReturn, Len(strReturn) - Len(strSeparator))
End Function
Sub ConcatRange()
Dim rngCell As Range
Dim strSeparator As String
Dim strFileName As String
strFileName = Application.DefaultFilePath & "\TestDelimOutput.txt"
Set rngCell = Sheets("Data").Range("A1:DX3000")
strSeparator = "|"
Open strFileName For Output As #1
Write #1, ConcatNonblankWithSeparator(rngCell, strSeparator)
Close #1
End Sub
The first line protects you (or more likely me) from not defining variables before using them.
The Function creates a fairly generic concatenation routine that takes a range and a string and creates a returned string with nonblank values within the range concatenated and separated by the string. The msgbox commands can be uncommented (by removing the ' at the start of the line) if you want to see what is going on. The "MsgBox strReturn" will unlikely display the full amount of the concatenated data due to size limitations of that function, but may during testing.
The Sub actually makes the specific identification of the range to be processed and identifies the separator to be used. It writes the result to a test file. If you want a different separator (perhaps a " | " rather than a "|" you would specify it here. If you want a different sheet name, you would replace "Data" with the name of your sheet (in quotes). If you have a different range, you would replace the "A1:DX3000" with your range (also in quotes). If you want to use a different file name, you would specify that in the strFileName variable (along with a path to the location you want to use).
I am a bit of a hacker when it comes to VBA, so someone may suggest some stylistic or technical improvements, but this should get you started. I did create a 700,000 byte file with this, so it will handle a lot of data.
I hope this points you in a better direction than my earlier post.
What I tried next were some manual steps. I tried using a Macintosh CSV file but could not work with it because I was having trouble interpreting the end-of-line characters, so I settled for allowing Excel to create an MS-DOS text file in this process. Your actual process may need to be adapted to your environment. I used MS Word instead of Notepad or Notepad++ in the hopes that Microsoft has already adapted their search/replace functionality to the MAC.
With the data in a sheet called "Data" in columns A1..DX3000, I created a second sheet with the following formula in A1:
=SUBSTITUTE(TRIM(SUBSTITUTE(SUBSTITUTE(Data!A1&","&Data!B1&","&Data!C1&","&Data!D1&","&Data!E1&","&Data!F1&","&Data!G1&","&Data!H1&","&Data!I1&","&Data!J1&","&Data!K1&","&Data!L1&","&Data!M1&","&Data!N1&","&Data!O1&","&Data!P1&","&Data!Q1&","&Data!R1&","&Data!S1&","&Data!T1&","&Data!U1&","&Data!V1&","&Data!W1&","&Data!X1&","&Data!Y1&","&Data!Z1&","&Data!AA1&","&Data!AB1&","&Data!AC1&","&Data!AD1&","&Data!AE1&","&Data!AF1&","&Data!AG1&","&Data!AH1&","&Data!AI1&","&Data!AJ1&","&Data!AK1&","&Data!AL1&","&Data!AM1&","&Data!AN1&","&Data!AO1&","&Data!AP1&","&Data!AQ1&","&Data!AR1&","&Data!AS1&","&Data!AT1&","&Data!AU1&","&Data!AV1&","&Data!AW1&","&Data!AX1&","&Data!AY1&","&Data!AZ1&","&Data!BA1&","&Data!BB1&","&Data!BC1&","&Data!BD1&","&Data!BE1&","&Data!BF1&","&Data!BG1&","&Data!BH1&","&Data!BI1&","&Data!BJ1&","&Data!BK1&","&Data!BL1&","&Data!BM1&","&Data!BN1&","&Data!BO1&","&Data!BP1&","&Data!BQ1&","&Data!BR1&","&Data!BS1&","&Data!BT1&","&Data!BU1&","&Data!BV1&","&Data!BW1&","&Data!BX1&","&Data!BY1&","&Data!BZ1&","&Data!CA1&","&Data!CB1&","&Data!CC1&","&Data!CD1&","&Data!CE1&","&Data!CF1&","&Data!CG1&","&Data!CH1&","&Data!CI1&","&Data!CJ1&","&Data!CK1&","&Data!CL1&","&Data!CM1&","&Data!CN1&","&Data!CO1&","&Data!CP1&","&Data!CQ1&","&Data!CR1&","&Data!CS1&","&Data!CT1&","&Data!CU1&","&Data!CV1&","&Data!CW1&","&Data!CX1&","&Data!CY1&","&Data!CZ1&","&Data!DA1&","&Data!DB1&","&Data!DC1&","&Data!DD1&","&Data!DE1&","&Data!DF1&","&Data!DG1&","&Data!DH1&","&Data!DI1&","&Data!DJ1&","&Data!DK1&","&Data!DL1&","&Data!DM1&","&Data!DN1&","&Data!DO1&","&Data!DP1&","&Data!DQ1&","&Data!DR1&","&Data!DS1&","&Data!DT1&","&Data!DU1&","&Data!DV1&","&Data!DW1&","&Data!DX1&","," ","~"),","," "))," ",",")
I copied this all the way down to A3000. This gave me a set of values on each line which were separated by a "|". Obviously, you would need to adapt this to the actual range you are processing. By reducing the size to a single line, I did not run into the 32,767 text limit. Hopefully, you won't either.
I saved this as an MS-DOS text file.
I opened it in MS Word and used its Find/Replace command to replace the paragraph break character (^p) with a "|" and re-saved this as a text file. The resulting file differed from what I wanted to create only by having an extra "|" at the end.
Hopefully, this will work for you or perhaps you need to save it as a Macintosh text file for the Mac version of MS Word to be able to replace the characters at the end of each line.
Not sure if this helps you achieve what you need. It certainly could work if you only need to do this once or occasionally, but it may be a bit of a pain if this is a frequent task.

.FormulaR1C1 containing variables with values read from files

The code lines
.FormulaR1C1 = "=IF(R1C1="&Chr(34)&text&Chr(34)&",1,0)"
.FormulaR1C1 = "=IF(R[-3]C[2]="&Chr(34)&text&Chr(34)&",1,0)"
are examples of the usual manner to insert R1C1 formulas in target cells when the addresses of the target cells are already known. No problem with that.
BUT...I can't find the way to devise a general R1C1 formula that can be used to do the job when the addresses of the target cells aren't previously known, but they are read from different text files instead, and both, rows and columns, may vary from one occasion to the next, getting inserted by means of loops. The following code lines can give an idea of what I'd like to have in a text file to be read, inserted in a cell and work properly:
"=IF(R[-"&varRow&"]C["&varCol&"]="&Chr(34)&text&Chr(34)&",1,0)"
"=IF(R"&varRow&"C"&varCol&"="&Chr(34)&text&Chr(34)&",1,0)"
In other words, I can't figure out how to use variables for the numbers of rows and columns in the examples given above. I asume such a possibility exists, but I have ran out of ideas on how to implement it. I've implemented some, but I've just gotten the code to be inserted as the value for the cells or the #NOMBRE(#NAME) error.
What is a solution to this problem?
I am not able to "replay" your issue...or I understood it wrong.
If I use this code:
Range("C2").Select
Dim varRow As String
Dim varCol As String
varCol = 2
varRow = 2
Range("A1").FormulaR1C1 = "=IF(R[" & varRow & "]C[" & varCol & "]=" & Chr(34) & Chr(34) & ",1,0)"
The right formula appears in cell A1 and does its job.
To write this in a file, just put the formula string as it is in a string variable and write it to a file.
Scott, SJR, and anyone reading this post.
Apologies in advance for the length of this post, but I think that the matter deserves it.
I have managed to solve the situation regarding the FormulaR1C1 format when it is to be loaded from a text file. However, I'm not totally sure of the why and the how involved in the analysis and the solution. Maybe you can give a thorough explanation about it.
The fact is that the solution (formula format) you have given, quite similar to the one given by Microsoft and similar to the result obtained when recording the macro in entering the procedure by hand, when entered as plain text in a text file and then read from it and placed in a cell just doesn't give the result expected. An example of the original data in a text file:
22_2_|"=IF(R[" & varRow & "]C[" & varCol & "]=" & Chr(34) & varValue & Chr(34) & ",1,0)"
"22" would be the row number (varRow) and "2" the column number (varCol).
I tried every different logical possibility that came to my mind over and over, all of them with similar results: failure. Then I decided to forget logic and try random thoughts.
First thing I did on this new trail was to revise the original idea: use a general formula exclusively with variables in it. I realized that I was making a stupid mistake giving the value for the variable -for instance, the column and row number- and right next to it placing the variable in the formula at the same time. Why to do it that way? Why not to place the numbers for the row and column directly in the formula? After all, the formula should work the same all the way.
An example for this new approach could be:
"=IF(R[number]C[number]=" & Chr(34) & varValue & Chr(34) & ",1,0)"
I did it this way and tested it, with dissapointing results, similar to the ones obtained at first.
Then I thought about the quotes used for the formula format. Could it be possible that the reason for the problem might have to do with them? Well, there was only one way to find out. So I tried it this way, then this other way, an other..and other...and then...EUREKA!...This is what I got, that finally works:
=IF(R22C2="value",1,0)
As you may notice, the final formula ends up having no variables at all. That was a stupid idea on my side, considering that the data is going to be obtained from a text file. So, forget the variables. BUT...and this is a very important "BUT"...
Notice what has happened to the quotes. The only ones remaning are the ones that enclose the value that is to be compared. All the others have dissapeared. And this is the only way in which the formula, when read from the text file and inserted in a cell, ends up as a functional formula in the cell, and not as the value for the cell or as a "#NAME?" error. And now, a very important question: WHY? My answer for it: I have no idea.
I must suppose -and that's the most I can do at this moment- that the fact the formula is being read from a text file and inserted as text in the cell is handled by Excel in a "certain way" that already includes -behind curtains- the necessary quotes in the internal works of the worksheet. So, if you previously include some quotes, the formula ends up having an excess of them and the final product is not the one expected. Might that be it? Who can tell? Well, what matters to me is that, as strange as it may look, it now works as I wanted it. Give it a try yourself, and then tell me.
Well, pals, that's it. And thanks a lot to all for your kind and timely help.

Substituting a calculated range into a lookup

In the contents of cell F3 I have the following formula (which takes one value on the current row and looks it up in another sheet, returning a range based upon where it was found):
="'OBS Procedures'!" & ADDRESS(MATCH(INDIRECT(ADDRESS(ROW(),1)),'OBS Procedures'!A:A,0),3) & ":" & ADDRESS(MATCH(INDIRECT(ADDRESS(ROW(),1)),'OBS Procedures'!A:A,0),50)
which outputs, as it ought, the text : 'OBS Procedures'!$C$1:$AX$1
All good so far.
I have in another cell (which outputs the rightmost filled cell of the range generated by cell F3):
=LOOKUP(2,1/(INDIRECT(F3)<>""),INDIRECT(F3))
And this outputs , as it ought, the text : 5 : 18/07/2016
Great.
But I'm wanting to get rid of cell F3 entirely, and incorporate the formula for F3 into the second formula above.
Simply copying in the F3 formula between the second INDIRECT() works fine. But copying it into the first F3 generates #N/A.
Specifically :
=LOOKUP(2,1/(INDIRECT("'OBS Procedures'!" & ADDRESS(MATCH(INDIRECT(ADDRESS(ROW(),1)),'OBS Procedures'!A:A,0),3) & ":" & ADDRESS(MATCH(INDIRECT(ADDRESS(ROW(),1)),'OBS Procedures'!A:A,0),50))<>""),INDIRECT(F3))
generates #N/A
Whereas even
=LOOKUP(2,1/(INDIRECT("'OBS Procedures'!$C$1:$AX$1")<>""),INDIRECT(F3))
and generates what I expect
(Keeping the intermediate cell or doing this in VBA, which is my usual go-to for anything more than simple formulae, isn't an option in this case)
I'm simply not seeing what I am doing wrong here - can anyone enlighten me, please? :)
As hinted at in my comments, I'm afraid to say that the construction you are currently using is a very poor one indeed, and could be replaced with a set-up which is both non-volatile and far more efficient.
The reason for your error is due to the manner in which you are using the ROW function. In fact, for generating incremental integers in this manner, ROW is, for several reasons, inferior to ROWS (https://excelxor.com/2014/08/25/row-vs-rows-for-consecutive-integer-generation/).
Despite its inferiority in this respect, however, this does not usually lead to errors. Here, you are a little unfortunate in that, due to the nature of your construction, Excel is expecting an array output from the portion:
ROW()
As such, and assuming for the sake of argument that the formula we are discussing is in row 1, this part:
ADDRESS(ROW(),1)
which, under different circumstances, would evaluate to:
"$A$1"
here evaluates instead to:
{"$A$1"}
since the LOOKUP is lending the necessary array-coercion to the overall construction such that the ROW() portion is expected to deliver an array output, albeit one containing just a single value. In fact, here (and you can see this by using the Evaluate Formula feature), this part evaluates to (for row 1), not 1, but {1}.
And so here:
INDIRECT(ADDRESS(ROW(),1))
is:
INDIRECT({"$A$1"})
and since, in general, INDIRECT is incapable of resolving an array of values (though this can be achieved via further manipulation), this part errors.
To avoid this, you can use ROWS, e.g. (for row 1):
ADDRESS(ROWS($1:1),1)
or else coerce the array output from ROW into a non-array type via some suitable additional function, e.g. SUM, viz:
ADDRESS(SUM(ROW()),1)
That said, I really think that you need to rethink your whole approach here. For example, this:
=LOOKUP(2,1/(INDEX('OBS Procedures'!$C:$AX,MATCH(A1,'OBS Procedures'!A:A,0),)<>""),INDEX('OBS Procedures'!$C:$AX,MATCH(A1,'OBS Procedures'!A:A,0),))
assumed to be in row 1, would be equivalent to your:
=LOOKUP(2,1/(INDIRECT("'OBS Procedures'!" & ADDRESS(MATCH(INDIRECT(ADDRESS(ROW(),1)),'OBS Procedures'!A:A,0),3) & ":" & ADDRESS(MATCH(INDIRECT(ADDRESS(ROW(),1)),'OBS Procedures'!A:A,0),50))<>""),INDIRECT(F3))
(again assumed to be in row 1), and not only does not require the prior construction of a text string representing a range, but is also concise and, importantly, non-volatile.
Regards

Writing numbers to cells as text

I am using VBA to manipulate a dataset and ahve run into a problem; I need to write some values to cells but keep them as strings rather than numbers so later filters will work with other database data.
I was using Cell.Value = x which does the job but is there a way of getting it to write with a string?
I have a feeling this has a simple answer but my google-fu is weak.
Consider
1) Formatting the cell as text (available on a right click as 'Format Cells')
2) Clobbering the cell completely with Cell.Value = "'" & CStr(x)
I prefer (1) since it is separating data from presentation; always good programming practice.

Resources