Error ` saveas method workbook failed` in vba Excel - vba

I have basic data with multiple excels which i need to sorted out the data based on the columns whereas the sorted data which has a unique values and these uniques values i put in array ,based on unique values a new workbook has to be create.
My problem:
When im excuting the macro sometimes its showing the error as
saveas method workbook class failed
How can i freeze a column?
My code:
sub marcel()
Dim sArray as string
Dim saArray as string
Dim Lastrow_Sub As Integer
Dim Lastrow_Aging As Integer
Dim Array_Sub As Variant
Dim Array_Sub_Aging As Variant
Dim rngFilter_Ws2 as range
Dim Sht6 as worksheet
Dim ColumnsToRemove2 as variant
Dim j2 as integer
Dim vItem2 as variant
Check_date = Format(Date, "yymm")
Sheets("q_Aging_base_data_incl_FDB").Columns("D:D").AdvancedFilter Action:=xlFilterCopy, _
CopyToRange:=.Range("AY1"), Unique:=True
Lastrow_Aging = .Cells(.Rows.Count, "AY").End(xlUp).Row
Array_Sub_Aging = Range("AY2:AY" & Lastrow_Aging)
saArray = Array_Sub_Aging(m, 1)
Sheets("BASE qry_Inventory Activation b").Columns("H:H").AdvancedFilter Action:=xlFilterCopy, _
CopyToRange:=.Range("AZ1"), Unique:=True
Lastrow_Sub = .Cells(.Rows.Count, "AZ").End(xlUp).Row
Array_Sub = Range("AZ2:AZ" & Lastrow_Sub)
sArray = Array_Sub(k, 1)
If sArray <> "APE" And saArray = "APE" Or sArray <> "XXUMA" And saArray = "XXUMA" Then
Dim NewBook_Sub_Aging As Workbook
Set NewBook_Sub_Aging = Workbooks.Add
With NewBook_Sub_Aging
.Title = saArray
NewBook_Sub_Aging.Worksheets("sheet1").Name = "Aging Inventory"
With rngFilter_Ws2
.AutoFilter Field:=4, Criteria1:=saArray, Operator:=xlFilterValues
.AutoFilter Field:=15, Criteria1:="reporting relevant Location", Operator:=xlFilterValues
.AutoFilter Field:=32, Criteria1:="<>(a) 0 - 360", Operator:=xlFilterValues
Set rngCopyAging_Sub = .SpecialCells(xlCellTypeVisible)
.AutoFilter ' Switch off AutoFilter
End With
rngCopyAging_Sub.Copy Destination:=NewBook_Sub_Aging.Worksheets("Aging Inventory").Cells(1, 1)
' Delete unwanted columns for subregions(APE and XXUMA) Aging
Set Sht6 = NewBook_Sub_Aging.Worksheets("Aging Inventory")
ColumnsToRemove2 = Array("Period", "AP_BU", "Subregion", "Strategic_BU", "Company_Code", "Company_name", "Plant_name", _
"Rep Location", "Storage_Location", "Storage_Location_name", "Date_last_goods_rec", "Stock_type", _
"Stock_type_name", "Kind_of_Material_name", "Supplier_name", "SummevonVEU_OIV1", "Days_since_production", _
"Remaining_shelf_life", "APO_Planner", "S_SCM_or_SVC")
For j2 = LBound(ColumnsToRemove2) To UBound(ColumnsToRemove2) Step 1
vItem6 = Application.Match(ColumnsToRemove2(j2), Sht6.Rows(1), 0)
Debug.Print vItem6
If IsNumeric(vItem6) Then
Sht6.Columns(vItem6).Delete
End If
Next j2
NewBook_Sub_Aging.Worksheets("Aging Inventory").Cells.EntireColumn.AutoFit
NewBook_Sub_Aging.Worksheets("Aging Inventory").Range("A1:P1").AutoFilter
NewBook_Sub_Aging.Worksheets("Aging Inventory").Range("A2:P2").Select
ActiveWindow.FreezePanes = True
.SaveAs Filename:="KPI" & " " & saArray & " " & Check_date & ".xlsx"
Application.DisplayAlerts = False
NewBook_Sub_Aging.Close
End With
End If
end sub
can anyone please help me out how to resolve this issue.

