Applescript get all values of Excel column - excel

I am trying to store all the values of an excel column in an array.
set rangeDate to {value of range "A14:A100"}
repeat with date in rangeDate
if (date as string is equal to "01/01/2001") then
log "It works"
end if
end repeat
In my Excel I do have an exact date of 01/01/2001 formatted in the specified columns. When I remove the range and it is just cell A14 (where the date is) it works. But when I include the range A14:A100 it doesn't work.
I am new to applescript, I guess that it doesn't store the values as array values and instead a string object? Any help would be appreciated

You have 4 issues :
1) value of range should not be between {}, but between ()
2) 'Date' is a reserved word in Applescript, so you should not use it as the variable in the loop. I replaced it with 'myDate'.
3) instead of converting your date to string to compare with "01/01/2001", it is quicker to keep comparing 2 dates, and then, compare with the date "01/01/2001"
4) I think it is a bug (at least with my Excel version), but the rangeDate variable is not a list of dates as expected, but for me a list of list : {{01/02/01},{02/02/01},………} Therefore, each member of 'rangeDate' is not a date, but a list made on one item which is a date ! I am not sure, but it could also be that range definition could be a list of ranges... So I am using item 1 of sub list.
Anyway, script bellow is working :
tell application "Microsoft Excel"
activate
tell active sheet of document 1
set rangeDate to (value of range "A14:A100")
repeat with mydate in rangeDate
set TheDate to item 1 of mydate
if TheDate = (date "lundi 1 janvier 2001 00:00:00") then
log "It works"
end if
end repeat
end tell
end tell

Quickly getting the values of a range of cells is great news! But even better is that you can fill in the values of a range by defining the value of that range. This is SO MUCH FASTER than doing it one cell at a time.
When I tried getting the value of a column (a range of cells), I received a list of lists. Each item in the list had only one value - that is the value of the cell.
To speed up complex operations, once you've got the list of values, take the process out of the "tell Excel" block and let AppleScript do the calculations. Then turn the result back into a list of lists and define the value of the range in Excel.

I had a problem reading ranges with some cells containing #VALUE! (failed formulas). I didn't find a solution on the Internet, so I thought it would be a good idea to share my solution here. Comments & improvement are surely welcome. I'm inclined to think there is a more straightforward solution to the problem than this. :)
Getting all values with value of range can lead to a problem messing up the output of the script. AppleScript doesn't consider a cell's content "#VALUE!" (= missing values) a value since it is, well, missing. Therefore the script doesn't include the cell's content in the list of values. This obviously messes up the cell order in the values list, since it has less items than the actual range has cells. In this situation it is quite impossible to return each value to its original cell in the workbook. Adding ”of ranges” to the code includes all cells with missing values solving the problem.
N.B. The values will be displayed as a one-dimensional array. Handling multi-column ranges requires more work. Nonetheless the missing values are included.
set celVals to (value of ranges of range "A1:A4")
E.g. {2.2.2022, 1.1.2011, missing value, 3.3.2033}
In order to return the values back to the workbook it is required to build back the list of lists. A missing value will be written to its cell as an empty string. Of course the original (failed) formula can be written instead, if needed.
N.B. again. This code applies to one column situation only. A little more is needed to put back a multi-column range. I'm sure you'll manage. :D
set returningCelVals to {}
repeat with i from 1 to count of celVals
set end of returningCelVals to {item i of celVals}
end repeat
set value of range ("A1:A4") to returningCelVals
EDIT: I knew there is a better solution. Here it is:
set celVals to string value of range "A1:A4"
String value gives a two-dimensional array of values and error messages of the range. String value gives also e.g. cell's currency symbols, so it is perhaps not suitable to all situations.

Related

Advanced finding connected value in selected row list box

