We have a macro that loops through a set of 50 workbooks that have different amounts of sheets. The sheets look similar but all have different sheet names.
We want to place a formula in the first sheet ("Framsida") that searches through column B in sheet 3 to the last sheet to identify how many unique entries there are.
We have been working with PRODUCTSUM and FREQUENCY.
The formula works when pasted into the sheet manually.
When trying this with the macro, it starts linking to other data sources with the error message
"This workbook contains links to other data sources".
The code we tried:
Worksheets("Framsida").Range("m5").Formula = "=SUMPRODUCT(--(FREQUENCY(''" & Sheets(3).Name & " : " & Sheets(Sheets.Count).Name & " '!$B$6:$b$200, ' " & ActiveWorkbook.Sheets(3).Name & " : " & ActiveWorkbook.Sheets(Sheets.Count).Name & " '!$B$6:$b$200)<>0))"
This is the result that comes out in sheet "Framsida" when running the macro:
=PRODUKTSUMMA(--(FREKVENS('8007029 :[ 8007062 ] 8007062 '!$B$6:$B$200; '8007029 :[ 8007062 ] 8007062 '!$B$6:$B$200)<>0))
Where PRODUKTSUMMA=PRODUCTSUM
and FREKVENS=FREQUENCY
It adds the last sheet name in square brackets and we have no idea why. We are open for suggestions to other solutions.
This is the entire loop:
Sub SummeringFramsida()
'Variabler för loopen
Dim MyFile As String
Dim Filepath As String
'-----------------------------------------------------------------------------------'
'Öppnar filer tills det att man kommer till Huvudfilen i listan, filerna som ska sökas måste alltså ligga ovanför i listan'
Filepath = "C:\Users\JohannaFalkenstrand\Desktop\Excelfix\"
MyFile = Dir(Filepath)
Do While Len(MyFile) > 0
If MyFile = "Huvudfil.xlsm" Then
Exit Sub
End If
Workbooks.Open (Filepath & MyFile)
Workbooks(MyFile).Activate
Worksheets("Framsida").Range("m5").Formula = "=SUMPRODUCT(--(FREQUENCY('" & Sheets(3).Name & " : " & Sheets(Sheets.Count).Name & " '!$B$6:$b$200, ' " & ActiveWorkbook.Sheets(3).Name & " : " & ActiveWorkbook.Sheets(Sheets.Count).Name & " '!$B$6:$b$200)<>0))"
'Stänger, sparar och går till nästa fil'
Workbooks(MyFile).Save
Workbooks(MyFile).Close
MyFile = Dir
Loop
End Sub
Give it a try with Range(...).Address(1,1,xlA1,1). This will give you a string of range reference that contains both the workbook and the sheet reference. Then you can compile the required formula with simple string manipulation, like
For Each wb in <SomeCollectionOfWorkbooks>
For Each sh in wb.Sheets
Debug.Print "Copy this to the required cell = SUM(" & _
sh.Range("B6:B200").Address(1,1,xlA1,1) & ")"
Next
Next
The key is the external reference parameter of .Address.
It looks like you want to use same range but on different sheets at same time, so you need to check this out:
Create a reference to the same cell range on multiple
worksheets
Applying this to your code this should kind of work:
Worksheets("Framsida").Range("m5").Formula = "=SUMPRODUCT(--(FREQUENCY(" & Sheets(3).Name & ":" & Sheets(Sheets.Count).Name & "!$B$6:$b$200," & ActiveWorkbook.Sheets(3).Name & ":" & ActiveWorkbook.Sheets(Sheets.Count).Name & "!$B$6:$b$200)<>0))"
Related
I am trying to write a code that takes a variable workbook name (declared earlier) and inputs it into a formula that will be pasted into a cell.
I have it to the point where the formula works if I just write the workbook in, and I have the variable declaration with the workbook path working okay, I just can't figure out the formatting to drop the variable into the formula code.
'''Spreadsheet opening and variable declaration
strFileToOpen = Application.GetOpenFilename _
(Title:="Select an updated Inventory Report", _
FileFilter:="Excel Files *.xls* (*.xls*),")
If strFileToOpen = False Then
'Displaying a message if file not choosedn in the above step
MsgBox "No file selected.", vbExclamation, "Sorry!"
'And existing from the procedure
Exit Sub
Else
End If
Workbooks.Open Filename:=strFileToOpen
Set InvRpt = ActiveWorkbook
InvRptName = ActiveWorkbook.FullName
Set InvSht = InvRpt.Worksheets("ALL")
'''Formula insert
ActiveCell.Formula = "=IF(ISERROR(GETPIVOTDATA(""Sec QTY Sum"", 'InvRptName'!$A$4,""Alias"",""" & y & """)),0,GETPIVOTDATA(""Sec QTY Sum"", 'InvRptName'!$A$4,""Alias"",""" & y & """))"
The end result would be for this code to work exactly like this, but with a variable instead of the workbook/sheet name:
ActiveCell.Formula = "=IF(ISERROR(GETPIVOTDATA(""Sec QTY Sum"", '[8-14-19 AM INVENTORY.xls]Sheet2'!$A$4,""Alias"",""" & y & """)),0,GETPIVOTDATA(""Sec QTY Sum"", '[8-14-19 AM INVENTORY.xls]Sheet2'!$A$4,""Alias"",""" & y & """))"
It always helps to place a Break Point into the line where you are having issues then use the Intermediate Window to figure out where it is going wrong. For example;
Place a Break Point at the last line where you have ActiveCell.Formula =
Run the code
In the Intermediate Window type ?"=IF(ISERROR(GETPIVOTDATA(""Sec QTY Sum"", 'InvRptName'!$A$4,""Alias"",""" & y & """)),0,GETPIVOTDATA(""Sec QTY Sum"", 'InvRptName'!$A$4,""Alias"",""" & y & """))"
Note; The ? instructs the Intermediate Window that you want to see a return value.
If it compiles ok you should see a resulting string, see if it looks as you expected. I'd guess your result will include the text 'InvRptName' where you are expecting '[8-14-19 AM INVENTORY.xls]Sheet2'.
To get '[8-14-19 AM INVENTORY.xls]Sheet2'!$A$4 try using;
?"'[" & ActiveWorkbook.Name & "]" & ActiveSheet.Name & "'!$A$4"
Another couple hot tips;
Use Debug.Print "add your messages here or a variable " & InvRptName within your code to output debug messages.
Use the Intermediate Widow to fix variables during execution InvRptName = "'[" & ActiveWorkbook.Name & "]" & ActiveSheet.Name & "'!$A$4" notice I excluded the ? this time.
So I have a currently working VBA script. I just want to edit the name by which the loop saves files.
I'll give a quick description of what it is currently doing. Basically it loops a set of actions for a defined range of rows, here A2:A72. The is a 'main' workbook where this loop is done is where all the input data is collected. Each row is a separate subject's input data and is copy/pasted into a template in a different workbook. Solver is then run to adjust the template for the given input data. Then it saves and names file as the text in the first cell of the row that was copy pasted. (ie A2,A3,A4,etc..) It then loops this for every row and every row will have its own template set up and saved separately.
This is ALMOST how I ideally want it to work.
I just want it save the File name not just as A2, but as =C2&" - "&A2
I tried using this that was suggested by someone
fName = Range("C" & c.Row) & Range("A" & c.Row)
But when I tried I would get a Method SaveAs error. On the watch view I could see it was because it wasn't reading the fName so it was just the file path in the script value. I changed it back to c.Value and then it started working by naming the file as the A column cell. Admittedly, I don't really understand how c.Value is returning column A which makes it harder for me to figure out how to modify it to get what I want.
Anyway here is the script as I currently have it:
Sub RunModels()
Dim fPath As String
Dim strTemplate As String
Dim fName As String
Dim wb As Workbook
Dim c As Range
Dim rngLoop As Range
'Where will files get stored?
fPath = "H:\ACQUISITIONS\Personal (D-AP)\Gmo\ALL MF"
'Where is the template file?
strTemplate = "H:\ACQUISITIONS\Personal (D-AP)\Gmo\ALL MF\Garden Grove - 11121 Chapman Ave.xlsm"
'Error check
If Right(fPath, 1) <Application.PathSeparator Then
fPath = fPath & Application.PathSeparator
End If
Application.ScreenUpdating = False
'Set Loop
Set rngLoop = ThisWorkbook.Worksheets("Sheet1").Range("A2:A72")
'Set Looped Actions
For Each c In rngLoop.Cells
'Open the template file
Set wb = Workbooks.Open(strTemplate)
'Add some data to the template file
c.EntireRow.Copy Destination:=wb.Worksheets("Insert
Sheet").Range("A2")
SolverOk SetCell:="$H$20", MaxMinVal:=3, ValueOf:=1.2, ByChange:="$F$35", Engine:=1, EngineDesc:="GRG Nonlinear"
SolverSolve
'Dynamic File Naming
fName = c.Value
'Save the file and close
wb.SaveAs Filename:=wb.Path & Application.PathSeparator & fName
wb.Close
Next c
Application.ScreenUpdating = True
End Sub
Thank you very very much for all and any help!!
You should not use wb.Path wb is assigned to the workbook, where fPath is your folder path, so use:
Filename:=fPath & "\" & fName & ".xlsx"
or ".xlsm" as required.
To assign fName use:
fName = c.Offset(, 2).Value & " - " & c.Value
The code assigns the variable c to each cell in the range A2:A72 in turn - so at the moment it is saving the code 71 times. The code
fName = Range("C" & c.Row) & Range("A" & c.Row)
would produce c2 & A2 on the first time through, and then C3 & A3 on the second (and so on. I suspect you'd like it to always use c2 and then add the value in A - in which case you'd need
Fname = Range("C2") & "- " & c
I try to do a Vlookup in a closed workbook via ExecuteExcel4Macro. Since I need to find a lot of values in different workbooks it makes sense to do it in a closed workbook.
I used the below code, but it doesn't work if I enter the function on my Excel book:
=GetVlookup("D:", "testfile.xls", "Sheet1", "C1:D8", 2, "CDM-01_10")
I can assure that there is a file on my D: named testfile.xls with a sheet1 and content in the range I mention here. But result is #value!.
I want this in a function since I want to make a price calculation add-in that can be used in several workbooks, so I rather haven't introduced this in a macro.
Public Function GetVlookup(path, file, sheet, ref, Col, vVal)
' Retrieves a value from a closed workbook
Dim arg As String
' Make sure the file exists
If Right(path, 1) <> "\" Then path = path & "\"
If Dir(path & file) = "" Then
GetVlookup = "File Not Found"
Exit Function
End If
If IsNumeric(vVal) Then
vVal = CDbl(vVal)
Else
vVal = Chr(34) & vVal & Chr(34)
End If
' Create the argument
arg = "'" & path & "[" & file & "]" & sheet & "'!" & Range(ref).Address(, , xlR1C1)
' Execute an XLM macro
GetVlookup = ExecuteExcel4Macro("Vlookup(" & vVal & "," & arg & "," & Col & ",0)")
End Function
Ok , here is the thing,
I have created a next invoice number program in which by pressing of a macro assigned button active invoice automatically saved and closed and the next invoice with a number increased appear.My problem is that, I want excel invoices to be created in their relevant folder by their first two digits of invoice number . as an example : 04-001 where 04 stands for April. also, when invoice number is given 05-002, the directory folder of may 2018-19 should be auto created and invoice should be there in the folder only. i am trying to figure out the code since some time but no luck till now. So far , The invoices are created according to date only but as darren said it is a problem for me when i am trying to create invoices from december on first day of january.
This is my current code :
Sub SaveInvoiceM()
Dim NewFN As Variant
If Len(Dir("C:\Invoices\" & Format(Date, "MMM YYYY") & "-" & (Format(Date, "YY") + 1), vbDirectory)) = 0 Then
MkDir "C:\Invoices\" & Format(Date, "MMM YYYY") & "-" & (Format(Date, "YY") + 1)
End If
' Copy Invoice to a New Workbook
NewFN = "C:\Invoices\" & Format(Date, "MMM YYYY") & "-" & (Format(Date, "YY") + 1) & "\Inv" & Range("F5") & ".xlsx"
ActiveWorkbook.saveas NewFN, FileFormat:=xlOpenXMLWorkbook
NextInvoice
End Sub
Range("F5") stands for my invoice number which is 04-001
I see what you are trying to do (keep nicely organized, automatically) and that's an excellent goal.
I have a suggestion of an alternate invoice numbering system (based on what I'm understanding of your situation & experience level) that will make tasks (like this "auto-filing" process) much easier, and will also simplify the process any time you (or especially anyone else) needs to look back at these invoices. There are a number of obvious benefits (same idea as metric vs imperial).
Ideal numbering system: (in my opinion)
To reduce confusion: Give each invoice and filename the same name instead of having a filename with a month and
Since you want granularity from months to years (but not days): make the invoice/file name include the all of those fields.
To make sorting & finding these logical (easier): place each "date part" in order of biggest to smallest. A unique sequential number goes at the very end.
Your code sample was a good start - I just have a bit of OCD when it comes to this kind of thing, and creation of a numbering system is an important task. (Also this will be "date-proof", and error-checked along the way...
This is a little different than what you had because instead of you telling the code what the next invoice number is, it tells you (by figuring out the next number in sequence based on the existing files).
Like yours, it creates a folder if necessary. Since the files are number YYMM-nnn then are always in the correct order when you sort them. (The "month folders" are unnecessary since the month is in the filename, but I included them anyway since that was your plan. You could just keep every month's invoices in one folder, and they'd still be organized in order of month.)
VBA #1: Save file with next sequential invoice number (creating folder if necessary)
Sub createInvoiceNumberAndSave()
'creates a new invoice number based on date in specified cell & creates new folder if necessary
'finds next unused invoice number & verifies that file is properly saved
Const invoicePath = "c:\invoices\" ' invoice root save path
Const fNamePrefix = "Inv" ' prefix for the filename
Const fNameExt = ".xlsm" ' file extension
Const getInvoiceDate = "F5" ' we GET the DATE of the invoice from F5
Const putInvoiceNumber = "F6" ' we will PUT the new filename into cell F6
Dim invDate As Date, folderName As String, fName As String, fNum As Long, nextInvoiceNum As Long
'get the invoice date and make sure it's valid
If IsDate(Range(getInvoiceDate).Value) Then
'valid date found in cell F5
invDate = Range(getInvoiceDate).Value
Else
'valid date not found in F5. Do we want to default to today's date?
If MsgBox("Cell " & getInvoiceDate & " does not contain a valid date." & vbLf & vbLf & _
"Do you want to use today's date instead?", vbQuestion + vbOKCancel, "Date not found") <> vbOK Then
Call MsgBox("Invoice Not Saved.", vbCritical + vbononly, "User Cancelled")
Exit Sub 'stop running
Else
invDate = Date 'use today's date
End If
End If
'find the next unused invoice number for this month
folderName = Format(invDate, "YYMM")
nextInvoiceNum = 0
'figure out the next unused "file number"
fName = Dir(invoicePath & folderName & "\" & fNamePrefix & folderName & "-*" & fNameExt)
If fName = "" Then
'file not found
If Dir(invoicePath & folderName, vbDirectory) = "" Then
'month not found - create folder?
If MsgBox("Okay to create folder '" & invoicePath & folderName & "' for invoice #" & folderName & "-001 ?", _
vbOKCancel + vbQuestion, "Folder not Found") <> vbOK Then Exit Sub
'create folder
MkDir (invoicePath & folderName)
End If
Else
'month found. Now find the highest invoice number in the folder.
Do While fName <> ""
Debug.Print "Found File: " & fName
'get the number (filename = fNamePrefix & "YYMM-___.xlsx" so we know where it is
If IsNumeric(Mid(fName, 6 + Len(fNamePrefix), 3)) Then 'it's a valid number
fNum = Val(Mid(fName, 6 + Len(fNamePrefix), 3))
'if it's the biggest so far, remember it
If fNum > nextInvoiceNum Then nextInvoiceNum = fNum 'biggest one so far
End If
fName = Dir
Loop
End If
'we have the next available invoice#
nextInvoiceNum = nextInvoiceNum + 1 'new invoice# (numeric)
'PUT the new invoice# (text) in cell F6
Range(putInvoiceNumber).Value = fNamePrefix & folderName & "-" & Format(nextInvoiceNum, "000")
fName = invoicePath & folderName & "\" & Range(putInvoiceNumber).Value & fNameExt
Debug.Print "Saving as: " & fName
'save file
ActiveWorkbook.SaveAs fName
'DOUBLE CHECK check that file exists (couple lines of code now save a headache later)
If Dir(fName) = "" Then
'something went wrong (file wasn't saved)
Call MsgBox("ERROR! FILE NOT SAVED: " & fName, vbCritical + vbOKOnly, "ERROR!")
Stop
End If
'success message!
Call MsgBox("Invoice saved successfully:" & vbLf & vbLf & fName, vbInformation, "Invoice Created")
'NextInvoice '?
End Sub
EDIT: ("Back to your way")
I can think of a number of ways that your method will be a problem, some of which I tried explaining, but you're determined to number & organize these files your way, so "here you go".
VBA #2: Save file with cell value as name:
This procedure saves the current file, named from the invoice number (like 04-001) that you enter in cell F5 (creating folder if necessary):
Sub SaveFileBasedOnInvoiceNumber()
Dim monthNum As Long, yearString As String, folderName As String, fName As String
'build filename
On Error Resume Next 'skip errors for now
monthNum = Val(Left(Range("F5"), 2))
yearString = Year(Date) & "-" & Right(Year(Date) + 1, 2)
folderName = "c:\invoices\" & StrConv(monthName(monthNum, True), vbUpperCase) & " " & yearString
fName = folderName & "\INV" & Range("F5") & ".xlsm"
'check if there was a problem
If Err Then MsgBox "Invalid invoice number": Exit Sub
MkDir (folderName) 'create folder
On Error GoTo 0 'turn error checking back on
'Confirm file saved properly
ActiveWorkbook.SaveAs fName 'save file
If Dir(fName) = "" Then MsgBox "Error! File not saved: " & fName: Exit Sub
MsgBox "Invoice saved successfully:" & vbLf & fName
End Sub
I'll leave "VBA #1" in the the top of the answer for others seeking a logical numbering & storage system with auto-generated invoice numbers.
(One day you'll figure out why that way would've been better, but be forewarned, it will be a lot more of a hassle to change your organization method later!)
Good luck!
I have the following function which I use to fetch data from a closed workbook:
Public Function GetValue(path, file, sheet, ref)
' Retrieves a value from a closed workbook
Dim arg As String
' Make sure the file exists
If Right(path, 1) <> "\" Then path = path & "\"
If Dir(path & file) = "" Then
GetValue = "File Not Found"
Exit Function
End If
' Create the argument
arg = "'" & path & "[" & file & "]" & sheet & "'!" & _
Range(ref).Range("A1").Address(, , xlR1C1)
' Execute an XLM macro
GetValue = ExecuteExcel4Macro(arg)
End Function
Then I have the following test routine which works:
Sub TestGetValue()
p = Range("B2").Value
f = Range("B3").Value
s = "TOTAL"
a = "D" & ActiveCell.Row + 3
MsgBox GetValue(p, f, s, a)
End Sub
However, if I use the GetValue function in an Excel cell providing all 4 parameters exactly like in the routine, it always throws a #VALUE! error.
Why does it work in a routine and not while being called as a function?
After several attempts I did find 3 solutions:
Using a function with "ExecuteExcel4Macro"
Opening the other workbook in the background by vba procedure, then copy/pasting data
Creating external references by vba procedure, then leaving only values
First one while being able to use it as an excel custom formula, it is the slowest, especially when you need data from a specific range. So I don't recommend this option as it would slow your workbook down a lot.
Second option while being much faster, still takes around 15 seconds to get data from several ranges across 2 workbooks.
Third option was the fastest. Like an instant really less than 1 second execution time during which data was pulled from 2 closed workbooks and for different ranges.
Here's the 3rd option's procedure:
Sub getDS()
asname = "data"
bsname = "Anual"
csname = "Total"
filename = Sheets("data").Range("B64").Value
path = Sheets("data").Range("B63").Value
Dim ws As Worksheet
Set ws = Sheets("data")
With ws.Range("D4:D34")
.ClearContents
.Formula = "='" & path & "[" & filename & "]" & bsname & "'!R11"
.Value = .Value
End With
With ws.Range("J4:J34")
.ClearContents
.Formula = "='" & path & "[" & filename & "]" & bsname & "'!U11"
.Value = .Value
End With
With ws.Range("J37:J37")
.ClearContents
.Formula = "='" & path & "[" & filename & "]" & csname & "'!DW96"
.Value = .Value
End With
End Sub
I found this by far to be the BEST and FASTEST VBA solution to fetch data in an INSTANT from closed workbooks (with variables) without opening them.