Both sArray and saArray don't seem to be defined as an actual array. Arrays are defined like this:
Dim myArray() as Integer
Any value assigned to any of the array positions need to be put in this way:
myArray(i) = 815 'i being a position, like 0 or 5
When you want to get the value held in a certain position, you would also reference that position. So, if I wanted the value in the 4th position of may array, I'd do
myArray(3) 'Arrays are zero-index based.
so when you do:
.SaveAs Filename:="KPI" & " " & saArray & " " & Check_date & ".xlsx"
You're giving the Filename parameter a variant variable that may contain nothing, so the line would throw an error.
I'll assume saArray and Check_date are global variables coming from a different Sub and the array is created and its values assigned over there; it would still throw an error, since you're giving the SaveAs module an array, and not a string. You HAVE to give it one of the values IN the array (And it has to be a string, since Filename has to be a string).

Related

Restoring AutoFilter with xlFilterValues raises error 13 Type mismatch if Len(Criteria1(i))>255?

I've inherited some VBA code that attempts to save and restore filters in Excel 2010-2016 (I'm testing on Excel 2016 - 32bit, 16.0.4549.1000). I've already learned this is pretty much impossible to do properly and in a sane way (e.g. Get Date Autofilter in Excel VBA), but the number of different ways it can fail amazes me.
In particular, it seems that an xlFilterValues filter, which selects cells with value longer than 254 characters, can not be saved and restored:
the values in .Criteria1(i) are truncated to 256 chars when reading,
if you save the criteria array (saved = .Criteria1) and attempt to restore it later via .AutoFilter Criteria1:=saved ..., the .AutoFilter will report "Run-time error '13' Type Mismatch" if any Len(saved(i)) >= 256
The testcase, which can be run in an empty workbook is listed below.
Can everyone reproduce? Any thoughts on an easy way around this limitation?
Sub test()
Const CRITERIA_LEN = 257 ' 255 or less works, 256 throws error
Dim ws As Worksheet: Set ws = ActiveSheet
Dim filtRng As Range: Set filtRng = ws.Range(ws.Cells(1, 1), ws.Cells(5, 1))
Dim s100 As String: s100 = "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890"
Const PREFIX_LEN = 2 ' the length of the "=X" prefix in Criteria1(i)
Dim longStr As String: longStr = Mid(s100 & s100 & s100, 1, CRITERIA_LEN - PREFIX_LEN)
ws.Cells(1, 1).Value2 = "header"
ws.Cells(2, 1).Value2 = "A" & longStr
ws.Cells(3, 1).Value2 = "B" & longStr
ws.Cells(4, 1).Value2 = "C" & longStr
ws.Cells(5, 1).Value2 = "another value"
If Not ws.AutoFilterMode Then
filtRng.AutoFilter
End If
SET_BREAKPOINT_HERE = 1
' after hitting the breakpoint use the autofilter to select the three long values by typing '123' into the autofilter search
Dim fs As Filters: Set fs = ws.AutoFilter.Filters
If Not fs.Item(1).On Then Exit Sub
Debug.Print "Operator = " & fs.Item(1).Operator ' should be xlFilterValues (7)
Debug.Print "Len(.Criteria1(1)) = " & Len(fs.Item(1).Criteria1(1)) ' this is never larger than 256
Debug.Print "Len(.Criteria1(2)) = " & Len(fs.Item(1).Criteria1(2))
Debug.Print "Len(.Criteria1(3)) = " & Len(fs.Item(1).Criteria1(3))
' Save the filter
Dim crit As Variant
crit = fs.Item(1).Criteria1
'crit = Array("=A" & longStr, "=B" & longStr, "=C" & longStr) ' This line has the same effect
ws.AutoFilter.ShowAllData ' reset the filter
' Try to restore
filtRng.AutoFilter Field:=1, _
Criteria1:=crit, _
Operator:=xlFilterValues
' => Run-time error '13' Type Mismatch
End Sub
I just came across this issue today also. Really unfortunate that this has this limitation. I think the best thing we can hope to do is the following.
Function MakeValid(ByVal sSearchTerm as string) as string
if len(sSearchTerm) > 255 then
MakeValid = Left(sSearchTerm,254) & "*"
else
MakeValid = sSearchTerm
end if
End Function
Sub Test()
ActiveSheet.ListObjects("Table1").Range.AutoFilter Field:=1, Criteria1:= MakeValid(sSearchTerm), Operator:=Excel.XlAutoFilterOperator.xlFilterValues
End Sub
Ultimately, the way it works, is it bypasses the problem by using a pattern search (so matches the first 253 characters, and then it searches for any pattern from there). This won't always work, in fact it is bound to not work at some points, but it seems this is the best option we have (other than designing our systems around this issue)
Seems like this also works for arrays:
Function MakeValid(ByVal sSearchTerm as string) as string
if len(sSearchTerm) > 255 then
MakeValid = Left(sSearchTerm,254) & "*"
else
MakeValid = sSearchTerm
end if
End Function
Sub Test()
Dim i as long
for i = lbound(sSearchTerms) to ubound(sSearchTerms)
sSearchTerms(i) = MakeValid(sSearchTerms(i))
next
ActiveSheet.ListObjects("Table1").Range.AutoFilter Field:=1, Criteria1:= sSearchTerms, Operator:=Excel.XlAutoFilterOperator.xlFilterValues
End Sub
It's kinda a bad solution but it sort of works

