Bypass transaction for logging - excel

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.

Related

How can I add parameters to my CosmosDB Data Explorer workbooks?

I have a CosmosDB Data Explorer workbook with a set of functional predefined queries. The workbook offers real benefits by improving efficiency when our organization needs to run some useful queries against when debugging our data/applications. The one draw back is that its prone to failure via user error.
As of now, users need to edit the cells, replacing query values for those that match their needs before running the cells. This is not optimal as it introduces the opportunity that the queries could be broken unintentionally by an unwitting user should they save an erroneous query, hence erasing record of the working query.
One of the queries looks as follows:
%%sql --database DatabaseName --container ContainerName
SELECT c.propertyOne, c.propertyTwo, SUM(c.propertyForSummation)
FROM c
WHERE c.propertyOne = "XYZ123"
AND c.propertyTwo = 1
AND c.timestamp >= '2021-07-15'
AND c.lastUpdatedTimestamp <= '2021-07-16'
GROUP BY c.propertyOne, c.propertyTwo
I'd like to introduce parameter fields {propertyOne, propertyTwo, timestamp, lastUpdatedTimestamp}, so the query would look as below for future users of the workbook to avoid the previously stated scenario. Is that possible?
%%sql --database DatabaseName --container ContainerName
SELECT c.propertyOne, c.propertyTwo, SUM(c.propertyForSummation)
FROM c
WHERE c.propertyOne = #propertyOne
AND c.propertyTwo = #propertyTwo
AND c.timestamp >= #timestamp
AND c.lastUpdatedTimestamp <= #lastUpdatedTimestamp
GROUP BY c.propertyOne, c.propertyTwo
I'm aware how to do this in Azure Data Studio (ADS) workbooks, but unfortunately can not see the option in Data Explorer, and am unaware of the possibility to connect to CosmosDb from ADS
I also posted the question in learn.microsoft.com and it has been confirmed that the feature is not supported at this time (Aug.10th, 2021)

How to access file resource via LotusScript

I want to access and use shared file. I considered these approaches:
1. Store the attachment in Notes Document
2. Store the attachment in database profile
but in both cases I will need to add extra UI design elements (form, view... )
So the easiest way to maintain it - use db Resources (images or files).
I need to create LotusSrcit action that would access the file from db Resources and attach it into new NotesDocument that will be sent as an email.
1 and #2 easy to implement. But how can I access certain file resource from db Resources?
You can access file or image resources using the NotesNoteCollection class. However, since Notes stores the attached files or images in its own, internal format, it is not easy -- if at all possible -- to access those attachments using pure LotusScript.
A workaround that I found useful is to attach the file to an Applet resource, where it can be accessed using pure LotusScript; see sample code below.
Dim sn As New NotesSession()
Dim db as NotesDatabase
Dim nc As NotesNoteCollection
Dim designNote As NotesDocument
Dim eo As NotesEmbeddedObject
Dim noteID$, myFileName$
myFileName = "foobar.txt"
Set db = sn.CurrentDatabase
Set nc = db.CreateNoteCollection(False)
nc.SelectJavaResources = True 'select all Applet resources
nc.SelectionFormula = {$TITLE="} + myFileName + {"} 'assuming the Applet resource has the same name as the file
Call nc.BuildCollection()
noteID = nc.GetFirstNoteID()
Do Until Len(noteID) = 0
Set designNote = db.GetDocumentByID(noteID)
Set eo = designNote.GetAttachment(myFileName)
If Not (eo Is Nothing) Then
'----- your code goes here -----
Exit Do
End If
noteID = nc.GetNextNoteID(noteID)
Loop
I'm making two assumptions here:
The name of the file you want to access is known ("foobar.txt" in the sample code). This is necessary, since otherwise you won't be able to access the attachment.
The Applet resource has the same name as the attached file. Not necessary, but convenient for being able to filter the NotesNoteCollection by the very name of the attached file.

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.

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

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.

Resources