I have in my userform listbox which display 10 columns. All rows have an individual ListIndex.
I would like to display f.e. in Msgbox value after select some row and click button "Show".
But this value is not avalible in listbox. So, I thought that all rows have individual ListIndex, I can use VLOOKUP for finding my target.
For example: I selected row no. 1 then ListIndex is 1 and this is my Look up Value. In my database sheet I have the same individual ID values.
Then of course, I have to declarate range, number of column and parameter "False".
Theoretically, I expect the result after that but it doesn't work.
My code:
Dim indexno As Long 'this is my delcaration for finding Look up Value
Dim myVLookupResult As Long 'this is my declaration for VLookup Result
indexno = ListBoxResult.ListIndex 'my Lookup Value is dynamic and depend of selected row
myVLookupResult = Application.VLookup(indexno, Worksheets("DataBase").Range("A1:J100"), 5, False)
MsgBox myVLookupResult 'should display result of VLOOKUP
But the result is error: Run-Time error: 13 - Type mismatch.
I guess the problem is with convert type of Dim from int to string.
Someone could support me, please? Thanks in advance!
The lookup value of a VLOOKUP worksheet function must be of the same data type as the data in which it is to be found. In your code the lookup value is of Long data type. If the column in which you are looking for it has text, the number you are looking for will not be found. So, you change the lookup value to Variant and hope that Excel will be able to work out what you want. But the better way is to examine your data column and look up the type of value you actually have there.
Next, given you are looking for a number which you have assigned to a Variant and Excel, in consequence, managed to find the string-equivalent of that number, that would be the functions return value, a text string. In most cases Excel is quite generous in this sort of situations but if it does complain about "Data Type" then it's because you are trying to assign a text string to a variable of Long data type.
Other than that, as #Michal Palko already pointed out, the ListBox's ListIndex is 0-based. If the numbers in your worksheet are 1-based the return of VLOOKUP won't be "totally different" but it will be from the adjacent row and therefore unexpected.
But I want to alert you to another possibility. As you know, you can load many columns into your list box. You can show some of the columns and hide others but you can access them all with code like this:-
With ListBox1
Debug.Print .List(.ListIndex, 3)
End With
This snippet will print out the value of the 3rd column in the row of ListIndex. You might also assign this value to a variable and, perhaps, have no need for VLOOKUP.

Why is my VBA with relative references returning incorrect references?

