I have a table in MS Access, which has the following data to be exported to excel
Release numbers
Test cases
Results
After exporting to Excel I want to have distinct release numbers as rows starting from A2 and distinct test case name as columns starting from B1. There might be couple thousands records. Then each cell will be set to result tag. Additionally will need some fancy coloring/bordering stuff.
The question - is it possible to do this using VBA in Access and if yes what is the way to go? Any hint, sample, example, resource would be appreciated... I've googled but the most thing I came accross is DoCmd.TransferSpreadsheet or DoCmd.OutputTo which I believe will not do what I want. Saw some examples with CreateObject("Excel.Application") but not sure what are limitations and performance using this way.
I don't know if it would work for your case, but you might try adding the VBA code to an Excel document rather than the Access database. Then you could refresh the data from the Excel file and add the formatting there much easier. Here is one example:
http://www.exceltip.com/st/Import_data_from_Access_to_Excel_%28ADO%29_using_VBA_in_Microsoft_Excel/427.html
(Or see other examples at http://www.exceltip.com/exceltips.php?view=category&ID=213)
Again, it may not work for your case, but it may be an option to consider. Essentially, instead of pushing from Access, you would pull from Excel.
Yes, there are many cases when the DoCmd.TransferSpreadsheet command is inadaquate.
The easiest way is to reference the Excel xx.x Object model within Access (Early Binding). Create and test your vba export function that way. Then once you are satisfied with your output, remove the Excel object model reference, then change your objects to use use Late Binding using CreateObject. This allows you to easily have other machines that are using different versions of Excel/Access to use it just the same.
Here is a quick example:
Sub ExportRecordsetToExcel(outputPath As String, rs As ADODB.Recordset)
'exports the past due report in correct formattig to the specified path
On Error GoTo handler:
Const xlUP As Long = -4162 'excel constants if used need to be referenced manually!
Dim oExcel As Object
Dim oBook As Object
Dim oSheet As Object
Dim row As Long
If rs.BOF And rs.EOF Then
Exit Sub 'no data to write
Else
rs.MoveFirst
End If
row = 1
Set oExcel = CreateObject("Excel.Application")
oExcel.Visible = False 'toggle for debugging
Set oBook = oExcel.Workbooks.Add 'default workbook has 3 sheets
'Add data to cells of the first worksheet in the new workbook.
Set oSheet = oBook.worksheets(1)
Do While rs.EOF = False
oSheet.range("A" & row).value = rs.Fields("MyField").value
'increase row
row = row + 1
Loop
oBook.SaveAs (outputPath)
'tidy up, dont leave open excel process
Set oSheet = Nothing
Set oBook = Nothing
oExcel.Quit
Set oExcel = Nothing
Exit Sub
handler:
'clean up all objects to not leave hanging processes
End Sub
Related
I wanted to find out if anyone can recommend an alternative way to query data in an excel file from an MS Access module.
If I have a data organised in a typical "Table" format e.g rows and columns with headers, then I have been connecting to the workbook using a DAO connection as below
Dim db as DAO.Database
Dim rsUsers as DAO.Recordset
Set db = OpenDatabase("C:\SaleLog.xls", False, True, "Excel 8.0;HDR=Yes;")
Set rsUsers = db.OpenRecordset("SELECT userID FROM [Sales$]")
I find this preferable to using
Dim xlApp As Excel.Application
Set xlApp = CreateObject("Excel.Application")
xlApp.Workbooks.Open "C:\SaleLog.xls", True, False
As it does not physically open an instance of an excel session and therefore runs a bit more quickly.
The only problem is when the data is not laid out like a table (e.g if I just wanted to get the value of 1 particular cell).
Does anyone know if there is a way to check the value of a cell in VBA without using Workbook.Open?
There may be a simpler way to achieve this but I have found 3 methods which can be used to pull data into MS Access from a single cell in a closed Excel workbook relatively efficiently (Many thanks to John Muggins for his help with this).
Firstly, the workbooks.open method can be used and will run more quickly if you set the read-only parameter to true (I'm unsure if this technically 'Opens' the workbook but it definitely seems to run more quickly while read-only). This requires you to set a reference to the Excel library.
Dim xlApp As Excel.Application
Dim src As Workbook
Set xlApp = CreateObject("Excel.Application")
Set src = xlApp.Workbooks.Open "C:\SaleLog.xls", True, True
You can then get data from specific cells using normal Excel VBA syntax like so
strUser = src.Worksheets("Sales").Range("B8")
src.Close False 'false doesn't save changes
Set src = Nothing
Another method is to use the DoCmd.Transferspreadsheet method to link or import a single cell, in this case you would need to set the HasFieldNames parameter to false as shown
path = "C:\SaleLog.xls"
DoCmd.TransferSpreadsheet acImport, acSpreadsheetTypeExcel9, "tblTemp", path, False, "Sales!B8:B8"
This will create a table with a single field called F1. You can get the value stored in this table using a DLookup or a recordset (You may then wish to delete the temp table e.g. DoCmd.DeleteObject acTable, "tblTemp").
Lastly you can use a DAO connection as mentioned in the original question. You can specify a specific cell like so
Dim db as DAO.Database
Dim rsUsers as DAO.Recordset
Set db = OpenDatabase("C:\SaleLog.xls", False, True, "Excel 8.0;HDR=Yes;")
Set rsUsers = db.OpenRecordset("SELECT * FROM [Sales$B8:B8]")
Again this will create a recordset with a single field called F1. You can get the value of this field by using rsUsers.Fields("F1")
Each of the above methods can be (and usually are) used to pull data from an entire worksheet or range of cells but for the purpose of this question I wanted to show how they can be used to pull data from a single cell. I haven't tested them for efficiency although I doubt there is much difference in speed between them so probably best to go with the method that you find simplest.
If anyone knows how to tweak any of these methods to make them more efficient or has another approach altogether please feel free to comment or add a new answer :)
Hi I am using MSAccess (2003) and retrieving an XML recordset from an URL successfully in a macro.
The trouble is the macro and the vba seem to be asynchronous meaning I need a callback on the retrieved xml to continue processing it. I can seem to find anything on the net for a basic VBA callback (I have found stuff on dll's but I am not using these). Does anyone have the missing piece in my puzzle please?
Const acAppendData = 2
Set objAccess = CreateObject("Access.Application")
objAccess.OpenCurrentDatabase "C:\Scripts\Test.mdb"
objAccess.ImportXML "http://api.com/api", acAppendData
I was able to run the following successfully in Access:
Public Sub Test()
Dim rs As DAO.Recordset
Application.ImportXML "http://www.w3schools.com/xml/plant_catalog.xml", acStructureAndData
Set rs = CurrentDb.OpenRecordset("PLANT")
rs.MoveLast
Debug.Print rs.RecordCount
End Sub
The .xml file is public, so you should be able to test this without modification.
A method I used to solving this was to use a do while loop querying the resultant table until a record count > 0 was returned and delaying each repeat by 1 second
I have an access 2003 front end database with a form that allows users to see a sorted and/or filtered view of some data. The data is displayed in a sub-form.
The base data (loaded when the form is opened) is retrieved into a disconnected ADODB.Recordset object (static client side cursor). The sub-form's Recordset property is set to the disconnected recordset and all records are displayed.
Applying just a sort (in code) to the recordset object and then setting the sub-form to use the sorted recordset displays the data with the correct sort applied. The filter property is set to adFilterNone for this to work. All records are displayed (correct).
Applying just a filter (in code) to the recordset object and then setting the sub-form to use the filtered recordset displays the data with the correct filter applied. The sort property is set to an empty string for this to work. All records matching the filter are displayed (correct).
When both the sort property AND the filter property are set on the recordset, and that recordset is then set to the sub-form's Recordset property, only the first 100 matching records are displayed (incorrect). They are displayed in sort order. The underlying recordset object shows the correct record count for the filtered records, they just don't all display on the form.
Does anyone know why this is happening and if there is a way to get around this apart from creating a recordset using a new SQL string each time?
Thanks in advance.
What you are seeing with filtering and sorting is a known limitation of ADO recordsets.
Take a look at the list of ADO Cons listed on this page. Notice the one on the bottom:
http://www.utteraccess.com/wiki/index.php/Choosing_between_DAO_and_ADO
I couldn't find any documentation on MS's Support site about this so I don't know if it's a bug or simply a limitation. I'm assuming it's the latter.
FYI, I think MS has basically forgotten about ADO (classic). The last release of MDAC (which is how you obtain ADO) was 5/10/2005.
As far as a work-around for this problem, you can try using this function. It returns a new, filtered and sorted recordset. Just keep a big, full recordset handy and use this function to get a new one every time you do a sort/filter. This does increase your overall resource usage, especially memory.
I have used this function but it hasn't been fully tested to make sure it's bullet proof in every way. You might quickly find some bug or limitation with it. I actually had a note that it needed some kind of work but my note was unclear, I didn't have time to test it now, and I did find that I'm using this function in my production code so I think it's working.
Public Function GetFilteredRecordset(ByRef rsSource As ADODb.Recordset, _
ByVal sWhere As String, _
Optional ByVal sOrderBy As String, _
Optional ByVal LockType As ADODb.LockTypeEnum = adLockUnspecified) As ADODb.Recordset
Dim sOriginalOrderBy As String
sOriginalOrderBy = rsSource.Sort
Dim F As ADODb.Field
For Each F In rsSource.Fields
'Debug.Print F.Name
Next F
rsSource.Filter = sWhere
If sOrderBy <> "" Then
If Left(LCase(sOrderBy), 8) = "order by" Then sOrderBy = Trim(Right(sOrderBy, Len(sOrderBy) - 8))
rsSource.Sort = sOrderBy
End If
Dim rsF As ADODb.Recordset
Dim objStream As ADODb.Stream
'Create a New ADO 2.5 Stream object
Set objStream = New ADODb.Stream
'Save the Recordset to the Stream object in XML format
rsSource.Save objStream, adPersistXML
'Create an exact copy of the saved Recordset from the Stream Object
Set rsF = New ADODb.Recordset
rsF.Open objStream, , , LockType
rsSource.Filter = ""
rsSource.Sort = sOriginalOrderBy
'Close and de-reference the Stream object
objStream.Close
Set objStream = Nothing
Set GetFilteredRecordset = rsF
End Function
Another strange limitation of filtering ADO recordsets is that your OR keyword must always be on the top level. That is also documented in the link I posted above although I'm not sure if the examples given are accurate.
After an extensive search I have been unable to find any information on this that I could understand. there are numerous examples, but these are all for access 2003, and these do not work in access 2010.
I need to run a vba code that will export the results of a query (QryTotalSale) to excel 2010 and automatically create a bar chart of the data and show this over the database that is running.
If anyone could give me some advise then I would greatly appreciate it, or even a link to a valid resource that will work in 2010.
So far I can get excel to open, and display the results of the query in question.
To make it more difficult I really need the query to open a specific excel file, which will be on a folder on the desktop and every time the button is pressed to run the VBA, a new page should be added to the excel workbook, and the new graph be shown, and saved into the spreadsheet, so that at a later date the entire excel file can be viewed.
I have the below code, but it does not work. the bit about ranges would work in excel, but access does not seem to recognise range ( which does not really suprise me as it does not really work with ranges to my knowledge.)
My second thought was to have the first two doCmd's run, then have the next bit be forced to auto run in the excel file.
Private Sub SalesImage_Click()
DoCmd.OpenQuery "QryTotalSale"
DoCmd.RunCommand acCmdOutputToExcel
Dim myRange as range
Set myRange = B2 * C30
Charts.Add
ActiveChart.ChartType = xlColumnClustered
ActiveChart.SetSourceData Source:=myRange, _
PlotBy:=xlColumns
ActiveChart.Location Where:=xlLocationAsNewSheet
End Sub
I found a adodb code type thing for the 2003 versions of access and excel, but could not get this to work. half of the options no longer seem to be recognised by access...
I am a long way off and would really appreciate any help.
Thanks
Sam
Here are some notes. I have used late binding, so you do not need to set a reference to the Excel library, however, I have included notes on the types.
Dim xl As Object ''Excel.Application
Dim wb As Object ''Excel.Workbook
Dim ws As Object ''Excel.Worksheet
Dim ch As Object ''Excel.Chart
Dim myRange As Object
Set xl = CreateObject("Excel.Application")
sExcelWB = "z:\docs\testchart.xls"
''This will overwrite any previous run of this query to this workbook
DoCmd.TransferSpreadsheet acExport, acSpreadsheetTypeExcel9, "Query1", _
sExcelWB, True
Set wb = xl.Workbooks.Open(sExcelWB)
''Sheets are named with the Access query name
Set ws = wb.Sheets("Query1")
Set ch = xl.Charts.Add
ch.ChartType = xlColumnClustered
xl.Visible = True
xl.UserControl = True
''Still not saved
EDIT: Changed as I have a different issue with the same code
2nd Edit: Adding additional code that seems to be casuing the issue
I have created a vba program in access that aggregates data from a number of external sources and write the results into a new table. Ideally when I run the program I want to wipe out all of the data that is currently in the table and replace it with my new data. I am currently currently deleting all of the data in the table... then writing my new data
Here is the code for reference
Function getTestFixtures(FixtureName As String) As Recordset
Dim db As Database
Set db = OpenDatabase(GetDBPath & "TestFixtures.xlsx", False, False, "Excel 12.0;HDR=Yes;")
If db Is Nothing Then
MsgBox "Can't find the file!", vbExclamation, ThisWorkbook.Name
Exit Function
End If
Set getTestFixtures = db.OpenRecordset("Select * from [" & FixtureName & "$]")
End Function
The recordset created above is modified and the output data is placed in a dictionary and passed to this function.
Sub Write_OTDC_Data(POlist As Dictionary)
Dim Rst As Recordset
DoCmd.SetWarnings False
DoCmd.runsql "Delete * from [OTDC Results]"
DoCmd.SetWarnings True
Set Rst = CurrentDb.OpenRecordset("OTDC Results")
With Rst
For Each key In POlist.Keys
.AddNew
For i = 0 To 9
.Fields(i).value = POlist(key)(i)
Next
.Update
Next
.Close
End With
End Sub
My Problem is that I get the following error if I try to change anything after running both of the above procuedures.
Running either in isolation does not generate the error.
I'm unsure whether this question is still unresolved. In case it's not, I have some suggestions for you to try, but not a lot of confidence they will cure the problem.
Try DoCmd.TransferSpreadsheet to import the sheet's data into a scratch table instead of using OpenDatabase with the workbook.
In your MsgBox, I wonder whether ThisWorkbook.Name means anything to an Access application. Aside from that, I would check whether the workbook exists, then open it (or import the sheet from it) only if the file is found.
If Len(Dir(GetDBPath & "TestFixtures.xlsx")) = 0 Then
'not found
Else
'use it
End If
Actually I'm unclear why you don't get an error from OpenDatabase if the workbook file doesn't exist. And that makes me suspicious of DoCmd.SetWarnings False Never, ever turn SetWarnings off. Doing so suppresses important information. And it is completely unnecessary. Set a DAO.Database object variable to CurrentDB(), then use this instead:
dbObjectVariable.Execute "Delete from [OTDC Results]", dbFailOnError
Add an error handler to deal with any problems dbFailOnError exposes.
Finally, this bears repeating because it's so important. NEVER turn SetWarnings off.