QueryTable.Refresh causes incomplete OLE action; how to complete? - excel

I'm trying to auotmate this report and have written VBA to perform various tasks, then I call each of them sequentially in a "RunAll" sub. I have data from an Access query to a tab in my workbook for a pivot table.
I've discovered that when I run this:
Sub QueryTableRefresh()
ActiveWorkbook.Sheets("data").Activate
Range("A2").Activate
Selection.ListObject.QueryTable.Refresh BackgroundQuery:=False
End Sub
it creates an incompelte OLE action with Access, which causes a Read Only instance of Access to open when I run the below ("TableRefresh" is a function with delete and append queries):
Public Sub RefreshAccessTables()
Dim acApp As Object
Dim db As Object
Set acApp = CreateObject("Access.Application")
acApp.OpenCurrentDatabase ("P:\Reports\Daily Origination Volume\Daily Origination Volume.accdb")
Set db = acApp
acApp.Run "TableRefresh"
acApp.Quit
Set acApp = Nothing
End Sub
The read only instance prevents the writing actions of the delete and append queries. This is not the sequence in which these two run within my RunAll, but the workflow would require that the RunAll would be run again if any updates are made, which then causes the conflict.
I've run the Access function from Excel multiple times back to back and have no problems, until I run the querytable refresh. Refreshall causes the same problem.
How do I update only my query table in Excel, without leaving this OLE action incomplete?

I discovered that the problem may be alleviated by choosing another method to import the data, that being MS Query. Rather than choosing the "From Access" option for importing data, choose "From Other Sources" then "From Microsoft Query."
After using this import method, I refreshed it multiple times back to back. Immediately following the refreshing, I was able to open the source database in read/write mode.

Related

Bypass transaction for logging

I'm importing data from Excel to Access. During the import I validate it and I insert only rows that are ok. Errors are logged into another table that is later used by a report. The import is done manually with a VBA macro. I use CurentDb.OpenRecordset for writing.
Current solution: only valid data + keep logs
I import data from Excel row by row and keep them in the database after the import. Even if I find errors in other rows.
At the same time I log errors into another table.
Desired solution: all or nothing + keep logs
Commit the imported data only if all data is valid.
Keep the error log also when the imported data is rolled back to show a report.
Issue
When I rollback the imported data, the logs are also rolledback so I cannot use it for reporting.
Question
Is there a way to bypass the import transaction so that the log is always written?
You can create multiple workspaces and corresponding database objects, which have separate transactions, even on the database that's currently open. Note that this won't work if you have an exclusive lock on the database.
Dim wsLog As DAO.Workspace
Dim wsImport As DAO.Workspace
Dim dbImport As DAO.Database
Dim dbLog As DAO.Database
Dim dbe As DAO.DBEngine
Set dbe = DBEngine
Set wsLog = dbe.CreateWorkspace("wsLog", "Admin", "")
Set wsImport = dbe.CreateWorkspace("wsImport", "Admin", "")
Set dbLog = wsLog.OpenDatabase(CurrentProject.FullName)
Set dbImport = wsImport.OpenDatabase(CurrentProject.FullName)
wsImport.BeginTrans
dbImport.Execute "INSERT INTO Table1(Field1) VALUES(1)" 'Do your imports/processing in this workspace
wsLog.BeginTrans
dbLog.Execute "INSERT INTO Table2(Field1) VALUES(1)" 'And logging here
wsLog.CommitTrans 'Commit log
wsImport.Rollback 'Rollback import afterwards
'Result: Table2 is changed, Table1 isn't
Note that while it's not required to use a separate logging database, if you use Access for storage, I do recommend it.
And, as said in the comments, you can just work with a temporary table too.
You create a permanently invisible table that's intended for temporary use using CreateTableDef with the dbHiddenObject constant:
CreateTableDef("TableName", dbHiddenObject)
It won't get removed automatically, so it's not temporary in that sense, though. And keep in mind that deleting + recreating temporary tables will increase the database size, likely requiring more compact operations.

Can a method in vba call another method