I'm trying to develop an Excel application that asks our 4D database for information. To do that, I built a query builder and it works. Now I want to make it more generic so that when I call the query builder, I can pass it a range in which the tables and fields the query is based on are stored. Here is a line where I call the sub and pass it the parameters:
QueryDatabase Worksheets("TablesAndFields").Range("A2:R20"), Worksheets("TablesAndFields"), Worksheets("Import")
Here is the first line in the sub:
Sub QueryDatabase(QuerySpecs As Range, QuerySheet As Worksheet, TargetSheet As Worksheet)
One of the things I need to do is have the VBA figure out the row of the last fields in the various columns. Here is my current code:
LastRowReportTables = QuerySpecs.Offset(0, 3).End(xlDown).Row
LastRowQuery = QuerySpecs.Offset(0, 6).End(xlDown).Row
LastRowSort = QuerySpecs.Offset(0, 14).End(xlDown).Row
This returns the same value for all 3 of them (the second line of the range). It seems to do this regardless of which cells have values in them. For instance, in the case of the range specified above it will return 3. If the range is "A22:R40" it returns 23. What I really need is for it to return the row relative to it's position in the range, but I could fake that if necessary by subtracting the largest multiple of 20 less than the result. (I'm formatting my query builder to fit in 19 rows + a buffer row.) So far, I haven't even been able to get it to return different results for the LastRow variables.
In addition to the Offset method you see above, I've also tried putting it in a With QuerySpecs... End With block. I don't remember the exact result, but I couldn't get that to work either.
The next thing I will need to do is pull values out of the various cells kind of like this:
strStartCell = QuerySpecs.Offset(0, 18).Value
This throws a Run time error 13: Type mismatch. Does anybody have any advice on how to accomplish my goals? Thank-you!
I suggest reading the documentation of the resources you are trying to use.
This can be done by selecting the word which you want to search in the documentation, then press the [F1] key, that will take you to the Microsoft documentation page (i.e. if you select Row and press [F1] will take you to the page Range.Row Property (Excel).
Reading the documentation for Row and Offset will help you understand these Range.Properties and the returns you are getting from your code.
To get the last non-empty row in a range of one column, assuming all data in the range is continuous (i.e. there are no empty cells between rows with content), use these lines:
With WorksheetFunction
LastRowReportTables = .CountA(QuerySpecs.Columns(4))
LastRowQuery = .CountA(QuerySpecs.Columns(7))
LastRowSort = .CountA(QuerySpecs.Columns(15))
End With
Note: If the "QuerySpecs" range is a kind of database, all columns in the fields should have the same number of records, if so, it should be sufficient to obtain the last row for one field only.
To obtain the value of a cell within a range, use this line:
This returns the value of the field (column) 18,row 13 of the QuerySpecs range
strStartCell = QuerySpecs.Cells(13, 18).Value2
The Run-time error 13: Type mismatch is generated when trying to assign an array to a string variable.

Countif without converting TEXT to NUMBER

I'm trying to set a macro, that will compare multiple lists, create a cross-table with unique values and display how many times the value is present in each list.
I'm doing OK, with one exception. When using Countif(s) formula =COUNTIFS(Source!$A$2:$A$5;[#Values]), it internaly converts "Text numbers" (e.g. 001, 00000002) into Numbers (e.g. 1, 2). I would like to avoid this behaviour and search for EXACTLY the same value, without converting.
Example data:
List1 List2
1 0001
0001
2
00000002
What I'm getting right now (WRONG):
What I want to get (EXPECTED):
My question:
How can I count EXACTLY the values in the list, without internaly converting "Text numbers" to Numbers?
This array formula could be suitable for you:
=MIN(SUMPRODUCT(IF(LEN($A$2:$A$5)=LEN(Table1[#Values]),1,0)),SUMPRODUCT(IF($A$2:$A$5=Table1[#Values],1,0)))
Put and CTRL+SHIFT+ENTER. In Table1[#Values], Table1 is your table name.
I solved the problem with simple UDF.
Function countifsExact(criteria_range As Range, criteria As String) As Long
Dim cell As Range
For Each cell In criteria_range
If cell = criteria Then
countifsExact = countifsExact + 1
End If
Next cell
End Function
EDIT1:
I made another version of the UDF using some of the advice given in Writing efficient VBA UDFs (Part 1) by Charles Williams and in Writing efficient VBA UDFs (Part 2) by Charles Williams.
Mainly:
Storing the criteria_range once in a Variant variable avoiding a large overhead each time a VBA program transfers data from an Excel cell to a VBA variable
Using Range.Value2 property, instead of Range.Value
Using excel MATCH function to get a starting point in the sorted range, with exit on value change.
EDIT2:
Yet a much better solution is to use the SUMPRODUCT formula as such:
=SUMPRODUCT(--(EXACT(Source!$A$2:$A$5;[#Values])))

VBA - Macro That Finds Specified Header, Filters Values in Various Sheets where Column Locations Change

Trying to figure out a Macro that will do the following:
1) Find a specified header (ie: "status")
2) Filter this column to a specified value (ie: "discontinued")
3) Once filtered, find another specified column (ie: "replaced with")
The challenge for me is I can't figure out how to set it so the column "Field" is variable to the sheet its on.
I have multiple files that have the "status" header in different locations, (one file has it in column c, another in column f, etc). Is there a way to have excel find that specific column in each sheet is and filter it?
Find a specified header
You would implement this quite exactly as you'd subconsciously do in your head while "finding a specific header": iterate all columns from left to right until you find it, ...and throw a fit when it's not there. The function should be generic enough to not be attached to any specific worksheet, so you should pass it a worksheet as a parameter.
Something like this:
'returns column number of specified header, or -1 if not found.
Public Function FindHeader(sheet As Worksheet, header As String) As Integer
...
End Function
Find your header row, and loop through the non-empty cells. Your exit conditions would be either you've iterated all non-empty cells in the header row, or you've found a cell that matches the content you're looking for. When the loop exits, you need to know two things: whether the header was found, and if it was, under which column.
Filter this column for a specified value
You don't say how the value gets specified, so I'll assume all you need is a Sub that takes an Integer for the header column to filter, and a String parameter for the value to filter with. Using AutoFilter -related methods should get you there. Again you want to be able to use this code for any sheet, so pass one as a parameter:
Public Sub FilterForSpecificValue(sheet As Worksheet, columnIndex As Integer, filter As String)
...
End Sub
Find another specified column
Well, just reuse the function from #1, passing it the same sheet with another column header:
result = FindHeader(ActiveSheet, "AnotherHeader")
That's about as much as I can give you for the question you've asked; feel free to ask SO for any specific programming issues you might encounter implementing this. However it's possible you won't even need to ask, because it's very likely that somebody else somewhere has documented exactly how to do it - here are a couple links that can help you:
Selecting an entire row of data (StackOverflow)
Looping Through a Range of cells (MSDN)
StringContainsAny (StackOverflow)
AutoFilter Method (MSDN)

Is there an Excel formula that, given the ending cell, returns the starting cell of a block of data?

The data in column A looks like this.
RowHeaderThatIsText
RowHeaderThatIsNumber
empty
empty
empty
empty
empty
14.00
-3.00
-4.00
The project goal is to to calculate summary statistics for the series of numbers and update the summary each month.
My goal is to allow a new number to be added to the series and have the summary update automatically.
Here's what I've done so far.
Define a range named LastCell with the formula
=INDEX($A:$A, MAX(($A:$A <> "") * (ROW($A:$A))))
This returns the last non-empty cell in the column. The data to summarize is always the last block of numbers.
Define a named range called HeaderOffset with the formula
=3
Used in the step 3.
Define a range named FirstCell with the formula
=OFFSET(LastCell, HeaderOffset - COUNTA($A:$A), 0)
This returns the first cell of the last block of numbers if, as is the case with the data I'll be summarizing, the cells between the first and last blocks are empty.
Define a range named DataBlock with the formula
=FirstCell:LastCell
So far so good. This allows one to enter =SUM(DataBlock) into any cell and get the expected result of 7.00. And, if I add another value, say 3.00, to the list the SUM result will update to 10.
The part I don't like is HeaderOffset. If another row is added to the header, HeaderOffset needs to be updated from =3 to =4. What if I forget to update HeaderOffset? This lead me to the problem I can't currently solve...
Is there an Excel formula that, given the ending cell, returns the starting cell of a block of data? Basically I'm looking for a FirstCell formula that removes the need for HeaderOffset.
As a bonus I was trying to do this whole thing without using volatile Excel functions. I failed by using OFFSET. Solving this is great. Solving it without volatile functions is ideal.
Is FirstCell always the first number? If so try this to define FirstCell
=INDEX($A:$A,MATCH(TRUE,ISNUMBER($A:$A),0))
That's an "array formula" if entered on the worksheet but doesn't need any special treatment if used in the "refers to:" box to define a named range.
Note: if your final aim is the sum of the numbers then could you just use =SUM(A:A) [I assume that's oversimplifying the issue?]
Revised given comment below:
Try this for LastCell
=INDEX($A:$A,MATCH(9.99E+307,$A:$A))
and this one for FirstCell
=INDEX($A:$A,MATCH(2,1/($A$1:LastCell=""))+1)
assumes LastCell is numeric (although that can be changed if required)

Resources