Getting a value from the update query prompt - vba

When we run a update query we get prompt saying that 'these many records are going to be updated. do you want to continue' is it possible to capture the value in the prompt message to a variable i.e the number of records going to be updated.

If you run the query from code, you can use the records affected property:
Dim db As Database
Set db=CurrentDB
db.Execute "Some SQL here"
db.RecordsAffected
If you use a transaction, you can rollback.

Patrick Cuff proposed this function:
Function RowsChanged(updateQuery As String) As Long
Dim qry As QueryDef
Set qry = CurrentDb.QueryDefs(updateQuery)
qry.Execute
RowsChanged = qry.RecordsAffected
End Function
I don't understand why one would go to the trouble of assigning a QueryDef variable to execute a query when it can be done directly CurrentDB.Execute without initializing (or cleaning up) any object variables.
Obviously, a parameter query is going need to use the QueryDef approach, since you have to assign the values to the parameters before executing it. But without parameters, there's no reason to make it more complicated than necessary. With a generic function like this that isn't set up to handle parameter queries, it seems wrongly designed.
And, of course, it ought also to use dbFailOnError, so that you don't get unexpected results (dbFailOnError works with QueryDef.Execute, just as it does with CurrentDB.Execute). In that case, there really needs to be an error handler.
Rather than write an error handler every time you execute SQL, you can do this, instead. The following function returns the RecordsAffected and will recover properly from errors:
Public Function SQLRun(strSQL As String) As Long
On Error GoTo errHandler
Static db As DAO.Database
If db Is Nothing Then
Set db = CurrentDB
End If
db.Execute strSQL, dbFailOnError
SQLRun = db.RecordsAffected
exitRoutine:
Exit Function
errHandler:
MsgBox Err.Number & ": " & Err.Description, vbExclamation, "Error in SQLRun()"
Resume exitRoutine
End Function
It can also be used to replace DoCmd.RunSQL (you just call it and ignore the return value). In fact, this function was entirely designed for use as a global replacement for DoCmd.RunSQL.

Yes, you can get the number of records updated via the RecordsAffected property:
Function RowsChanged(updateQuery As String) As Long
Dim qry As QueryDef
Set qry = CurrentDb.QueryDefs(updateQuery)
qry.Execute
RowsChanged = qry.RecordsAffected
End Function
You can call this function with the name of your update query to get the number of rows updated:
Dim numRows as long
numRows = RowsChanged("UpdateQuery")

Related

Can I open a recordset using application-level features (user-defined functions, form-based parameters) in Access?