I have written some code to connect to a database with excel.
When retrieving information from the database you need two things when using ADODB.
- Connection to the database
- record to retrieve the information.
Now I have used the copy past to change what needs to be changed.
This creates a lot of duplicate data for setting the connection whats is always the same
can something be done like this.
Sub StartQ1
Call Openconnection
Call DoStuffWithRecord
Call CloseConnection
End sub
or can something be done like this
Sub StartQ1
call Connection( call DoStuffWithRecord )
en sub
Thanks
Let me transpose your question: When you make a phone call you establish a connection, then you talk, and when you finish talking you close the connection. Now, is it possible to establish the connection and talk in one operation? Perhaps the first counter question to come up is why I should want that.
I think what you mean is to return the connection to the Main procedure from a function call like
Set MyConnection = OpenConnection()
' then use the connection to make your call
MyData = GetData(MyConnection)
' pass the data to the manipulating procedure
DoStuffWithRecord MyData
' and then close the connection
CloseConnection MyConnection
The subject of your question is how to pass data and objects as parameters to procedures and function. There is a lot of literature on this available on the web. Prepare to invest an hour of your time. This link may not be the first one you will need but it's a good, useful and reliable source of knowledge.

User Defined Functions (UDF) from Access Query to Excel using VBA OpenRecordset failed - Undefined Function

How do I get the results of a query from Access into Excel if it has a UDF?
I receive the following error: "Run-time error '3085': Undefined function 'XXXX' in expression". The error occurs when opening an (access query) recordset from Excel VBA. The query being opened has a user defined function (UDF) which is triggering the error.
The code is in Excel Office 365. The query is in Access Office 365.
I have successfully utilized the query being called (and others with the UDFs) for about twelve months, and "suddenly" it is not working any more. I have googled and tested many options with no success.
Most threads say it can't be done, or to not use a udf but try a built-in that works. I am challenging those responses because it has worked previously. The main udf I am using is one called "iMax" which is written about in other posts. It functions like max() in Excel. (No max(x,y) function in Access)
I have also seen threads that suggest executing this in two steps: 1 - change the query to a make table query. 2 - pull the table results into Excel. While I could maybe get away with this (after much rework), it would result in me making many temporary tables with thousands and thousands of rows and doesn't seem very slick.
I have compiled vba and compacted the db with no impact to my problem.
As a long shot I created a dummy database with a simple udf public function that returned the number 1, a simple query that returns three records and a field for the function results. This gets the same error when pulling into Excel.
Sub RunQuery()
Dim MyDatabase As dao.Database
Dim qdf As dao.QueryDef
Dim rs As dao.Recordset
Dim qryname As object
Dim SheetName As String
Set MyDatabase = DBEngine.OpenDatabase _
("SomePath\SomeFilename.accdb")
For Each qryname In Range("SomeRange")
Set rs = MyDatabase.OpenRecordset(qryname) '<<<ERROR IS HERE
SheetName = "SomeSheetName"
With Sheets(SheetName)
.ListObjects(SomeTableName).DataBodyRange.Rows.ClearContents
.Range("A2").CopyFromRecordset rs
End With
Set rs = Nothing
Set qdf = Nothing
Next qryname
End Sub
For all queries in the For loop that do not have a udf, the results are pulled and dumped into a series of tables in Excel. Any query with a udf errors at the "Set rs = Mydatabase.OpenRecordset(qryname)
If you run the query within an Access application session, as Gustav suggested, the expression service can handle the UDF in your query.
Here is a quick tested Excel VBA snippet which pulls data from a query which includes a UDF:
Const cstrDbFile As String = "C:\share\Access\Database2.accdb"
Dim objAccess As Object
Dim rs As Object
Dim ws As Worksheet
Dim strSelect As String
Set objAccess = CreateObject("Access.Application")
objAccess.Visible = True ' useful during testing '
objAccess.OpenCurrentDatabase "C:\share\Access\Database2.accdb"
strSelect = "SELECT ID, DummyFunction('a', '', 'c') FROM Dual;"
Set rs = objAccess.CurrentDb.OpenRecordset(strSelect)
If Not rs.EOF Then
Set ws = ThisWorkbook.Sheets("Sheet1")
ws.Range("A1").CopyFromRecordset rs
End If
rs.Close
objAccess.Quit
Most threads say it can't be done,
and they are right.
Your only option is to use automation to open an instance of Access and, within this, run the query.
Well, as noted, most are saying this should not work.
However, if you are 100% sure it was and did work at one time?
You need to set the "sandbox" mode of the JET (now ACE) database engine.
The expression service normally does not allow evaluation of VBA functions as a security setting to prevent SQL injection, or code running outside of Access to allow SQL to run + call VBA functions. At one time, this feature did default to "on", but now the default is set to access only.
You have to set the folder where Access application as trusted. This should allow the VBA functions to now work. so, make sure you set the folder as trusted.
If the location (folder) where your access application is NOT trusted, then Access will use sandbox mode, and VBA in the SQL will not run.
If the location is trusted, THEN access uses the registry setting on your computer.
My bets are that the location is not trusted - so you always get sandbox mode for SQL in Access.
If you are 100% sure that the folder location is set as trusted in Access, and you still receive the errors, then you have to change the registry setting for Access "sandbox" mode.
The setting in the registry is outlined here:
https://support.office.com/en-us/article/Turn-sandbox-mode-on-or-off-to-disable-macros-8CC7BAD8-38C2-4A7A-A604-43E9A7BBC4FB
The registry settings are:
for x32 bit access:
Software\Microsoft\Office\ClickToRun\Registry\Machine\Software\
Wow6432Node\Microsoft\Office\16.0\Access Connectivity Engine\Engines
The above is for Office 2016
14 = 2010
15 = 2013
16 = 2016
The key value for sandbox mode is:
0 to 3
0 Sandbox mode is disabled at all times.
1 Sandbox mode is used for Access, but not for non-Access programs.
2 Sandbox mode is used for non-Access programs, but not for Access.
3 Sandbox mode is used at all times. This is the default value, set when you install Access
So, from above, you want a setting of 0.