Appending to a cell value in VBA

I'm sure there's an obvious answer here, but I'm stuck. This part in particular is throwing 424: Object Required. The really odd part, to me, is that it does successfully append 0s to the column, but then halts, and doesn't continue.
If cellLen < 9 Then
Set C.Value = 0 & C.Value
End If
The rest of the code is below for clarity. In case it's not clear, this is the intended code flow:
Grabs named fields
Copies those columns to a new sheet
Renames them and deletes the original sheet
Creates some new sheets for use with a different script
Searches for missing leading 0s in a specific column
Adds them back in (this is the part the breaks)
Deletes rows where that specific column's cell value is 0
Pulls that cleaned-up column out to a new file and saves it
Sub Cleanup_Mapwise_Import()
Dim targetCols As Variant
Dim replColNames As Variant
Dim index As Integer
Dim found As Range
Dim counter As Integer
Dim headerIndex As Integer
Dim question As Integer
Dim rowCount As Variant
Dim colNum As Variant
Dim colLetter As Variant
Dim C As Range
Dim cellLen As Integer
' Add or remove fields to be copied here
targetCols = Array("gs_account_number", "gs_meter_number", "gs_amr_identification", _
"gs_amr_phase", "gs_city", "Name", "Phase", _
"gs_rate_schedule", "gs_service_address", _
"gs_service_map_location", "gs_service_number")
' Put the same fields from above in the desired order here, with the desired name
replColNames = Array("Acct #", "Meter #", "AMR ID", "AMR Phase", "City", _
"Name", "Phase", "Rate", "Address", "Srv Map Loc", "Srv Num")
counter = 1
ActiveSheet.Range("A1").Select
' This counts the number of columns in the source array and sets the index to that value
For index = LBound(targetCols) To UBound(targetCols)
Set found = Rows("1:1").Find(targetCols(index), LookIn:=xlValues, LookAt:=xlWhole, _
SearchOrder:=xlByColumns, SearchDirection:=xlNext, MatchCase:=False)
' This is basically an insertion sort, and ends up with the columns in A:K
If Not found Is Nothing Then
If found.Column <> counter Then
found.EntireColumn.Cut
Columns(counter).Insert shift:=xlToRight
Application.CutCopyMode = False
End If
counter = counter + 1
End If
Next index
' There is a more dynamic way of doing this, using index
' As it is, replace A:K with the range of actual data
' PROTIP: targetCols is 1-indexed, and has 11 entries -->
' A:K encompasses that entire array -->
' Add/subtract 1 for each entry you add/remove
Range("A:K").Cut
Set TargetSheet = Sheets.Add(After:=Sheets(Sheets.Count))
TargetSheet.Name = "Contributors"
Range("A:K").Insert
question = MsgBox("Do you want to delete the original sheet?", vbYesNo + vbQuestion, "Delete Sheet")
If question = vbYes Then
Sheets(1).Activate
Sheets(1).Delete
Else
End If
Sheets.Add.Name = "Data"
Sheets("Contributors").Move After:=Sheets("Data")
Sheets.Add.Name = "Graph"
Sheets("Graph").Move After:=Sheets("Contributors")
Sheets("Data").Activate
Range("A1").Value = "Date/Time"
Range("B1").Value = "kW"
Range("C1").Value = "Amps"
' Yes, counter is 0-indexed here, and 1-indexed previously
' headerIndex does an absolute count of 0 To # targetCols, whereas index is relative
' If you change these, there is a non-zero chance that the For will throw an error
counter = 0
Sheets("Contributors").Activate
ActiveSheet.Range("A1").Select
For headerIndex = 0 To (UBound(targetCols) - LBound(targetCols))
ActiveCell.Value = replColNames(counter)
' If you don't use a Range, it fits columns based on headers, which isn't large enough
' A1:Z500 is a big enough sample to prevent that
ActiveCell.Range("A1:Z500").Columns.AutoFit
ActiveCell.Offset(0, 1).Select
counter = counter + 1
Next headerIndex
' Find column number with meters numbers, then assign its corresponding letter value
colNum = Application.WorksheetFunction.Match("Meter #", Range("A1:ZZ1"), 0)
colLetter = (Split(Cells(, colNum).Address, "$")(1))
rowCount = Range(colLetter & Rows.Count).End(xlUp).Row
'Range(colLetter & "2:" & colLetter & rowCount).Select
'Selection.SpecialCells(xlCellTypeBlanks).Select
'Selection.Delete Shift:=xlUp
' Meter numbers are 9 digits, so if one is shorter, assume a trimmed leading 0 and append it
For Each C In Range(colLetter & "2:" & colLetter & rowCount).Cells
' If cell type isn't set to text, the 0s will be non-visible, which while not an issue for the CSV, is confusing
' Note that this does not persist, as CSVs have no way of saving Excel's formatting
C.NumberFormat = "#"
cellLen = Len(C.Value)
If C.Value = "0" Or cellLen = 0 Then
C.Delete shift:=xlUp
End If
If cellLen < 9 Then
Set C.Value = 0 & C.Value
End If
Next C
question = MsgBox("Do you want to create a CSV file with meter numbers for use with MDMS?", vbYesNo + vbQuestion, "MDMS File")
If question = vbYes Then
' Call CopyMeters for use with MDMS
Sheets("Contributors").Activate
CopyMeters
Else
End If
End Sub
Sub CopyMeters()
Dim index As Integer
Dim fileSaveName As Variant
Dim rowCount As Variant
Dim colNum As Variant
Dim colLetter As Variant
Dim cellLen As Integer
colNum = Application.WorksheetFunction.Match("Meter #", Range("A1:ZZ1"), 0)
colLetter = (Split(Cells(, colNum).Address, "$")(1))
rowCount = Range(colLetter & Rows.Count).End(xlUp).Row
MsgBox ("Filename will automatically be appended with ""Meter List""")
fileSaveName = Split(ActiveWorkbook.Name, ".")
fileSaveName = fileSaveName(LBound(fileSaveName)) & " Meter List"
'For Each C In Range(colLetter & "2:" & colLetter & rowCount)
' C.NumberFormat = "#"
' cellLen = Len(C)
' If C.Value = "0" Or cellLen = 0 Then
' C.Delete shift:=xlUp
' End If
' If cellLen < 9 And cellLen <> 0 Then
' C.Value = "0" & C.Value
' End If
'Next C
Range(colLetter & "1:" & colLetter & rowCount).EntireColumn.Copy
Set newBook = Workbooks.Add
newBook.Worksheets("Sheet1").Range("A1").PasteSpecial (xlPasteAll)
Selection.Replace What:="0", Replacement:="", LookAt:=xlWhole, _
SearchOrder:=xlByColumns, MatchCase:=False, SearchFormat:=False, _
ReplaceFormat:=False
Selection.Columns.AutoFit
newBook.SaveAs Filename:=fileSaveName, FileFormat:=xlCSV, CreateBackup:=False
End Sub
The error message is telling you that C is not an object. Therefore, you do not need the Set statement. Change your code to this:
If cellLen < 9 Then
C.Value = 0 & C.Value
End If
Why not just change the numberformat on the range? Or use a function for the value? A function would be something like
Public Function FormatValues(ByVal Input as String) as String
If Input <> vbNullString Then FormatValues = Format(Input, "000000000")
End Function
And it would be called like:
C.Value = FormatValues(C.Value)
But, if you're strictly interested in what the value looks like, and not as much as what the value is (since the leading zero will only be retained for strings) you could do something like this:
Public Sub FixFormats()
ThisWorkbook.Sheets("SomeSheet").Columns("A").NumberFormat = "000000000")
End Sub
This would format Column A of Worksheet "SomeSheet" to be of the format "0000000" which means numbers would look like "000000001", "000000002" so on so forth, regardless of whether something like "2" was actually entered.

