So I have a form that executes a VBA script via a macro. The purpose of said script is to open Excel, create a new workbook, gather information from several tables and export them to a formatted spreadsheet. Each person has a sheet with their name, and the relevant data is printed in said sheet. It works perfectly for the most part. Only one problem... The table in Access where the name and demographics data is gathered from is formatted to sort by last name ascending alphabetically. The VBA script exports it in the order the names were entered. I want my VBA script to respect the formatting in the database table, and I would prefer not to have to add an alphabetizing subroutine to my VBA script.
Table A Format: ID, Active, Last, First, Role, Traveler, Resident, Preceptee, Phone, Completion
Table B Format: ID, Course, Course ID, Offered, HLC, Course Type
Last in Table A called "Roster" is the field with which I want my VBA script to sort alphabetically. The database is already configured to do this.
Thanks in advance!
VBA Code:
Option Compare Database
' This module exports the database to a spreadsheet with specific formatting when called from a Macro
' Each Employee will have a sheet named thier last name which will contain all HLC modules they have completed in a list
' It is specific to this Database, but can be adapted to others.
' Version 1.0 Stable
Public Function ExportXLS(TblA As String, TblB As String, Optional names As String, Optional specific As Boolean)
'****************'
'Set up variables'
'****************'
Dim ctrA As Integer
Dim ctrB As Integer
Dim var As Long
Dim str As String
Dim excel As Object 'Pointer to Excel Application
Dim book As Object 'Pointer to Excel Workbook
Dim sheet As Object 'Pointer to Excell Sheet
Dim Roster As DAO.Recordset
Dim Course As DAO.Recordset
Dim Child As DAO.Recordset
Dim last_name As DAO.Recordset 'Matrix of pointers that will hold parts of the tables to be printed to the corresponding Excel sheets
Dim course_name As DAO.Recordset 'Matrix of pointers that will hold parts of the tables to be printed to the corresponding Excel sheets
'********************************************************'
'Initialize our tables into thier recordsets for analysis'
'********************************************************'
Set Roster = CurrentDb.OpenRecordset(TblA)
Set Course = CurrentDb.OpenRecordset(TblB)
str = "SELECT Last FROM Roster"
Set last_name = CurrentDb.OpenRecordset(str)
str = "SELECT Course FROM [Course List]"
Set course_name = CurrentDb.OpenRecordset(str)
'**************************************************************************'
'Create the new excel file with default parameters and print the cover page'
'**************************************************************************'
Set excel = CreateObject("Excel.Application")
Set book = excel.Workbooks.Add
excel.Visible = True
Set sheet = book.Worksheets("Sheet1")
str = "Coversheet"
sheet.Name = str
sheet.Range("B2") = "HLC Database Export tool V1.0"
sheet.Range("B3") = "Written by Levi T Jackson, RN, BSN"
sheet.Range("B4") = "All rights reserved, Copyright 2021"
sheet.Range("B5") = "For use only by Emory Healhtcare, and others with permissions"
'**********************************'
'Main Loop, where the magic happens'
'**********************************'
ctrA = 0
Roster.MoveFirst
last_name.MoveFirst
Do Until last_name.EOF 'Move through the list of last names in the table Roster, one at a time
If Roster!Active = True Then 'No need to report on inactive employees, use access query for that
Set Child = Roster!Completion.Value 'Open a Recordset for the multivalued field Completion in Roster
ctrB = 1
If Child.EOF = True Then 'save the number of records for printing, or set to 0
var = 0
Else
Child.MoveLast
var = Child.RecordCount
Child.MoveFirst
End If
Course.MoveLast
If Child.EOF = False Then 'Avoid errors by not processing a page if no completion records exist
Set sheet = book.sheets.Add(After:=book.Worksheets(book.Worksheets.count)) 'For active employees, make a new sheet and switch to it, and set its name to the current last name from Roster
sheet.Activate
sheet.Range("A1").SELECT
str = Roster!Last & ", " & Roster!First
sheet.Name = str
sheet.Range("B2") = "Courses Completed"
Do Until Child.EOF 'If there are records in Completion for the current name, print them, move on when done
Course.MoveFirst
course_name.MoveFirst
Do Until Course.EOF
If Course![Course ID] = CInt(Child!Value.Value) Then
sheet.Range("D" & Mid(coordinates(ctrB), 2, Len(coordinates(ctrB)) - 1)) = Course![Course ID] 'prints course ID next to the name
sheet.Range("D2") = "'" & CStr(var) & " / " & CStr(Course.RecordCount) 'Prints number of records in completions
sheet.Range("B3") = "Course Name"
sheet.Range("D3") = "Course ID"
sheet.Range(coordinates(ctrB)) = Course!Course 'Prints course name
ctrB = ctrB + 1
Course.MoveLast
Course.MoveNext
Else
Course.MoveNext
course_name.MoveNext
End If
Loop
Child.MoveNext
Loop
End If
ctrA = ctrA + 1 'I might use this later in code updates, counts how manmy records are processed
Child.Close
excel.ActiveSheet.Cells.SELECT 'Selects all of the cells
excel.ActiveSheet.Cells.EntireColumn.AutoFit 'Does the "autofit" for all columns
sheet.Range("A1").SELECT 'Selects the first cell to unselect all cells
End If
Roster.MoveNext
last_name.MoveNext
Loop
'Clean up recordsets
last_name.Close
course_name.Close
Roster.Close
Set Roster = Nothing
Course.Close
Set Course = Nothing
End Function
'Converts the iteration of the print course sub loop into a sheet coordinate cell and returns it as a string
'This function is here so that later a more complicated printing coordinate system can be easily added as the database grows larger
Private Function coordinates(num As Integer) As String
coordinates = "B" & CStr(num + 4)
End Function
Add an order by clause to your OpenRecordset statements.
Related
I haven't been able to find a question that is close enough to what i need or maybe i'm not as good as googling as I thought I was.
Anyway I have an issue where I need to use excel as a database as my place of work doesn't have access.
The way in which this is supposed to work is that in the 'ui' workbook after the user has inputed the data required it needs to take the users employee number from the 'ui' workbook and use that to find them in a list of all employees on the 'database' worksheet then increase a field that indicates that they have completed this specific task one more time. Then on a different sheet also on the second workbook it needs to record the date completed and also input the users id against this date.
so far I haven't attempted the code for the second part as I am having trouble with the first. Here is what i have so gotten to so far and it errors at the match function
Sub SendToDB()
Application.ScreenUpdating = False
Dim DB As Workbook
Dim UI As Workbook
Dim ENo As Long
Dim CopyFrom As Worksheet
Set CopyFrom = Worksheets("Checklist")
ENo = Range("G45").Value
Set UI = ThisWorkbook
Set DB = Workbooks.Open("file location here\database.xlsx", True)
Dim EmCount As Long
Dim EmRow As Long
Dim EmCell As Range
Application.ScreenUpdating = False
DB.Worksheets("EMLIST").Activate
EmRow = Application.WorksheetFunction.Match(ENo, Range("AML"), 0)
EmCell = Range("G" & EmRow)
EmCount = Application.WorksheetFunction.VLookup(EmENo, Range("AML"), 7, False)
EmCount = EmCount + 1
EmCell.Value = EmCount
DB.Close True
Set DB = Nothing
Application.ScreenUpdating = True
End Sub
AML is a named range on the database spreadsheet which contains all the employee data
Thanks
I am using the code from Function to export query or table to MS Excel to export all the data from one Access table to a worksheet in MS Excel.
This program stores time in and time out of employees in the table.
Let's say the admin wants to filter the data from 01 Jan 19 to 15 Jan 19.
I want to put two datepickers on my form as a basis for the "From" and "To".
I want to export that selected data. How can I inject that to this code?
Public Function Export2XL(InitRow As Long, DBAccess As String, DBTable As String) As Long
Dim cn As New ADODB.Connection 'Use for the connection string
Dim cmd As New ADODB.Command 'Use for the command for the DB
Dim rs2 As New ADODB.Recordset 'Recordset return from the DB
Dim MyIndex As Integer 'Used for Index
Dim MyRecordCount As Long 'Store the number of record on the table
Dim MyFieldCount As Integer 'Store the number of fields or column
Dim ApExcel As Object 'To open Excel
Dim MyCol As String
Dim Response As Integer
Set ApExcel = CreateObject("Excel.application") 'Creates an object
ApExcel.Visible = True 'This enable you to see the process in Excel
pExcel.Workbooks.Add 'Adds a new book.
ApExcel.ActiveSheet.Name = "" & (Export_data.Label1.Caption) & ""
'Set the connection string
cn.ConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0;data source=" &
app.Path & "\Dbase.mdb; User ID=admin;Persist Security Info=False;JET
OLEDB:Database Password=akgtrxx21"
'Open the connection
cn.Open
'Check that the connection is open
If cn.State = 0 Then cn.Open
Set cmd.ActiveConnection = cn
cmd.CommandText = DBTable
cmd.CommandType = adCmdTable
Set rs2 = cmd.Execute
'Count the number of fields or column
MyFieldCount = rs2.Fields.count
'Fill the first line with the name of the fields
For MyIndex = 0 To MyFieldCount - 1
ApExcel.Cells(InitRow, (MyIndex + 1)).Formula = rs2.Fields(MyIndex).Name
'Write Title to a Cell
ApExcel.Cells(InitRow, (MyIndex + 1)).Font.Bold = True
ApExcel.Cells(InitRow, (MyIndex + 1)).Interior.ColorIndex = 36
ApExcel.Cells(InitRow, (MyIndex + 1)).WrapText = True
Next
'Draw border on the title line
MyCol = Chr((64 + MyIndex)) & InitRow
ApExcel.Range("A" & InitRow & ":" & MyCol).Borders.Color = RGB(0, 0, 0)
MyRecordCount = 1 + InitRow
'Fill the excel book with the values from the database
Do While rs2.EOF = False
For MyIndex = 1 To MyFieldCount
ApExcel.Cells(MyRecordCount, MyIndex).Formula = rs2((MyIndex - 1)).Value
'Write Value to a Cell
ApExcel.Cells(MyRecordCount, MyIndex).WrapText = False 'Format the Cell
Next
MyRecordCount = MyRecordCount + 1
rs2.MoveNext
If MyRecordCount > 50 Then
Exit Do
End If
Loop
'Close the connection with the DB
rs2.Close
'Return the last position in the workbook
Export2XL = MyRecordCount
Set cn = Nothing
Set cmd = Nothing
Set rs2 = Nothing
Set ApExcel = Nothing
End Function
Excel does have a way to import data from Access with no VBA at all.
Create the connection to fill your worksheet.
Go to Menu Data > Access.
You will be asked to pick an Access database and select the table you want. You probably want a query to be executed but for now, pick any table; this will be edited later.
Edit the query to what you want.
Open the connection window by clicking on the menu Data > Connections and pick the connection you have just created. Then, go to the next tab (Definition), change Command Type from Table to SQL then in command text, type your command.
Don't close the window just yet.
Add condition on your date.
If the field is called, for instance, MyDate, then add a WHERE clause like this one: (MyDate >= ? AND MyDate <= ?).
When you refresh the data, you will be prompted to give values to replace the 2 question marks, and you will have the option to designate a cell to do it. You will also have an option for the query to always use what you have defined.
Note that when done correctly, you can reorder fields and/or create formulae in the table without causing any sort of problem to Excel at all. You can also create a Total row at the bottom to sum up values, using a formula (Excel will show you a dropdown to create a SUBTOTAL formula, that is conveniently sensitive to filters.
If you want to refresh data with VBA, it takes a single line of code to do: ThisWorkbook.Connections(...).Refresh or ApExcel.Workbooks(..).Connections(...).Refresh.
PS: If you absolutely want to keep your code above, then at least make sure not to copy rs2 cell by cell (that is way to slow due to Excel event handling) but rather, do something like: ApExcel.Cells(2, 1).CopyFromRecordset rs2
I have a button in Access (2003) that transfers data to Excel (also 2003). It opens the Excel workbook, then cycles through the Access subforms and transfers data.
To give more information on how this works, Excel has a range called "Tables" which contains the names of the Access subforms ("Main", "Demographics", "History", etc). Excel also has a range for each of the names in that first range. For example, the range "Demographics" contains a series of field names ("FirstName", "LastName", etc). So the first loop moves through the subforms, and the nested loop moves through the field names. Each field then passes the value in it over to excel. Excel also has ranges for "Demographics_Anchor" and "History_Anchor" etc, which is the first value in the column next to each range (ie the range Demographics has firstname, lastname, and to the right is where the data would go. So the first item in the range is FirstName, to the right "Demographics_Anchor" is where firstname will go. Then LastName goes to Demographics_Anchor offset by 1 - or 1 cell down from the anchor).
Dim ThisForm As Form
Dim CForm As Object
Dim CTab As TabControl
Dim CControl As Control
Dim CurrentTab As Variant
Dim CControlName As Variant
Dim CControlValue As String
Dim Code As Control
Dim counter1 As Integer
Dim appExcel As Object
Dim Anchor As Object
Dim PageRange As Object
Dim ControlNameRange As Object
strpath = "C:\blah\blah\filename.xlsm"
Set appExcel = CreateObject("Excel.Application")
appExcel.Workbooks.Open Filename:=strpath, UpdateLinks:=1, ReadOnly:=True
Set wbk = appExcel.ActiveWorkbook
Set PageRange = appExcel.Range("Tables")
'set Access environment
Set ThisForm = Forms("frmHome")
Set CTab = ThisForm.Controls("Subforms")
'export the data from Access Forms to Excel
For Each CurrentTab In PageRange
If CurrentTab = "Main" Then
Set CForm = ThisForm
Else
CTab.Pages(CurrentTab).SetFocus
Set CForm = ThisForm.Controls(CurrentTab & " Subform").Form
End If
Set ControlNameRange = appExcel.Range(CurrentTab)
Set Anchor = appExcel.Range(CurrentTab & "_Anchor")
counter1 = 0
For Each CControlName In ControlNameRange
Set CControl = CForm.Controls(CControlName)
CControl.SetFocus
Anchor.Offset(RowOffset:=counter1).Value = CControl.Value
counter1 = counter1 + 1
Next CControlName
Next CurrentTab
I hope this explains what is going on in the code. I just can't figure out why this keeps bombing out with type mistmatch (error 13).
The data DOES transfer. It goes through the entire code and every piece of data correctly gets transferred over. It bombs out at the end as if it goes through the code 1 last time when it shouldn't. I did confirm that every range is correct and doesn't contain any null values. The code bombs out on this line: Set CControl = CForm.Controls(CControlName) which is towards the bottom of the second loop.
Please help! I've spent weeks working with this code and had no luck. This exact code works in every other database I've worked with.
You are getting the name of the control CControlName from your Excel Range, but then setting the value of this control to the control on the Access form Set CControl = CForm.Controls(CControlName). From this, the most likely explanation is probably that the CControlName isn't actually on the Access form (perhaps a typo?).
In the VBA IDE, go under the Tools Menu, select Options and then select the General tab. Under the Error Trapping section, select the "Break on All Errors" option and click "OK" to set the preference. Run your code again; when an error is encountered VBA will stop processing on the line that caused the error. Check the value of CControlName and make sure it actually exists on the Access form.
I have to write some VBA code in excel to go with my VB.NET program and I am struggling with the basics of VBA as I have not used it before. I found some code which allowed me to get the values from my temporary table and put them directly in appropiate fields in the excel spreadsheet. I am populating a purchase order document, so information passed over are things like subtotal, vat, shipping, total, etc.
This is the code I have used to populate a single cell:
'get quantity
strQry = "SELECT quantity from [temp];"
Set rs = New ADODB.Recordset
With rs
Set .ActiveConnection = cn
.Open strQry
End With
'append data to document
Worksheets("PurchaseOrder").Range("D22").CopyFromRecordset rs
quantity = rs.Fields("quantity") 'setting the quatity in a variable
The last line of code is my attempt of storing the value of quantity in a variable, which I need to use to calculate the sub total as sub total was not passed over to excel. The code to populate the cells works fine, it is just putting the data in a variable to manipulate which I am struggling with. Populating the cells directly from the database works fine, but I am getting an error on that last line.
After storing quantity in a variable, I would also like to store the cost per unit in a variable, remove the £ sign at the start using a substring equivalent, convert it to a decimal then times the cost per unit by the quantity to get the sub total.
I tried using this following code:
'get price
strQry = "SELECT costPerUnit from [temp];"
Set rs = New ADODB.Recordset
With rs
Set .ActiveConnection = cn
.Open strQry
End With
'append data to document
Worksheets("PurchaseOrder").Range("N22").CopyFromRecordset rs
costPerUnit = Right(rs(0), Len(costPerUnit) - 1) 'setting the cost per unit in a variable
subtotal = costPerUnit * quantity
Worksheets("PurchaseOrder").Cells("Q47").Value = "£ " & subtotal
Any help is appreciated. Thankyou.
Ok. So until filling the of the Recordset you have done it correct and then you have to see that the Recordset is more like a table and can have none or multiple rows with multiple columns.
First you have to check if the query returned any result or if the Recordset is empty (rs.BOF and rs.EOF are true). Then you would loop through the rows (rs.MoveNext).
To access a single value you can either give the index of the column or the column name.
The following example loops through rows and through columns but also extracts again the column "quantity" at the end:
If (rs.EOF) And (rs.BOF) Then
Exit Function
Else
rs.MoveFirst
Do Until rs.EOF
For j = 1 To rs.Fields.Count
valueOfColumnJ = rs.Fields(j - 1).Value
Next j
quantity = rs.Fields("quantity")
rs.MoveNext
Loop
End If
Recordsets have a cursor and any reference to the recordset is going to return properties (like the value of a field) based on where that cursor is.
When you called CopyFromRecorset, you moved the cursor to the end (EOF = True). Then when you tried to get the Fields("quantity"), there was no active record of the recordset, so you got an error.
You could have first done rs.MoveFirst if you have the right type of recordset. Then quantity would have equaled the quantity field from the first record. Probably not what you want.
There is not a one-liner (as far as I know) that will get you the total of all the fields in your recordset. You have to loop like Graffl shows.
What might be the better path is to use Excel. You already have the data in there, so insert a formula to get the subtotal you want.
Worksheets("PurchaseOrder").Cells("Q47").Formula = _
"=SUMPRODUCT(P23:P46*Q23:A46)"
or something like that.
you can save Recordset to array
as shown below
Sub Check_gg_date_time_Unique(i As Integer)
Dim filmSet As New ADODB.Recordset
Dim dbConn As New ADODB.Connection
Dim filmName As String, lastrow As Integer
Dim Varray As Variant
dbConn.ConnectionString = "Provider=MSDASQL.1;Data Source=Excel_to_MySQL_Maintenance"
dbConn.Open
Set filmSet = dbConn.Execute("select count(wono) from maintennce_db.work_order where post_gg_date_time = '2022-08-30 14:21:37'")
Varray = filmSet.GetRows(1)
If Varray(0, 0) = 0 Then
'Do whatever you want
End If
End Sub
Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
Questions asking for code must demonstrate a minimal understanding of the problem being solved. Include attempted solutions, why they didn't work, and the expected results. See also: Stack Overflow question checklist
Closed 9 years ago.
Improve this question
I'm a QA. I'm not a developer, but I asked my boss for some busy work. She gave me a development project, and I'm completely confused. We have a spreadsheet with a long list of salespeople and their customers. For each customer, the salesperson, customer name, sales amount, and location are listed like so:
Salesperson Customer Name Sales Amount Location
salesperson1 customerA 3456789 Atlanta
salesperson1 customerB 9475903 Atlanta
salesperson1 customerC 7236433 Atlanta
salesperson1 customerD 9809489 Raleigh
salesperson2 customerA 3456789 Raleigh
salesperson2 customerB 9475903 Raleigh
salesperson2 customerC 7236433 Raleigh
salesperson2 customerD 9809489 Raleigh
salesperson2 customerE 3456789 Raleigh
salesperson3 customerA 9475903 Portland
salesperson3 customerB 7236433 Portland
salesperson3 customerC 9809489 Portland
salesperson4 customerA 9475903 Portland
salesperson4 customerB 7236433 Portland
salesperson4 customerC 9809489 Portland
There are hundreds of lines of salespeople and customers on this spreadsheet. My job is to create a new workbook template. I have successfully done that. The template takes relative sales data and does calculations based on sales numbers. Currently, I've been able to get all of the data from the source in to the template, and the calculations work. However, I need to go further. For each salesperson, I need there data loaded on to a separate sheet, and I need a separate template for each location. For example: notice that salesperson3 and salesperson 4 are in the Portland location. I need a separate sheet for salesperson3 and a separate sheet for salesperson4 on the same workbook which needs to be saved as PORTLAND.XLSM. There will also need to be a template called RALEIGH.XLSM and ALTANTA.XLSM.
So here are my questions:
How do I make an array to notice when I go from salesperson1 to salesperson2 to salesperson3 to salesperson4?
How do I do an array that will eliminate anyone not at that location? I presume I will need a worksheet for each location, and if the name of the worksheet doesn't match the location of that salesperson, that salesperson doesn't get a sheet in that workbook.
I haven't used VB since I was in college almost 2 decades ago. I know the logic, but I don't know the language and syntax.
As Brad mentioned this is going to be a beast to maintain. That being said, if I understand your question correctly, I'd use the .Range() function. If you have your data in a table you can pull an array and do what you're looking for with this function.
Public Sub createSheets()
Dim salespersons As Range, location As String, fileLocation As String, salesperson As String, newSheet As Worksheet
Set salespersons = Range("Table1[Salesperson]")
For i = 1 To salespersons.Count
location = salespersons(i).Offset(0, 3).Value
If i = 1 Or (salespersons(i).Offset(0, 3).Value <> salespersons(i - 1).Offset(0, 3).Value) Then
Call createBook(location)
End If
Next i
For Each wbook In Application.Workbooks
For i = 1 To salespersons.Count
If i = 1 Or (i > 1 And salespersons(i) <> salespersons(i - 1)) Then
salesperson = salespersons(i).Value
location = salespersons(i).Offset(0, 3).Value
If location & ".xlsx" = wbook.Name Then
If i = 1 Or (salespersons(i).Value <> salespersons(i - 1).Value) Then
On Error Resume Next
Set newSheet = wbook.Worksheets(salesperson)
If Err.Number <> 0 Then
Set newSheet = wbook.Worksheets.Add
newSheet.Name = salesperson
End If
End If
End If
End If
Next i
Next
End Sub
Public Sub createBook(location As String)
Dim newBook As Workbook, newSheet As Worksheet, result1 As Boolean, result2 As Boolean
result1 = isWorkbookOpen(location & ".xlsx")
If result1 = False Then
On Error Resume Next
Workbooks(location).Save
If Err.Number <> 0 Then
On Error Resume Next
Workbooks.Open (location & ".xlsx")
If Err.Number <> 0 Then
Workbooks.Add.SaveAs (location & ".xlsx")
Error Err.Number
End If
End If
End If
End Sub
Function isWorkbookOpen(location As String)
Dim ff As Long, Errno As Long
On Error Resume Next
ff = FreeFile()
Open Filename For Input Lock Read As #ff
Close ff
Errno = Err
On Error GoTo 0
Select Case Errno
Case 0:
isWorkbookOpen = False
Case 70:
isWorkbookOpen = True
Case 75:
isWorkbookOpen = False
Case Else: Error Errno
End Select
End Function
Here's an option using pivot tables that requires NO VBA. If you use this, maintenance takes a little more work, but it's very simple to set up and hand off:
From your source, make a pivot-table and add it to a new document.
For your Report Filter, add the SalesPerson and Location. Leave the actual dropdowns at (All) for now or just pick values at random.
For your Row label, you can add the Customer.
For your Values, you can sum your sales amounts.
Make the tab as pretty as you like. This will be your template for all the sales people. The Options ribbon lets you add custom calcuations, % of row totals, etc. Go wild. You now have a tab which can serve as a template for every salesperson!
Make a blank workbook for each location. For each location's workbook, add this template tab for each salesperson that works there. In that tab's pivot table's filters, pick the location and a salesperson.
Repeat #6 until each workbook has a tab for each salesperson and each of those tabs has the proper salesperson and location selected.
Yes, that was monotonous, but now you're done with the hard part! Every salesperson has their own tab and every location has their own file. Thanks to the report filters in the many, many tabs you've just made, each tab is filtered to just the sales in that location for that salesperson.
Updating the data:
The biggest drawback to this option is that everytime a SalesPerson starts selling at a new location, or a new SalesPerson is hired, you'll have to add a new tab. But thanks to your common template tab, this isn't very painful.
For regular updates, just open each file and go to the Data ribbon and Refresh all.
This will loop over a source sheet (the active sheet) and generate a number of workbooks (one per location). A sheet containing customer name and sales amount for each sales person is added to the workbook.
once the workbooks are generated it saves them as their location's name.
' get a named worksheet from specified workbook, creating it if required
Public Function GetSheet(ByVal Name As String, ByVal Book As Workbook, Optional ByVal Ignore As Boolean = False) As Worksheet
Dim Sheet As Worksheet
Dim Key As String
Dim Result As Worksheet: Set Result = Nothing
Key = UCase(Name)
' loop over all the worksheets
For Each Sheet In Book.Worksheets
' break out of the loop if the sheet is found
If UCase(Sheet.Name) = Key Then
Set Result = Sheet
Exit For
End If
Next Sheet
' if the sheet isn't found..
If Result Is Nothing Then
If Ignore = False Then
If Not GetSheet("Sheet1", Book, True) Is Nothing Then
' rename sheet1
Set Result = Book.Worksheets("Sheet1")
Result.Name = Name
End If
Else
' create a new sheet
Set Result = Book.Worksheets.Add
Result.Name = Name
End If
End If
Set GetSheet = Result
End Function
Sub Main()
Dim Source As Worksheet
Dim Location As Workbook
Dim Sales As Worksheet
Dim LocationKey As String
Dim SalesKey As String
Dim Index As Variant
Dim Map As Object: Set Map = CreateObject("Scripting.Dictionary")
Dim Row As Long
Set Source = ThisWorkbook.ActiveSheet
Row = 2 ' Skip header row
Do
' break out of the loop - assumes that the first empty row signifies the end
If Source.Cells(Row, 1).Value2 = "" Then
Exit Do
End If
LocationKey = Source.Cells(Row, 4).Value2
' look at the location, and find the workbook, creating it if required
If Map.Exists(LocationKey) Then
Set Location = Map(LocationKey)
Else
Set Location = Application.Workbooks.Add(xlWBATWorksheet)
Map.Add LocationKey, Location
End If
SalesKey = Source.Cells(Row, 1).Value2
' get the sheet for the salesperson
Set Sales = GetSheet(SalesKey, Location)
' insert a blank row at row 1
Sales.Rows(1).Insert xlShiftDown
' populate said row with the data from the source
Sales.Cells(1, 1).Value2 = Source.Cells(Row, 2)
Sales.Cells(1, 2).Value2 = Source.Cells(Row, 3)
'increment the loop
Row = Row + 1
Loop
' loop over the resulting workbooks and save them - using the location name as file name
For Each Index In Map.Keys
Set Location = Map(Index)
Location.SaveAs Filename:=Index
Next Index
End Sub
Update
This code is only really helpful if you are generating read-only reports, that get replaced every time you generate the report. (this code will probably fail to save on subsequent runs as it does not check to see if a file exists when trying to save, you'll need to delete any previous output before running it again).
This code (in its current form) would need to be added into the workbook with your source data, and the data sheet would need to be active before you run the Main() macro.
If you are trying to do more than just generate a report (like replacing the source data sheet with your new files) it would be much simpler in Access as a few people have already suggested.