I'm using Excel and Access 365 for the record.
I have information on around 100,000 account numbers, far too many for Excel to handle efficiently. I put them into Access.
In Excel, I have a list of about 10 account numbers. This list changes daily. How do I get the account information from Access into Excel? If I was able to keep everything in Excel I would use INDEX MATCH, what is the equivalent to get information from Access?
I would suggest setting up a linked table to Excel within Access, and running an SQL statement. Much simpler than loops in VBA.
Open Access
Create a linked table to the Excel worksheet, which is nothing more than connection information to the worksheet; it doesn't actually store the records from the worksheet.
This allows the following:
From within Access -- run queries that join data between Access tables and the linked Excel table. You can save such queries, use them as RecordSource for a form or report etc.
From within Excel -- you can open an ADO connection from within Excel and run an SQL statement joining Access tables and the linked Excel worksheet. You can then use the Excel Range.CopyFromRecordset method to paste those results into an Excel worksheet.
It sounds like you need to use the 'In' clause. I have the following data points on Sheet2 in Range A1:A5.
Ryan
Sam
Timmy
Tommy
Teddy
Paste the code below into a Module and set a reference to 'Microsoft Active X Data Objects 2.8 Library' under Tools in the VBE Window.
Sub Import()
Dim connect As ADODB.Connection
Dim rec1 As ADODB.Recordset
Dim wb As Worksheet
Dim Wb2 As Worksheet
Dim Param() As ADODB.Parameter
Dim Command1 As ADODB.Command
Dim lrow As Integer
Dim i As Integer
Dim ConcatSQL As String
Set wb = ActiveWorkbook.Sheets("Sheet1")
Set Wb2 = ActiveWorkbook.Sheets("Sheet2")
lrow = Wb2.Range("A" & Wb2.Rows.Count).End(xlUp).Row
'Concatenate desired range into one cell
For i = 0 To lrow
ConcatSQL = ConcatSQL & "'" & Wb2.Cells(i + 1, 1) & "'" & ","
Next i
ConcatSQL = "(" & Left(ConcatSQL, Len(ConcatSQL) - 1) & ")"
'Open Command Object with One Paramter
Set Command1 = New ADODB.Command
With Command1
.CommandText = " Select ID, Price from TABLE where ID IN " & ConcatSQL
.CommandType = adCmdText
.CommandTimeout = 600
End With
'Connect to Data Source
Set connect = GetNewConnection 'Represents Private Function with Connection String
Command1.ActiveConnection = connect
Set rec1 = New ADODB.Recordset
Set rec1 = Command1.Execute()
'Paste Results
wb.Activate
With wb.QueryTables.Add(Connection:=rec1, Destination:=wb.Range("A1"))
.Name = "data"
.FieldNames = True
.Refresh BackgroundQuery:=False
End With
'Close Connections
rec1.Close
connect.Close
Set rec1 = Nothing
Set connect = Nothing
End Sub
Here is a screen shot to show how the variables are created.
I'm 100% certain that you can run a simple query in Access and export the results of said query to Excel. Or, save that query, and import the records in the object to Excel. When you get into larger data sets like you described, you may want to consider using different tools for the job. Python and R come to mind.
This will probably require VBA to do efficiently.
Loop through the account numbers, and for each account number query the Access database (using ADO) and return only the required data for each account.
Related
Hi I currently have two worksheets in an excel file with one of them acting as a database of all the products we sell, with the columns Product ID, Product Code, and Description (sample below).
I have another worksheet that acts as a product finder tool, where you would paste multiple Product IDs in the first column and it would return the Product code and Description in the adjacent columns (image below).
I currently use an INDEX search to make this happen, but the database sheet has become too big to manage in the same file, leading to severe slow downs. What would be the easiest solution for this? I was thinking of separating the database sheet as an Excel or AccessDB file but I think I will need a lot of VBA manipulation if I do that. Any help would be much appreciated.
You can access your data in Microsoft Access using ADO and doing a SQL query to gather data.
Could you tell me if it's possible to give a cell range to the WHERE clause?
Yes, there is a trick. SQL commands are plain text, you just need to build it with your parameters. Use the operator IN in the WHERE clause.
I made a fake dataset as example. Here's my Excel Product Finder (a table named Table1):
Notice I want the info only of products 6,3 and 2. Now my fake database:
The code to query those specific products:
Sub TEST()
Dim cnn As Object
Dim RST As Object
Dim DatabasePath As String
Dim i As Long
Dim Allid As String
Dim Arrayid As Variant
Dim SQLQuery As String
DatabasePath = "C:\Temp\temp.accdb" 'path to database
'Create a connection object.
Set cnn = CreateObject("ADODB.Connection")
'Create recordset object
Set RST = CreateObject("ADODB.Recordset")
'Open a connection using the OLE DB connection string.
cnn.Open "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & DatabasePath & ";Persist Security Info=False;"
'merge all ID into one single string
Arrayid = Range("Table1[PRODUCT ID]").Value
For i = LBound(Arrayid) To UBound(Arrayid) Step 1
Allid = Allid & Arrayid(i, 1) & ","
Next i
Allid = Left(Allid, Len(Allid) - 1) 'get rid of last comma
Erase Arrayid 'clean array variable
'specify query
SQLQuery = "SELECT PRODUCT_TABLE.[Product Id], PRODUCT_TABLE.[Product Code], PRODUCT_TABLE.Description FROM PRODUCT_TABLE " & _
"WHERE PRODUCT_TABLE.[Product Id] In (" & Allid & ") ORDER BY PRODUCT_TABLE.[Product Id]"
'Open a recordset using the Open method
'and use the connection established by the Connection object.
RST.Open SQLQuery, cnn
'copy all data into cells. This will bring full query without headers
Range("A6").CopyFromRecordset RST
'close and clean variables
RST.Close
cnn.Close
Set RST = Nothing
Set cnn = Nothing
End Sub
After executing code I get this:
NOTICE that the output is not sorted as we had before. We asked the products in order 6,3,2 but the output is 2,3,6!
This is because my SQL query got the operator ORDER BY that sorts by ID field. If there is no ORDER BY clause the output will be sorted as it is in the database stored, not as your Excel.
If you really really really need the output to be exactly in the same order that your Product Finder, you can create an UDF function to query each single id once and return a single row for each product but if you work with a lot of data this can consume a lot of time. So think carefully how to approach this part.
By the way, make sure you use the right connection string. You can find many on Access connection strings
I'm trying to determine the most efficient/effective way to display specific rows from a central table on sheet1 as text on sheet2.
I have setup a table that contains numerous events that is continually being used by multiple people. This sheet acts as a central database and is shared with multiple people who are all using it in real time.
I want to provide a table on sheet2 that allows other users to view specific events from the central database. So I need to export specific values from sheet1 to sheet2. I understand this can easily be done using a filter but the table on sheet1 is constantly being used and it can't be disrupted.
I can't just do a sort or filter on the sheet1 table because it needs to be used by other parties at all times
I really only need to view specific values from sheet1 for the last month. I've got code the exports all rows based off a specific value entered into a designated column on Sheet1. But due to the size of the file Excel constantly crashes.
I then thought a pivot table may be easier and I wouldn't have to use VBA. Is it possible to pivot out specific rows as text, which can be grouped by date, e.g. month?
For instance, if I want to view all ['A's'] and ['X's'] from Column B from the last month as full text it would look like the following:
Central Database table Sheet1
A B C D
0 11/1 A Big Dog
1 10/1 X 1 2
2 11/1 Y Y Y
3 1/2 A Big Cat
4 1/2 X 3 4
5 1/2 Y Y Y
Output table Sheet2
A B C D
1 1/2 A Big Cat
2 1/2 X 3 4
As others have mentioned in the comments, using SQL with ADODB is likely a better approach than using PivotTables. I'd also recommend separating your data (Sheet1) from your presentation layer (Excel). E.g. store your data in an actual database like Access, SQL Server etc.
However, as you are looking for a stop gap, I figured I could give you an approach that might fill the need temporarily. The code is commented, but feel free to ask questions. You will need to add a reference to Microsoft Active X Data Object 2.8 or greater to get this working. How to add a reference?
Early Binding Approach
Option Explicit
Public Sub DisplayView(StartDate As Date, EndDate As Date)
'Add a reference to Microsoft Active X Data Object 2.8 or greater
Dim dbConnection As ADODB.Connection
Dim dbRecordset As ADODB.Recordset
Dim dbCommand As ADODB.Command
Dim OutputSheet As Excel.Worksheet
Dim dbField As Variant
Dim fieldCounter As Long
Set dbConnection = New ADODB.Connection
Set dbRecordset = New ADODB.Recordset
Set dbCommand = New ADODB.Command
Set OutputSheet = ThisWorkbook.Worksheets("Sheet2")
'Do a quick check to determine the correct connection string
'if one of these don't work, have a look here --> https://www.connectionstrings.com/excel/
If Left$(ThisWorkbook.FullName, 4) = "xlsm" Then
dbConnection.connectionstring = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & _
ThisWorkbook.FullName & ";Extended Properties='Excel 12.0 Macro;HDR=YES';"
Else
dbConnection.connectionstring = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & _
ThisWorkbook.FullName & ";Extended Properties='Excel 12.0;HDR=YES';"
End If
'Open the connection and parameterize the query
dbConnection.Open
With dbCommand
.ActiveConnection = dbConnection
.CommandType = adCmdText
'A in B in the text below are the field names in your Sheet 1
'I wasn't sure what the names of the fields are so I named them as they appeared
'That being Column A is called A, Column B is called B etc
.CommandText = "Select * from [Sheet1$] where B in ('A','X') and A >= #StartDate and A < #EndDate"
.Parameters.Append .CreateParameter("#StartDate", adDate, adParamInput, , StartDate)
.Parameters.Append .CreateParameter("#EndDate", adDate, adParamInput, , EndDate)
Set dbRecordset = .Execute
End With
'Clear the Output Sheet
OutputSheet.Cells.Clear
'Add Headers to output
For Each dbField In dbRecordset.Fields
fieldCounter = fieldCounter + 1
OutputSheet.Cells(1, fieldCounter).Value2 = dbField.Name
Next
'Dump the found records
OutputSheet.Range("A2").CopyFromRecordset dbRecordset
If dbConnection.State = adStateOpen Then dbConnection.Close
End Sub
'Run from here
Public Sub ExampleRunner()
'Supply the dates you want to filter for
DisplayView #1/1/2019#, #1/20/2019#
End Sub
As requested, here is the Late Binding Approach that doesn't require an explicit reference to Microsoft Active X Data Object.
Option Explicit
Private Const adCmdText As Long = 1
Private Const adDate As Long = 7
Private Const adParamInput As Long = 1
private const adStateOpen as long = 1
Public Sub DisplayView(StartDate As Date, EndDate As Date)
'Add a reference to Microsoft Active X Data Object 2.8 or greater
Dim dbField As Variant
Dim fieldCounter As Long
Dim dbConnection As Object
Dim dbRecordset As Object
Dim dbCommand As Object
Dim OutputSheet As Excel.Worksheet
Set dbConnection = CreateObject("ADODB.Connection")
Set dbRecordset = CreateObject("ADODB.Recordset")
Set dbCommand = CreateObject("ADODB.Command")
Set OutputSheet = ThisWorkbook.Worksheets("Sheet2")
'Do a quick check to determine the correct connection string
'if one of these don't work, have a look here --> https://www.connectionstrings.com/excel/
If Left$(ThisWorkbook.FullName, 4) = "xlsm" Then
dbConnection.connectionstring = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & _
ThisWorkbook.FullName & ";Extended Properties='Excel 12.0 Macro;HDR=YES';"
Else
dbConnection.connectionstring = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" & _
ThisWorkbook.FullName & ";Extended Properties='Excel 12.0;HDR=YES';"
End If
'Open the connection and parameterize the query
dbConnection.Open
With dbCommand
.ActiveConnection = dbConnection
.CommandType = adCmdText
'A in B in the text below are the field names in your Sheet 1
'I wasn't sure what the names of the fields are so I named them as they appeared
'That being Column A is called A, Column B is called B etc
.CommandText = "Select * from [Sheet1$] where B in ('A','X') and A >= #StartDate and A < #EndDate"
.Parameters.Append .CreateParameter("#StartDate", adDate, adParamInput, , StartDate)
.Parameters.Append .CreateParameter("#EndDate", adDate, adParamInput, , EndDate)
Set dbRecordset = .Execute
End With
'Clear the Output Sheet
OutputSheet.Cells.Clear
'Add Headers to output
For Each dbField In dbRecordset.Fields
fieldCounter = fieldCounter + 1
OutputSheet.Cells(1, fieldCounter).Value2 = dbField.Name
Next
'Dump the found records
OutputSheet.Range("A2").CopyFromRecordset dbRecordset
If dbConnection.State = adStateOpen Then dbConnection.Close
End Sub
'Run from here
Public Sub ExampleRunner()
'Supply the dates you want to filter for
DisplayView #1/1/2019#, #1/20/2019#
End Sub
Here are some screenshots of the Results of the below mentioned Power Query. I selected (In Excel 2003) Data->Import External Data->New Database Query
Then, I chose "Excel Files" and added the data I wanted. Make sure to select "is not null" in the query options. I then added the auto sort feature.
You could use a Power Query in the Sheet2 Excel Workbook. Another link here. This way you could update the data whenever you need to. Then, use SQL to query what you need.
It is pretty simple to use and doesn't take any coding (unless you want to use SQL).
This can be done and then in your other workbook you can do the filtering and sorting.
#ryan-wildry 's post is pretty great (and so is his command text for SQL), but if you do not want to use vba or a database then, you can use this (as well as his SQL text).
An example would be:
SELECT * FROM [Sheet1$] WHERE Column2='X';
The only problem would be if you have mixed datatypes in the same column. As can be seen with rows 1 & 4 (they were not able to come across with the query because the values are not the same type. (This is using Excel 2003 so if you have a newer version then, you may be able to check it with that).
I've been doing some research and found that Excel's Power Query will handle mixed data types so, you should be set if you are using Power Query.
I've got some queries I have connected to an excel sheet that I refresh in order to get the latest data.
Is it possible to make excel "ask" for input parameters for one column lets say?
I am looking to get data by company so wondering if it's possible to do it as in access (type [company]:) in the field criteria
If I save the query like that in access it won't let me connect it to excel
Thanks
Alright another edit.
Here it is, I created a database, its simple.
The database is called "Database1.accdb"
There are more records than shown in the screen shot.
I created a workbook with one sheet, its name is "AccessDBtest.xlsm"
I created a button on Sheet1 and entered the field parameter I wanted in the cell beside it, C3 or (3,3) in (row,col) format.
This is the code that works returning the data set (without field names) based on the input criteria. I made a msgBox before the SQL query execution so that I could look at it first. You don't need that if you do not want it, good for testing.
Private Sub CommandButton1_Click()
Dim inputSheet As Worksheet
Dim fieldSTR As String
Dim placementRange As Range
Dim rs As Object 'record set
Dim conn As Object
Dim strQuery As String
Dim myDB As String
Set inputSheet = ThisWorkbook.Worksheets("Sheet1")
Set placementRange = inputSheet.Range("E2")
fieldSTR = CStr(inputSheet.Cells(3, 3).Value) 'C3 cell
myDB = "C:\Users\Documents\0_Excel Projects\Testing\Database1.accdb"
Set conn = CreateObject("ADODB.Connection")
With conn
.Provider = "Microsoft.ACE.OLEDB.12.0" 'For *.ACCDB Databases
.ConnectionString = myDB
.Open
End With
strQuery = "SELECT * FROM " & _
"tbl_test WHERE tbl_test.Color = " & "'" & fieldSTR & "'" & ";"
'The below gives the same result as * but you could limit the fields returned as well
'tbl_test.ID, tbl_test.Color, tbl_test.number
'just using Color also works you do not need to reference the table directly
MsgBox (strQuery)
Set rs = conn.Execute(strQuery)
placementRange.CopyFromRecordset rs
rs.Close
Set rs = Nothing
conn.Close
Set conn = Nothing
End Sub
See if you can map what you want to do using that.
I wanted to capture a string from a cell and then use that string in the query.
Here is the MsgBox before the query executes (what access will see):
I tested it with * for all fields and below you can see that I called up specific fields, they both work.
And here is what happens after I clear the msgBox, the recordset is pulled and pasted starting in the range I specified Range.("E3") on the sheet I specified (Sheet1)
As you can see, we can pull queries from a database using input that is found on a current sheet.
Let me know if this helps your situation.
We can dig deeper if required.
-WWC
I am building a form in Access that will allow a user to select an excel file, and then select a worksheet to import. I have code that allows a user to select an Excel file and it stores the filename in a control on the form.
Now I want to display the names of the worksheets that are in that file. I know how to do the import, what I don't know is how to get the worksheet names in the file and store them either in a table, or a listbox on the form so the user can select one. Sadly, although all the Excel files are supposed to have standard names for the worksheets, some of the sheets are off and that is why I want to display them.
I am using Office 2007.
Set oWkb = oXLApp.Workbooks.Open(FileName)
For Each oSh In oWkb.Worksheets
MsgBox oSh.Name
Next
oXLApp would be an instance of the Excel application, which you can get using Set oXLApp = CreateObject("Excel.Application").
Make sure you close your workbook when you are done getting the sheet names. I would probably store them in a collection or an array (instead of the MsgBox oSh.Name) so that you can collect them, close the workbook, and display them on your form using the contents of the collection.
In my testing, using an ADOX Catalog to retrieve the worksheet names appears significantly faster than opening an Excel application instance to enumerate the WorkSheets collection.
Public Sub List_WorksheetsAdox(ByVal pWorkBook As String)
Dim cat As Object 'ADOX.Catalog
Dim cn As Object 'ADO.Connection
Dim strConnect As String
Dim tbl As Object 'ADOX.Table
strConnect = "Provider=" & _
CurrentProject.Connection.Provider & ";" & _
"Data Source='" & pWorkBook & "';" & _
"Extended Properties=Excel 8.0;"
Set cn = CreateObject("ADODB.Connection")
cn.Open strConnect
Set cat = CreateObject("ADOX.Catalog")
Set cat.ActiveConnection = cn
For Each tbl In cat.Tables
Debug.Print tbl.Name
Next tbl
Set tbl = Nothing
Set cat = Nothing
cn.Close
Set cn = Nothing
End Sub
This approach will include the dollar sign after each sheet name, which may not be what you want. You could strip it off easily.
I tested using Excel 8.0 for my xls 2003 format workbook. It appears the Excel 2007 format would require "Excel 12.0" for Extended Properties. You can find more details at ConnectionStrings.com.
As in the other answers, you will want to do something more useful with tbl.Name other than Debug.Print it.
Note this approach will list both named ranges and worksheets. If that is an issue for you, you can distinguish between them based on whether or not the name includes a dollar sign ("$") on the end. Sheets have the dollar sign; named ranges do not.
Something like this?
Dim wb As Excel.Workbook
Dim ws As Excel.Worksheet
Set wb = Excel.Workbooks.Open("C:\MyBook.xls") ' Or whatever
For Each ws In wb.Worksheets
Debug.Print ws.Name
' This prints the name in the Immediate window.
' You'll want to do something useful with them instead.
Next ws
To use this, must set reference to Excel object library: Tools > References > set checkmark next to Microsoft Excel xx.0 Object Library.
Hey all, have been working on designing a new database for work. They have been using Excel for their daily reports and all the data is stored in there, so I decided to have the back-end of the database in Access and the front-end in Excel, so any analytical work can be easily performed once all the data has been imported into Excel.
Now I'm fairly new to VBA, slowly getting used to using it, have written some code to transfer one of the calculated tables from Access to Excel:
Option Explicit
Public Const DataLocation As String = "C:\Documents and Settings\Alice\Desktop\Database\TestDatabase21.accdb"
Sub Market_Update()
Call ImportFromAccessTable(DataLocation, "Final_Table", Worksheets(2).Range("A5"))
End Sub
Sub ImportFromAccessTable(DBFullName As String, TableName As String, TargetRange As Range)
Dim cn As ADODB.Connection, rs As ADODB.Recordset, intColIndex As Integer
Set TargetRange = TargetRange.Cells(1, 1)
' open the database
Set cn = New ADODB.Connection
cn.Open "Provider=Microsoft.ACE.OLEDB.12.0; Data Source=" & DBFullName & ";"
Set rs = New ADODB.Recordset
With rs
' open the recordset
' .Open TableName, cn, adOpenStatic, adLockOptimistic, adCmdTable
' all records
.Open "SELECT * FROM Final_Table", cn, , , adCmdText
' filter records
For intColIndex = 0 To rs.Fields.count - 1 ' the field names
TargetRange.Offset(0, intColIndex).Value = rs.Fields(intColIndex).Name
Next
TargetRange.Offset(1, 0).CopyFromRecordset rs ' the recordset data
End With
rs.Close
Set rs = Nothing
cn.Close
Set cn = Nothing
End Sub
Sub Company_Information()
Dim companyName As String
On Error GoTo gotoError
companyName = Application.InputBox(Prompt:="Enter Company Name", _
Title:="Company Name", Type:=2)
Exit Sub 'Don't execute errorhandler at end of routine
gotoError:
MsgBox "An error has occurred"
End Sub
The above code works fine and pulls up the desired calculated table and places it in the right cells in Excel.
I've got two problems that I'm having trouble with; firstly I have some cell-formatting already done for the cells where the data is going to be pasted into in Excel; I want it to apply the formatting to the values as soon as they are pasted in Excel.
Secondly; I have an add-on for Excel which updates some daily Stock Market values; these values need to be transferred into Access at the end of each working day, to keep the database maintained, I tried some code but have been having some problems with it running.
The code for this part can be seen following:
Sub UPDATE()
Dim cnt As ADODB.Connection
Dim stSQL As String, stCon As String, DataLocation As String
Dim stSQL2 As String
'database path - currently same as this workbook
DataLocation = ThisWorkbook.Path & DataLocation
stCon = "Provider=Microsoft.Jet.OLEDB.4.0;" & _
"Data Source=" & DataLocation & ";"
'SQL code for GL Insert to Access
stSQL = "INSERT INTO Historical_Stock_Data SELECT * FROM [Portfolio] IN '" _
& ThisWorkbook.FullName & "' 'Excel 8.0;'"
'set connection variable
Set cnt = New ADODB.Connection
'open connection to Access db and run the SQL
With cnt
.Open stCon
.CursorLocation = adUseServer
.Execute (stSQL)
End With
'close connection
cnt.Close
'release object from memory
Set cnt = Nothing
End Sub
I get the following error with this.
Run-time Error '-2147467259 (80004005)'
The Microsoft Jet database engine cannot open the file 'Cocuments and Settings\Alice\Desktop\Database'. It is already opened exclusively by another user or you need permission to view its data.
I'm fairly new to databases, VBA and Access so any help would be greatly appreciated.
Also I have been told that the above method of having an Excel front-end and Access back-end is not recommended but alot of the analysis they conduct is done through Excel, and the charts feature in Excel is much better than Access in my experience atleast; and that is also one of the requirements for this project.
Thank you advance!
Solution to your first problem:
Sorry to be the bearer of bad news, but your entire first module is unnecessary. Instead, try:
Go to Data->Import External Data->Import Data, select your Access file, select your table, and presto! done!
Right-click on your new "External Data Range" to see a number of options, some related to formatting. You can even keep the original cell formatting and just update the values. I do this all the time.
To update the Excel data table later, there is a "External Data Range" toolbar that allows you to refresh it as well as a "refresh all" option to refresh every table in the Excel file. (You can also automate this thru code. It'll take some trial and error, but you're definitely up to the task)
Regarding your second problem
I've never used it, but there is also a "New Web Query" option in there as well. I assume it can be manipulated and updated the same way.
And lastly
Your choice of the Excel front-end and the Access back-end sounds good for your needs. It gets the data to your analysts in a medium they are familiar with (Excel) while keeping the calculations out of the way in Access. Technically, you could try putting all your calculations in Excel, but that might the Excel file much bigger and slower to open.
Do the data entry/updating/reviewing in Access. One of Access' strengths is using forms that allow you to update the tables without any code. Then allow the users to easily export the data to Excel such as by clicking on some command buttons.
Modules: Sample Excel Automation - cell by cell which is slow
Modules: Transferring Records to Excel with Automation
nothing wrong in principle with the excel/access pairing. I'm not familiar with ADO (I use DAO), but your error message seems to be indicating that the path to the datasource is not fully formed; or you already have it opened and hence are locking it.