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
Related
I am trying to insert some records into MS Access Table with the help of below VB Script. But when am trying to execute it, it's throwing Compilation error: Expected end of statement. Could someone please help me figure out where am I going wrong.
Private Sub Form_Click()
Dim dbs As DAO.Database
Dim DbFullNAme As String
DbFullName = "D:\G\Diamond\FINAL MS-Access\MS-Access project.accdb"
Set dbs = OpenDatabase(DbFullName)
dbs.Execute "INSERT INTO [2014_Status] ( Prompt, Project_Name, STATUS,Release_Name )SELECT RoadMap.SPRF_CC, RoadMap.SPRF_Name, RoadMap.Project_Phase,RoadMap.Release_Name FROM RoadMap WHERE (((Exists (select 1 FROM [2014_Status] where RoadMap.SPRF_CC = [2014_Status].[Prompt]))=False));"
dbs.Close
End Sub
VBScript (as opposed to VBA or other dialects) does not support typed Dims. So
Dim dbs As DAO.Database
Dim DbFullNAme As String
need to be
Dim dbs
Dim DbFullNAme
VBscript has no native OpenDatabase() function. You need to use ADO to connect to your Access 'database'. First create a connection
Set dbs = CreateObject("ADODB.Connection")
Then determine the connection string and
dbs.Open cs
The rest of your code should work.
Update wrt comment:
The error message:
D:\G\Diamond\FINAL MS-Access\query1.vbs(2, 9) Microsoft VBScript compilation error: Expected end of statement
prooves that the OT tried to write a VBScript (the addition of the misleading vba/access tags is (C) Pankaj Jaju).
So lets break down the real reason why this code doesn't work.
You copied and pasted Visual Basic for Applications(VBA) into a .VBS(Visual Basic Script) file and expected it to work, I assume.
The problem with this is that VBA and VBScript are slightly different languages. Review the info section for both tags on stackoverflow when you get the opportunity.
For now lets just patch your code and maintain your DAO object so you don't have to reconstruct your Database usage with ADODB.
ExecuteInsert
Sub ExecuteInsert()
Dim dbs, DbFullName, acc
Set acc = createobject("Access.Application")
DbFullName = "D:\G\Diamond\FINAL MS-Access\MS-Access project.accdb"
Set dbs = acc.DBEngine.OpenDatabase(DbFullName, False, False)
dbs.Execute "INSERT INTO [2014_Status] ( Prompt, Project_Name, STATUS,Release_Name )SELECT RoadMap.SPRF_CC, RoadMap.SPRF_Name, RoadMap.Project_Phase,RoadMap.Release_Name FROM RoadMap WHERE (((Exists (select 1 FROM [2014_Status] where RoadMap.SPRF_CC = [2014_Status].[Prompt]))=False));"
dbs.Close
msgbox "done"
End Sub
Changes made.
Blocked your dim'd variables and removed As *** statements for vbscript compatibility
Set an access object so you could maintain the remainder of your code.
Added the acc.DBEngine. before OpenDatabase with additional parameters.
Renamed your Sub from Form_Click to ExecuteInsert, then placed ExecuteInsert at the top of the code so that the vbscript activates the sub. If you just place a sub in a vbscript file, it will not necessarily run, you have to activate it directly.
This code is tested and functions. Best of luck to you.
Adding to Ekkehard.Horner
http://www.csidata.com/custserv/onlinehelp/vbsdocs/vbs6.htm
VBScript has only one data type called a Variant. A Variant is a
special kind of data type that can contain different kinds of
information, depending on how it's used. Because Variant is the only
data type in VBScript, it's also the data type returned by all
functions in VBScript.
I have a very complex process that involves downloading a number of files from different shares, concatenating those files into working tables, manipulating and calculating related information, and then exporting specific fields (and calculations) as reports into a number of Excel workbooks.
I have this process coded so that I can click one button and the entire process will execute end to end. I have a series of text boxes that function as 'indicators' (red - this part failed, green - this part succeeded). As you can imagine, the code for the entire process is HUGE (32 pages when copied into MSWord) and difficult to weed through when I have a problem.
I got it into my mind that I wanted to put the code into a table so that it was much more modular and easier to deal with. I have setup a combo box with the action that I want to take and a second combo box with the report/file/object that I want to work with (ie Delete - Table 2, Acquire - File 1, Export - Report 4). I have been successful at creating the SQL statement to do simple things like del * from tbl_test and execute that from the combo boxes without any issue.
What I need to know is if there is a way to put what is essentially a code snippet into the table (memo field) and then have that vba code execute when I select the matching combos.
IE the code for 'Acquire - File1' is completely VBA code; it maps a network drive, locates the file, downloads the file, and moves it to a directory.
IE the code for 'Scrub - tblMain_Part1' is a combination of vba and sql code; it checks for the existence of a file (vba), if it finds it, it deletes a portion of the main table (sql) and appends the contents of the file it finds (sql), then it updates the monitor to indicate that it is completed (vba). If the file is not found, it changes the monitor box to red and updates a command button caption (vba)
I am NOT a genius with vba, but I hold my own. The thought process I had was that if I can essentially get the code broken into managable chunks in the table, I could call the code smippets in order if I want to run the entire process, or I could just re-execute portions of the code as needed by selecting the action and report/file/object combination.
Any thoughts/ideas are appreciated.
I think it would be best to split the code into Subs. The table you loop through would have a Sub-Name field and a blnSuccess field. Your code would loop though the table running each sub and then updating blnSuccess based on any errors you receive. This would give you queryable result set when you try to see what happened.
Consider using macros. You shouldn't need a table. Also, consider moving your hard-coded SQL to queries.
I think that you shouldn't use a table, just create a module with different subs for each operation. On your button event, after the combo selections, I would do a case statement.
dim strOperation as string
strOperation = me!selectionOne
Select Case strOperation
Case "delete": deleteTable(me!selectionTwo)
Case "export": export(me!selectionTwo)
case "acquire": acquire(me!selectionTwo)
End Select
Of course, you'd have your acquire, delete, and export methods written in a module and have whatever parameters you need for each operation there.
This is just one idea of many that you could use to approach this.
I was going to edit the original answer but this seems to be off on a different tack....
I think it would be best to split the code into functions that return a string if there is an error. The table you loop through would have a strFunction,strError and strObject fields. Your code would loop though the table running each function based on the case statement while passing the strObject as a string and then updating strError based on any errors you receive. You could query the table after this process to see which records have errors in them.
If the button is called cmdRunAll here is the code for it.
Private Sub cmdRunAll_Click()
On Error GoTo ErrHandler
Dim rst As DAO.Recordset
Set rst = CurrentDb.OpenRecordset("tblCode", dbOpenDynaset, dbSeeChanges)
If Not rst.EOF Then
With rst
.MoveFirst
Do While Not .EOF
.Edit
Select Case !strFunction
Case "fExport"
!strError = fExport(!strObject)
End Select
.Update
.MoveNext
Loop
End With
End If
rst.Close
Set rst = Nothing
MsgBox "Processes complete"
Exit Sub
ErrHandler:
Debug.Print Err.Description & " cmdRunAll_Click " & Me.Name
Resume Next
End Sub
Here is a simple sample function
Public Function fExport(strTable As String) As String
On Error GoTo ErrHandler
Dim strError As String
strError = ""
DoCmd.TransferText acExportDelim, , strTable, "C:\users\IusedMyUserNameHere\" & strTable & ".txt"
fExport = strError
Exit Function
ErrHandler:
strError = Err.Description
Resume Next
End Function
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.
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
I am trying to retrieve an ADODB recordset from a function in MS-Access 2007 but get a very annoying error message thrown in my face saying: "Argument not optional (Error 449)".
I really can't figure out what I am doing wrong, please help!
Regards,
Stefan
FUNCTION:
Function Rs(sourceSQL As String) As ADODB.Recordset
' Create New Disconnected Recordset
Dim rsConnection As ADODB.Connection
Dim rsRecordset As ADODB.Recordset
Set rsConnection = New ADODB.Connection
rsConnection.Open CurrentProject.Connection
Set rsRecordset = New ADODB.Recordset
rsRecordset.CursorLocation = adUseClient
rsRecordset.Open sourceSQL, rsConnection
Set Rs = rsRecordset
Set rsRecordset.ActiveConnection = Nothing
End Function
FUNCTION CALL:
Private Sub Form_Load()
Call Rs("tblDocumentCode")
Debug.Print Rs.txtDocumentCode(0).Value
End Sub
You are using rs twice, once as a function, once as the name of a recordset:
Private Sub Form_Load()
Set Myrs= Rs("tblDocumentCode")
Debug.Print MyRs(0).Value
End Sub
Assuming that "txtDocumentCode" is a field in the recordset, this:
Private Sub Form_Load()
Call Rs("tblDocumentCode")
Debug.Print Rs.txtDocumentCode(0).Value
End Sub
...should be changed to this:
Private Sub Form_Load()
Debug.Print Rs("tblDocumentCode").Fields("txtDocumentCode").Value
End Sub
So far as I can tell, that should work without needing to assign the recordset returned by the function to a variable.
But let me step back a bit and suggest that fixing this syntactical error begs the question: what's being done her is pretty inadvisable. Every time this function is called, you're opening a new connection, but Access works better with a single connection that is used over and over again. That's true of both Jet/ACE back ends and server back ends. The overhead required to initialize the connection is going to make your forms load really slowly.
But even worse, this is not Access programming -- this is "refugee from a programming environment that lacks bound forms/controls" programming. You should be using ODBC with linked tables and then you can assign recordsources to your forms without having to muck about with ADO recordsets. If you insist on doing what you're doing, you might as well not be using Access at all, because you're discarding 75% or more of the advantages of Access, and you're getting no performance or concurrency benefits from doing so (and buying yourself a world of problems).
Of course, now that I look at it again, you're using the CurrentProject.Connection, and I'm not sure how this interacts with ODBC linked tables. Indeed, I guess it's possible this is an ADP and not an MDB/ACCDB, but that makes even less sense, since you don't need to do anything extra at all in an ADP to populate your forms with ADO recordsets -- it's the default.
So, in general, something is wrong beyond the syntax error -- what you are doing simply doesn't make a lot of sense.