I want users to be able to provide a query they made in the GUI, using a combo box, and then load that query into a recordset to do further processing on it. This fails if the query contains a user-defined function or form-based parameter.
My code looks like this:
Private Sub cmbSelectionColumn_AfterUpdate()
Dim r As DAO.Recordset
Set r = CurrentDb.OpenRecordset("SELECT DISTINCT " & EscapeSQLIdentifier(Me.cmbSelectionColumn.Value) & " FROM " & EscapeSQLIdentifier(Me.cmbSelectionTable.Value))
Do While Not r.EOF
'Do stuff
r.MoveNext
Loop
End Sub
Where cmbSelectionColumn is a user-selected column, and cmbSelectionTable is a user-selected table or query, and EscapeSQLIdentifier is a function that escapes and adds brackets to ensure the field and tablename are safe. This mostly works fine, but it fails in multiple cases, such as involving pass-through queries, user-defined functions, and form-based parameters.
Is there a way I can create a recordset from any query that works in Access, without having to worry about this?
Yes, there is, but you will have to do some trickery.
Forms support these queries just fine. And forms have a .RecordsetClone property that allows us to retrieve the recordset.
To allow us to retrieve the recordset from code, we're going to create a new blank form, and add a module to it (in fact, any form with a module will do). We'll name it frmBlank.
Then, we can adjust the code to use this form to retrieve the recordset.
Private Sub cmbSelectionColumn_AfterUpdate()
Dim r As DAO.Recordset
Dim frm As New Form_frmBlank
frm.RecordSource = "SELECT DISTINCT " & EscapeSQLIdentifier(Me.cmbSelectionColumn.Value) & " FROM " & EscapeSQLIdentifier(Me.cmbSelectionTable.Value)
Set r = frm.RecordsetClone
Do While Not r.EOF
'Do stuff
r.MoveNext
Loop
End Sub
This allows us to retrieve the recordset. The form will not pop up (since we haven't set .Visible to True), and will close once the code is done running since there is no active reference to it. I haven't yet seen any tables or queries that do work in Access, but do not work with this approach, and the performance penalty is minor. It does make for odd code and an odd blank form with blank module that will cause your database to malfunction when deleted.
The following may present an alternative approach to opening DAO recordsets which reference form-based parameters:
Dim db As DAO.Database
Dim pr As DAO.Parameter
Set db = CurrentDb
With db.CreateQueryDef("", "SELECT DISTINCT " & EscapeSQLIdentifier(Me.cmbSelectionColumn.Value) & " FROM " & EscapeSQLIdentifier(Me.cmbSelectionTable.Value))
For Each pr In .Parameters
pr.Value = Eval(pr.Name)
Next pr
With .OpenRecordset
If Not .EOF Then
.MoveFirst
Do Until .EOF
' Do stuff
.MoveNext
Loop
End If
.Close
End With
End With
Here, since references to objects outside of the scope of the query (such as references to form controls) become query parameters whose parameter name matches the original reference, the parameter name is evaluated to yield the value held by the form control, and the parameter value is then updated to the result of this evaluation.

MS Access - Use VBA to automatically select yes for "access was unable to append all the data" error message

I am using MS access where I click a button and it will upload a large number of files to my database. I want the user to be able to click the button and then minimise the application and when they come back all files are uploaded. However for a few of the files I get the error message "access was unable to append all the data to the table". This needs a user input and will not continue unless yes or no is selected.
For all these I always select yes, as I have a validation piece after this steps that will point out any issues.
Is there a way using VBA to build this yes selection into my code?
I already have the following in my code:
DoCmd.SetWarnings = False
DoCmd.RunSQL ...
DoCmd.SetWarnings = True
Thanks in advance,
Here is a function I use to execute sql, it returns the number of records effected by the SQL statement. It uses the 'On Error Resume Next' to handle any errors raised (not the best of coding practices). The function returns a 0 - it failed, if more then that's the number of recs effected by the SQL statement.
Function execSQL(vSQL) As Long
On Error Resume Next
Dim dbF As DAO.Database
Dim lngRecs As Long
DoCmd.SetWarnings False
Set dbF = CurrentDb
dbF.Execute vSQL
lngRecs = dbF.RecordsAffected
execSQL = lngRecs
DoCmd.SetWarnings True
dbF.Close
Set dbF = Nothing
End Function
Failing that, it may be better to use dao to execute the sql instead and then you can error trap properly on that and move on to the next record.
You could do something like this:
Sub MySub()
Dim strSql As String, fileName As String
On Error GoTo Err_MySub
'loop thru all files
strSql = "...'" & fileName & "' ...."
CurrentDb.Execute strSql
'end of loop
Exit Sub
Err_MySub:
Debug.Print fileName & " gives this error:" & Err.Description
End Sub
Press Ctrl-G to show the debug window. Maybe you should do something more clever in the error handler.
Action queries should be run using the Execute() method. No warnings of any kind are raised.
No parameters:
Currentdb().QueryDefs("QueryName").Execute dbFailOnError
With parameters:
With Currentdb().QueryDefs("QueryName")
.Parameters("ParameterName").Value = ParameterValue
.Execute dbFailOnError
End With
The dbFailOnError option will generate a run-time error if the query fails for whatever reason, so make sure your method handles errors. Lastly, if you need to see the records affected, check the RecordsAffected property of the query.

Type Mismatch in VBA SQL expression

I've been out of Access for years, but I've been tasked with a small database function. What I need to do is create a query based on dropdown results, and open that query so that end users can copy/paste what they want from it (or the entire result set).
My code looks like this:
Private Sub btnSubmit_Click()
Dim X As String
Dim Y As String
Dim sSQL As String
Dim MyRs As Recordset
If IsNull(cboReportName.Value) Or IsNull(cboError.Value) Or cboReportName.Value = "" Or cboError.Value = "" Then
MsgBox "One or more of your selections is empty."
Exit Sub
End If
X = cboReportName.Column(2)
Y = cboError.Column(1)
sSQL = "Select * from " & X & " where Error = '" & Y & "'"
Set MyRs = CurrentDb.OpenRecordset(sSQL)
End Sub
I'm getting an error on the Set MyRS line, it's telling me there's a Type Mismatch. Does this have to do with how Access uses Short Text and Long Text? There are NULL results in the query, would that throw this off? Any ideas are appreciated.
It is very unlikely that you get a VBA Type Mismatch error from your query since even if the [Error] column were not Text, it would simply return false when comparing to a string value. (This isn't to discount Erik's comment about multiple query levels causing errors... been there, dealt with that, and I believe this could still be a cause if my answer doesn't help.)
It is more likely that you have referenced an ADO library (from VBA window menu Tools | References...) and placed its priority above the default Access data object libraries. That would cause Dim MyRs As Recordset to interpret this as an ADO recordset, but CurrentDb.OpenRecordset(sSQL) will return a DAO.Recordset.
Update the declaration to
Dim MyRs As DAO.Recordset
or change the priority order of the ADO library in the Tools | References... list.

MS Access VBA: Are Object Variables necessary, when they aren't used, beyond the scope in which they are declared?

I often see code examples, that go through the laborious and possibly confusing process, of declaring, setting, calling, and any associated cleanup, of Object Variables, that otherwise, work fine without the variable - least of all, on a variable that is Private to that function.
Is this at all really necessary, when writing out the full reference, does just as well?
I've heard arguments, that the code is easier to read, and runs faster. The former is highly subjective, and the latter, I have yet to really notice.
Examples;
Public Sub TransactionExample()
Dim wrkSpaceVar As DAO.Workspace
Dim dbVar As DAO.Database
Set wrkSpaceVar = DBEngine(0)
Set dbVar = CurrentDb
On Error GoTo trans_Err
wrkSpaceVar.BeginTrans
dbVar.Execute "SomeActionQuery", dbFailOnError
dbVar.Execute "SomeOtherActionQuery", dbFailOnError
wrkSpaceVar.CommitTrans
trans_Exit:
wrkSpaceVar.Close
Set dbVar = Nothing
Set wrkSpaceVar = Nothing
Exit Sub
trans_Err:
wrkSpaceVar.Rollback
MsgBox "Transaction failed. Error: " & Err.Description
Resume trans_Exit
End Sub
vs
Public Sub TransactionExample()
On Error GoTo trans_Err
DAO.DBEngine.BeginTrans
CurrentDb.Execute "SomeActionQuery", dbFailOnError
CurrentDb.Execute "SomeOtherActionQuery", dbFailOnError
DAO.DBEngine.CommitTrans
Exit Sub
trans_Err:
DAO.DBEngine.Rollback
MsgBox "Transaction failed. Error: " & Err.Description
End Sub
I am not asking about setting variables to "Nothing"; I am asking if they are necessary at all. And for what it's worth, necessary, within the scope, of the examples provided.
In short, no - it isn't necessary to store them in local variables because the references will be the same in both of your code samples. The reason why you would set them to local variables is to avoid necessary object dereferencing calls. In your example, DAO.DBEngine is called three times. Each one is essentially a function call that carries some processing overhead to retrieve the object reference you're working with. In your top example, that function call is only made once and the result is cached in the local variable reference.
If you don't want to declare a local variable, you can do the same thing by wrapping code that uses the same reference in a With block:
Public Sub TransactionExample()
With DBEngine(0)
On Error GoTo trans_Err
.BeginTrans
With CurrentDb
.Execute "SomeActionQuery", dbFailOnError
.Execute "SomeOtherActionQuery", dbFailOnError
End With
.CommitTrans
trans_Exit:
.Close
Exit Sub
trans_Err:
.Rollback
MsgBox "Transaction failed. Error: " & Err.Description
Resume trans_Exit
End With
End Sub
Unless you're doing a ton of work with it (i.e. looping through it extensively), the performance difference is negligible whichever method you use.
Note - setting variables to Nothing is not necessary. The runtime takes care of that when they leave scope.

Run-time Error 2498 for Append and Make Table Queries Created in VBA

I'm getting a 2498 error and really don't understand why. I'm building a string in VBA and am getting the error with the following line of code...
DoCmd.OpenQuery qdfNew, acNormal
It happened with a very long string created to create the query so I simplified the code as much as possible and am still getting the error.
Here's the code...
Option Compare Database
Option Explicit
Dim dbsFootball As Database
Dim strInsertSQL, strSelectSQL, strIntoSQL, strFromSQL, strOrderSQL, strSQL As String
Dim qdfNew As QueryDef
Sub CreateFormattedData()
Set dbsFootball = CurrentDb()
strSelectSQL = ""
strIntoSQL = ""
strFromSQL = ""
strOrderSQL = ""
strSQL = ""
strSelectSQL = "SELECT [tbl_Raw_League_Data].[Season]"
strIntoSQL = "INTO [tbl_Manip Data]"
strFromSQL = "FROM [tbl_Raw_League_Data]" _
+ "LEFT JOIN Referees ON [tbl_Raw_League_Data].[Referee] = Referees.[Referee from Source Data]"
strSQL = strSelectSQL + " " + strIntoSQL + " " + strFromSQL + " " + strOrderSQL
On Error Resume Next ' If query doesn't exist, error won't stop execution
DoCmd.DeleteObject acQuery, "pgmqry_Create Table tbl_Manip"
On Error GoTo 0 ' Reset error handler
Set qdfNew = dbsFootball.CreateQueryDef("pgmqry_Create Table tbl_Manip", strSQL)
DoCmd.OpenQuery qdfNew, acNormal
End Sub
The source field, [tbl_Raw_League_Data].[Season], is a "Short Text" data type (field size = 7).
If I terminate the VBA code and run the query that was created by the code, it works fine with no apparent errors. However, it will never run the query from within the VBA code.
I was originally getting the error 2498 when using "INSERT INTO" for an append query, but realized that the table could as easily be recreated at code execution time.
I'm lost and would sure appreciate some ideas!
Thanks in advance,
Jason
You are passing the querydef object to DoCmd.OpenQuery when it expects a string referencing name of a stored query object. Consider using the querydef's Name property:
DoCmd.OpenQuery qdfNew.Name, acNormal
Alternatively, use .Execute command from database object using the SQL string variable, bypassing any need for querydef:
dbsFootball.Execute strSQL, dbFailOnError
Or with querydef object, as #HansUp suggests, where you simply execute directly since it is an action query:
qdfNew.Execute dbFailOnError
Do note above two options bring up the regular MS Access discussion, of using stored vs VBA string query. While the former is precompiled and runs through query optimizer caching best plan, the latter can have sql dynamically created (structural components that is like SELECT, FROM and JOIN clauses as both can use passed in parameters). From your code snippet consider saving SQL query beforehand without needing to build it in VBA on the fly, and call it with DoCmd.OpenQuery.