VBA to properly access multi user database

I have an Excel Add-In project that I'm working on which has multiple users accessing a database on the server. Currently all the code works and everything processes correctly as long as only one user is accessing the database at a time. I'm using DAO to access the database and passing an SQL string it to retrieve records using the following lines of code
Set db = OpenDatabase(g400DBPath, , True)
Set rs = db.OpenRecordset(sSQL, dbOpenSnapshot)
This creates an issue where if more than one person attempts to access the database at the same time, only the first person is able to access it. I tried changing the recordset line of code to the following
Set rs = db.OpenRecordset(sSQL, dbOpenSnapshot, , dbOptimistic)
but that gives me the following error: Run-time error '3001: Invalid Argument
How would I go about setting the access to the record set so that multiple users can run the report? The users are not updating any information in the database at all, everything is read only.
Your problem is: a snapshot-type recordset is always read-only. Specifying any edit lock options will throw a run-time error, since snapshot-type recordsets don't do locking.
I can't reproduce the behavior, but you can try the following:
Set db = OpenDatabase(g400DBPath, False, False) 'Read-only can lead to exclusive locks since more specific locks are stored in the DB
Set rs = db.OpenRecordset(sSQL, dbOpenSnapshot, dbReadOnly)
Alternatively, you could try using ADO and disconnected recordsets. DAO doesn't offer that functionality as far as I know.

Excel 2013 - Power Query, Background refresh. Method 'Refresh' of object 'WorkbookConnection' failed

I have a workbook which contains several Power Query queries (data source is SQL server) which I am trying to refresh via task scheduler on a remote server. I have a VBscript which opens an Excel file (Refresh Engine) which then refreshes the file containing the queries (Target File). It's structured in this way as each month the target file has it's name changed and a copy is archived as a historic log.
If I log on to the remote server and kick the VBscript off manually the code runs with no error. However, when the script is run via scheduled tasks the Refresh Engine fails with the error recorded as '-2147417848, Method 'Refresh' of object 'WorkbookConnection' failed'.
I've tried changing numerous connection settings without any success. Can anybody help?
The code I am using to refresh the queries within the target workbook is as follows. (Please note various variables have already been created and bound in previous subs).
On Error GoTo ErrLog
For Each Cn In CurStr.Connections
Log.WriteLine (Now() & " Looking at " & Cn.Name)
bg = Cn.OLEDBConnection.BackgroundQuery
Cn.OLEDBConnection.BackgroundQuery = False
Log.WriteLine (Now() & " Refreshing " & Cn.Name)
Cn.Refresh
Cn.OLEDBConnection.BackgroundQuery = bg
Next
Log.WriteLine (Now() & " Refreshing model")
CurStr.Model.Refresh
On Error GoTo 0
Exit Sub

Resources