Enquoting a cell value in double quotes: Excel VBA Macro

I want to put double quotes inside all cells in a particular column.
I have wrote the code to put double quotes but the problem is it is putting 3 double quotes around the value.
For Each myCell In ActiveWorkbook.Sheets("Sheet1").Range("B:B")
If myCell.Value <> "" Then
myCell.Value = Chr(34) & myCell.Value & Chr(34)
End If
Next myCell
The basic requirement is to split the excel file according to column B and save them as CSV files.
In the split filed, the values of column B and D must be enclosed within double quotes.
Full Code :
Option Explicit
Sub ParseItems()
Dim LR As Long, Itm As Long, MyCount As Long, vCol As Long
Dim ws As Worksheet, MyArr As Variant, vTitles As String, SvPath As String
Dim myCell As Range, transCell As Range
'Sheet with data in it
Set ws = Sheets("Sheet1")
'Path to save files into, remember the final \
SvPath = "D:\SplitExcel\"
'Range where titles are across top of data, as string, data MUST
'have titles in this row, edit to suit your titles locale
'Inserting new row to act as title, copying the data from first row in title, row deleted after use
ws.Range("A1").EntireRow.Insert
ws.Rows(2).EntireRow.Copy
ws.Range("A1").Select
ws.Paste
vTitles = "A1:Z1"
'Choose column to evaluate from, column A = 1, B = 2, etc.
vCol = 2
If vCol = 0 Then Exit Sub
'Spot bottom row of data
LR = ws.Cells(ws.Rows.Count, vCol).End(xlUp).Row
'Speed up macro execution
Application.ScreenUpdating = False
'Get a temporary list of unique values from key column
ws.Columns(vCol).AdvancedFilter Action:=xlFilterCopy, CopyToRange:=ws.Range("EE1"), Unique:=True
'Sort the temporary list
ws.Columns("EE:EE").Sort Key1:=ws.Range("EE2"), Order1:=xlAscending, Header:=xlYes, _
OrderCustom:=1, MatchCase:=False, Orientation:=xlTopToBottom, DataOption1:=xlSortNormal
'Put list into an array for looping (values cannot be the result of formulas, must be constants)
MyArr = Application.WorksheetFunction.Transpose(ws.Range("EE2:EE" & Rows.Count).SpecialCells(xlCellTypeConstants))
'clear temporary worksheet list
ws.Range("EE:EE").Clear
'Turn on the autofilter, one column only is all that is needed
'ws.Range(vTitles).AutoFilter
'Loop through list one value at a time
For Itm = 1 To UBound(MyArr)
ws.Range(vTitles).AutoFilter Field:=vCol, Criteria1:=MyArr(Itm)
'transCell = ws.Range("A2:A" & LR)
ws.Range("A2:A" & LR).EntireRow.Copy
Workbooks.Add
Range("A1").PasteSpecial xlPasteAll
Cells.Columns.AutoFit
MyCount = MyCount + Range("A" & Rows.Count).End(xlUp).Row - 1
For Each myCell In ActiveWorkbook.Sheets("Sheet1").Range("B:B")
If myCell.Value <> "" Then
myCell.Value = Chr(34) & myCell.Value & Chr(34)
End If
Next myCell
ActiveWorkbook.SaveAs SvPath & "po" & MyArr(Itm) & ActiveWorkbook.Sheets("Sheet1").Range("D1") & "." & Date2Julian(Date), xlCSV, local:=False
ActiveWorkbook.Close False
ws.Range(vTitles).AutoFilter Field:=vCol
Next Itm
'Cleanup
ws.Rows(1).EntireRow.Delete
ws.AutoFilterMode = False
Application.ScreenUpdating = True
End Sub
Function Date2Julian(ByVal vDate As Date) As String
Date2Julian = Format(DateDiff("d", CDate("01/01/" _
+ Format(Year(vDate), "0000")), vDate) _
+ 1, "000")
End Function
Sample Input Data :
24833837 8013 70 1105
25057089 8013 75 1105
25438741 8013 60 1105
24833837 8014 70 1106
25057089 8014 75 1106
25438741 8014 60 1106
Expected Output is Two files created with following data
File 1 :
24833837,"8013",70,1105
25057089,"8013",75,1105
25438741,"8013",60,1105
File 2:
24833837,"8014",70,1106
25057089,"8014",75,1106
25438741,"8014",60,1106
Resultant Output :
File 1 :
24833837,"""8013""",70,1105
25057089,"""8013""",75,1105
25438741,"""8013""",60,1105
Same for File 2
Kindly help. :)
Afaik, there is no simple way to trick Excel into using quotes around numbers when using the normal "save as csv"-procedure. You can, however, use VBA to save in whatever csv format you like.
Take code example from https://support.microsoft.com/en-us/help/291296/procedure-to-export-a-text-file-with-both-comma-and-quote-delimiters-in-excel
Just add an if-statement to determine whether to use quotes or not
' Write current cell's text to file with quotation marks.
If WorksheetFunction.IsText(Selection.Cells(RowCount, ColumnCount)) Then
Print #FileNum, """" & Selection.Cells(RowCount, _
ColumnCount).Text & """";
Else
Print #FileNum, Selection.Cells(RowCount, _
ColumnCount).Text;
End If
The WorksheetFunction.IsText will recognize your numbers as text if they are entered with a preceding ' (single high quote)
You would need to adjust the example to export the range you want with the pre-given filename from your code.
This little sub will do as you need. Just give it a filename fname, range to export as csv rg and a column number column_with_quotes - so something like this but with a range to suit:
save_as_csv_with_optional_quotes SvPath & "po" & MyArr(Itm) & ActiveWorkbook.Sheets("Sheet1").Range("D1") & "." & Date2Julian(Date), Range("A1:C5"), 2
Here is the sub:
Sub save_as_csv_with_optional_quotes(fname As String, rg As Range, column_with_quotes As Long)
Dim ff, r, c As Long
Dim loutput, cl As String
ff = FreeFile
Open fname For Output As ff
For r = 1 To rg.Rows.Count
loutput = ""
For c = 1 To rg.Columns.Count
If loutput <> "" Then loutput = loutput & ","
cl = rg.Cells(r, c).Value
If c = column_with_quotes Then cl = Chr$(34) & cl & Chr$(34)
loutput = loutput & cl
Next c
Print #ff, loutput
Next r
Close ff
End Sub
the problem is this line.
myCell.Value = Chr(34) & myCell.Value & Chr(34)
The quotes you are adding are then being quoted again when you export as CSV, hence three quotes each side of the value. A better option I think would be to change the number format of the myCell to be Text, rather than number. I haven't tried this but I think changing it to this should help.
myCell.Value = Chr(39) & myCell.Value
Chr(39) is an apostrophe and when you enter it as the first character of a cell value it forces the format to be Text.

excel vba - Using autofilter - can't pass the filtered range to a sub, it keeps passing the entire sheet range

I can't seem to figure this one out. I have a function and a sub where I call the function to get the unique values (from column N (text values)) from the range I've already selected from the autofilter. Somehow, the range keeps being the entire sheet range and not the selected.
Function UniquesFromRange(rng As Range)
Dim d As Object, c As Range, tmp
Set d = CreateObject("scripting.dictionary")
For Each c In rng.Cells
tmp = Trim(c.Value)
If Len(tmp) > 0 Then
If Not d.Exists(tmp) Then d.Add tmp, 1
End If
Next c
UniquesFromRange = d.Keys
End Function
Sub mainSub()
For Each key In fCatId.Keys
With wshcore
llastrow = wshcore.Range("A" & Rows.Count).End(xlUp).Row
.AutoFilterMode = False
.Range("A1:N" & llastrow).AutoFilter
.Range("A1:N" & llastrow).AutoFilter Field:=1, Criteria1:=fCatId(key)
lwmin = WorksheetFunction.Subtotal(5, Range("H:H"))
lwmax = WorksheetFunction.Subtotal(4, Range("H:H"))
'This does not work, I want to get the unique values from column N
'that are already in the filtered range. So far this shows
'all the values in the column not only the ones already filtered.
varArray = UniquesFromRange(Range("N:N"))
'I've also tried this:
'varArray = UniquesFromRange(Range.Cells)
'Debug.Print fCatId(key) & " - " & key & " " & lwmin & "-" & lwmax & fData(key) & " - " & Join(varArray, vbNewLine)
End With
Next key
Application.ScreenUpdating = True
End Sub
any suggestions?
Instead of
varArray = UniquesFromRange(Range("N:N"))
use
varArray = UniquesFromRange(Range("N1:N" & llastrow).SpecialCells(xlCellTypeVisible))
In response to the additional question asked in the comments, you could copy varArray to another sheet (assumed to already exist, and being referred to by the object wsOutput, and output to be written to column A) as follows
Dim r as Integer
For r = LBound(varArray) To UBound(varArray)
wsOutput.Cells(r, 1).Value = varArray(r)
Next

How can I go through all the formulas and array formulas of a worksheet without repeating each array formula many times?

I would like to write a VBA function, which outputs a list of all the single formulas and array formulas of a worksheet. I want an array formula for a range to be printed for only one time.
If I go through all the UsedRange.Cells as follows, it will print each array formula for many times, because it covers several cells, that is not what I want.
For Each Cell In CurrentSheet.UsedRange.Cells
If Cell.HasArray Then
St = Range(" & Cell.CurrentArray.Address & ").FormulaArray = " _
& Chr(34) & Cell.Formula & Chr(34)
ElseIf Cell.HasFormula Then
St = Range(" & Cell.Address & ").FormulaR1C1 = " _
& Chr(34) & Cell.Formula & Chr(34)
End If
Print #1, St
Next
Does anyone have a good idea to avoid this?
You basically need to keep track of what you've already seen. The easy way to do that is to use the Union and Intersect methods that Excel supplies, along with the CurrentArray property of Range.
I just typed this in, so I'm not claiming that it's exhaustive or bug-free, but it demonstrates the basic idea:
Public Sub debugPrintFormulas()
Dim checked As Range
Dim c As Range
For Each c In Application.ActiveSheet.UsedRange
If Not alreadyChecked_(checked, c) Then
If c.HasArray Then
Debug.Print c.CurrentArray.Address, c.FormulaArray
Set checked = accumCheckedCells_(checked, c.CurrentArray)
ElseIf c.HasFormula Then
Debug.Print c.Address, c.Formula
Set checked = accumCheckedCells_(checked, c)
End If
End If
Next c
End Sub
Private Function alreadyChecked_(checked As Range, toCheck As Range) As Boolean
If checked Is Nothing Then
alreadyChecked_ = False
Else
alreadyChecked_ = Not (Application.Intersect(checked, toCheck) Is Nothing)
End If
End Function
Private Function accumCheckedCells_(checked As Range, toCheck As Range) As Range
If checked Is Nothing Then
Set accumCheckedCells_ = toCheck
Else
Set accumCheckedCells_ = Application.Union(checked, toCheck)
End If
End Function
The following code produces output like:
$B$7 -> =SUM(B3:B6)
$B$10 -> =AVERAGE(B3:B6)
$D$10:$D$13 -> =D5:D8
$F$14:$I$14 -> =TRANSPOSE(D5:D8)
I'm using a collection but it could equally well be a string.
Sub GetFormulas()
Dim ws As Worksheet
Dim coll As New Collection
Dim rngFormulas As Range
Dim rng As Range
Dim iter As Variant
Set ws = ActiveSheet
On Error Resume Next
Set rngFormulas = ws.Range("A1").SpecialCells(xlCellTypeFormulas)
If rngFormulas Is Nothing Then Exit Sub 'no formulas
For Each rng In rngFormulas
If rng.HasArray Then
If rng.CurrentArray.Range("A1").Address = rng.Address Then
coll.Add rng.CurrentArray.Address & " -> " & _
rng.Formula, rng.CurrentArray.Address
End If
Else
coll.Add rng.Address & " -> " & _
rng.Formula, rng.Address
End If
Next rng
For Each iter In coll
Debug.Print iter
'or Print #1, iter
Next iter
On Error GoTo 0 'turn on error handling
End Sub
The main difference is that I am only writing the array formula to the collection if the current cell that is being examined is cell A1 in the CurrentArray; that is, only when it is the first cell of the array's range.
Another difference is that I am only looking at cells that contain formulas using SpecialCells, which will be much more efficient than examining the UsedRange.
The only reliable solution I see for your problem is crosschecking each new formula against the ones already considered to make sure that there is no repetition. Depending upon the amount of information and speed expectations you should rely on different approaches.
If the size is not too important (expected number of records below 1000), you should rely on arrays because is the quickest option and its implementation is quite straightforward. Example:
Dim stored(1000) As String
Dim storedCount As Integer
Sub Inspect()
Open "temp.txt" For Output As 1
For Each Cell In CurrentSheet.UsedRange.Cells
If Cell.HasArray Then
St = Range(" & Cell.CurrentArray.Address & ").FormulaArray = " _
& Chr(34) & Cell.Formula & Chr(34)
ElseIf Cell.HasFormula Then
St = Range(" & Cell.Address & ").FormulaR1C1 = " _
& Chr(34) & Cell.Formula & Chr(34)
End If
If(Not alreadyAccounted(St) And storedCount <= 1000) Then
storedCount = storedCount + 1
stored(storedCount) = St
Print #1, St
End If
Next
Close 1
End Sub
Function alreadyAccounted(curString As String) As Boolean
Dim count As Integer: count = 0
Do While (count < storedCount)
count = count + 1
If (LCase(curString) = LCase(stored(count))) Then
alreadyAccounted = True
Exit Function
End If
Loop
End Function
If the expected number of records is much bigger, I would rely on file storage/checking. Relying on Excel (associating the inspected cells to a new range and looking for matches in it) would be easier but slower (mainly in case of having an important number of cells). Thus, a reliable and quick enough approach (although much slower than the aforementioned array) would be reading the file you are creating (a .txt file, I presume) from alreadyAccounted.