MS Access DAO recordset update not working - ms-access

I made a MS Access DB (old XP version) which used to work seamlessy.
I had to add a routine to "move" some data from a table to another, and wrote a function to do this.
The method I always used was to use dynamic recordsets (or dynasets), but this time it doesn't work.
The flow correctly opens dynasets, find and copy data from one recordset to the other, but when .update is done nothing appears in the original table.
I use DAO 3.60.
Here's the (summarized) code:
On Error Resume Next
Dim rstDoc As Recordset
Dim rstAdd As Recordset
Dim rstDocEmessi As Recordset
Dim rstAddDocEmessi As Recordset
Dim Incassato As Integer
Set rstDoc = CurrentDb.OpenRecordset("Documenti", dbOpenSnapshot)
Set rstDocEmessi = CurrentDb.OpenRecordset("TS_DocumentiEmessi", dbOpenDynaset)
Set rstAdd = CurrentDb.OpenRecordset("Addebiti", dbOpenDynaset)
Set rstAddDocEmessi = CurrentDb.OpenRecordset("TS_Addebiti_DocumentiEmessi", dbOpenDynaset)
numDoc = Forms!TS_SceltaStampa!IdDocumento
With rstDocEmessi
rstDocEmessi.AddNew
rstDocEmessi!IdDocOriginale = rstDoc!IdDocumento
rstDocEmessi!Data = rstDoc!Data
rstDocEmessi![#Fattura] = rstDoc![#Fattura]
...
rstDocEmessi!TS_Opposizione = rstDoc!TS_Opposizione
rstDocEmessi!TS_DataPagamento = rstDoc!TS_DataPagamento
rstDocEmessi!IsIncassato = (IIf(Incassato = vbYes, True, False))
rstDocEmessi!IsImportatoInSospesi = False
rstDocEmessi.Update
rstDocEmessi.Close
' Copia Addebiti
If Not (rstAdd.EOF And rstAdd.BOF) Then
rstAdd.MoveFirst
Do Until rstAdd.EOF = True
If rstAdd!Documento = numDoc Then
rstAddDocEmessi.AddNew
rstAddDocEmessi!IdAddebito = rstAdd!IdAddebito
rstAddDocEmessi!Documento = rstAdd!Documento
...
rstAdd!TS_TipoSpesa
rstAddDocEmessi!Calcola = rstAdd!Calcola
rstAddDocEmessi!Totale = rstAdd!Totale
rstAddDocEmessi.Update
End If
rstAdd.MoveNext
Loop
End If
rstAddDocEmessi.Close
rstAdd.Close
TS_Registra = True`

I have a few suggestions.
Firstly don't use On Error Resume Next unless you are expecting a particular error in a line of code that you are going to explicitly test for and handle in the very next line of code (by testing If Err.Number = ...). You should have an error handling code block and use On Error GoTo ERROR_CODE_BLOCK. If you are going to turn off the error handler for one particular command then you should turn it back on again straight after you've handled the expected error.
Because you've turned off error handling, it could be that your insert statements are failing due to some constraint violation but you're just not seeing this. For error handling I would recommend structuring your code like this:
On Error GoTo PROC_ERR
Dim rstDoc As Recordset
'...
'insert the body of your Procedure here
'...
PROC_EXIT:
'Add any tidying up code that always needs to run. For example, release all your Object variables
Set rstDoc = Nothing
Set rstAdd = Nothing
Set rstDocEmessi = Nothing
Set rstAddDocEmessi = Nothing
Exit Sub
PROC_ERR:
MsgBox "Error " & Err.Number & " - " & Err.Description
Resume PROC_EXIT
End Sub
General code tidying suggestions.
The With rstDocEmessi construct is used to save you a bit of typing. There should be an associated End With somewhere in your code, but I don't see this. I would change this bit of code as follows:
With rstDocEmessi
.AddNew
!IdDocOriginale = rstDoc!IdDocumento
!Data = rstDoc!Data
![#Fattura] = rstDoc![#Fattura]
...
!TS_Opposizione = rstDoc!TS_Opposizione
!TS_DataPagamento = rstDoc!TS_DataPagamento
!IsIncassato = (IIf(Incassato = vbYes, True, False))
!IsImportatoInSospesi = False
.Update
.Close
End With
Finally, the inserts into rstAddDocEmessi could be cleaned up a bit. Rather than opening the whole table of records for rstAdd and then checking each record in turn to see if you need to add a rstAddDocEmessi record, why not just get the relevant records in your rstAdd recordset?
Set rstAdd = CurrentDb.OpenRecordset("Select * From Addebiti " & _
"Where Documento = " & Forms!TS_SceltaStampa!IdDocumento, dbOpenDynaset)
'No need to test for (rstAdd.BOF And rstAdd.EOF), and no need for rstAdd.MoveFirst
'Just go straight into...
Do Until rstAdd.EOF = True
rstAddDocEmessi.AddNew
rstAddDocEmessi!IdAddebito = rstAdd!IdAddebito
rstAddDocEmessi!Documento = rstAdd!Documento
...
rstAddDocEmessi!Calcola = rstAdd!Calcola
rstAddDocEmessi!Totale = rstAdd!Totale
rstAddDocEmessi.Update
rstAdd.MoveNext
Loop

Related

Through VBA use existing database if open, otherwise open new one then close after

I have the below code which will use an already open Access database if it's there, if not it will use a new Access instance. This is working fine.
Sub DoStuff()
Dim AccApp As Application
Set AccApp = GetObject("C:\DatabaseName.accdb")
--Do Something e.g.
Debug.Print AccApp.CurrentDb.Name
Set AccApp = Nothing
End Sub
What I want to do after this is to leave the database open if it was already open but close it if it wasn't to start with. How can I tell whether it was there or not to start with.
I don't want to test for laccdb files as these can remain after Access closing unexpectedly.
Any ideas most appreciated.
I managed to crowbar another function I had for another purpose into this which solves the issue:
Function bDatabaseOpen(strDBPath As String) As Boolean
Dim objWMIService As Object, colProcessList As Object, objProcess As Object
bDatabaseOpen = False
Set objWMIService = GetObject("winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2")
Set colProcessList = objWMIService.ExecQuery("Select * from Win32_Process Where Name = 'MSACCESS.EXE'")
For Each objProcess In colProcessList
If Not (IsNull(objProcess.commandline)) Then
If objProcess.commandline Like "*" & strDBPath & "*" Then
bDatabaseOpen = True
End If
End If
Next
Set objProcess = Nothing
Set objWMIService = Nothing
Set colProcessList = Nothing
End Function
I can test prior to calling my original code if it's already open and then afterwards deal with it appropriately.
IMO the easiest way is to try to delete the .laccdb file. If it's there and can't be deleted (because it is locked), the Db is in use.
Const TheDB = "C:\DatabaseName.accdb"
Dim DbWasOpen As Boolean
Dim slaccdb As String
slaccdb = Replace(TheDB, ".accdb", ".laccdb")
DbWasOpen = False
If Dir$(slaccdb) <> "" Then
On Error Resume Next
' Try to delete .laccdb
Kill slaccdb
' If that fails, the database is in use
If Err.Number <> 0 Then
DbWasOpen = True
End If
On Error GoTo 0
End If

Access VB: Validating blank values and incorrect values in a textbox

I have a textbox where the user inputs the ID of a row to be updated/edited. I got it to work properly when they enter an ID that exists, but I get an error when they input an ID that doesn't exist or when they leave it blank.
Goals:
Allow blank - if the user leaves the field blank, then I need to wipe the form and simply let the user continue (no message box needed).
Warn user when ID is not valid with a message box.
Current Code
Private Sub Text135_LostFocus()
If Me.Text135 = Nothing Then
MsgBox "Nothing entered into the ID field. Query will not run"
GoTo Last_Line:
Else
sql = "SELECT * FROM tbl_Downtime WHERE ID = " & Forms![DTForm]![Text135] & ";"
Set db = CurrentDb
Set rs = db.OpenRecordset(sql)
Me.Text126.Value = rs!production_date
Me.Text144.Value = rs!shift
Me.Text116.Value = rs!job
Me.Text118.Value = rs!suffix
Me.Text121.Value = rs!reason
Me.Text123.Value = rs!downtime_minutes
Me.Text4.Value = rs!people_missing
Me.Text128.Value = rs!comment
Set db = Nothing
Set rs = Nothing
Last_Line:
End If
End Sub
Completely stop executing the sub when the field is blank:
If Nz(Me.Text135) = "" Then 'Text135 is null or empty
Exit Sub
End If
But if the code in your question is your actual code (and not just a shortened example), you need neither the Exit Sub nor the GoTo Last_Line: part, because after the message box, the code execution will jump to the End If anyway.
Check whether the Recordset contains any rows:
Set db = CurrentDb
Set rs = db.OpenRecordset(sql)
If rs.EOF Then
'rs.EOF is True when there are no rows
MsgBox "ID is not valid"
Else
'do stuff
End If
Set db = Nothing
Set rs = Nothing

How to properly use Seek in DAO database

I'm trying to search for currently selected item in my listbox control on my table.
In my listbox control after update event, I have this code
Private Sub lst_MainList_AfterUpdate()
Dim theDB As DAO.Database
Dim theProposalsTable As DAO.Recordset
Set theDB = CurrentDb
Set theProposalsTable = theDB.OpenRecordset("tbl_PROPOSAL", dbOpenDynaset)
theSeeker theProposalsTable, Me.lst_PPpg_MainList.Value
End Sub
Then I have a sub on my Module1 with this code. I got this from an example code # https://msdn.microsoft.com/en-us/library/office/ff836416.aspx
Sub theSeeker(ByRef rstTemp As Recordset, intSeek As Integer)
Dim theBookmark As Variant
Dim theMessage As String
With rstTemp
' Store current record location.
theBookmark = .Bookmark
.Seek "=", intSeek
' If Seek method fails, notify user and return to the
' last current record.
If .NoMatch Then
theMessage = "Not found! Returning to current record." & vbCr & vbCr & "NoMatch = " & .NoMatch
MsgBox theMessage
.Bookmark = theBookmark
End If
End With
End Sub
I am getting Runtime Error 3251 Operation is not supported for this type of object.
When I hit Debug, it highlights .Seek "=", intSeek
In this point from the linked page ...
Locates the record in an indexed table-type Recordset object
... "table-type Recordset" means you must use dbOpenTable instead of dbOpenDynaset with OpenRecordset()
That point is critical. If you can't open the table with dbOpenTable, you can't use Seek. And dbOpenTable can only be used with native Access tables contained in the current database. It can not be used with any kind of linked table.
So if dbOpenTable is compatible with tbl_PROPOSAL, this change will eliminate the first error ...
'Set theProposalsTable = theDB.OpenRecordset("tbl_PROPOSAL", dbOpenDynaset)
Set theProposalsTable = theDB.OpenRecordset("tbl_PROPOSAL", dbOpenTable)
If that does work, the next error will be #3019, "Operation invalid without a current index." That happens because you must set the controlling index before calling Seek ...
With rstTemp
' Store current record location.
theBookmark = .Bookmark
' Set the index.
.Index = "PrimaryKey" '<- use your index name here
.Seek "=", intSeek
If you need to list the names of your table's indexes, you can examine its TableDef.Indexes collection. Here is an Immediate window example with a table in my database ...
set db = CurrentDb
for each idx in db.TableDefs("tblFoo").Indexes : ? idx.name : next
id
PrimaryKey
You can't use the Seek method on a linked table because you can't open linked tables as table-type Recordset objects...
However, you can use the Seek method if you use the OpenDatabase method to open the backend database.
So instead of:
Set theDB = CurrentDb()
Do this:
Set theDB = OpenDatabase("full path to backend database")
Set theProposalsTable = theDB.OpenRecordset("tbl_PROPOSAL", dbOpenTable)
Allow me to combine these two old answers. Yes, you get an error when opening a linked table as dbOpenTable because that is only supported on tables local to the DB object you are working on. Like David pointed out, you can open the linked backend as a DB object and use that.
I'm using a reliable table in my back-end called "Settings". If you don't have a reliable table you can use to pull the back end you can pass in the table name as an argument.
I'm storing the backend object once I have a handle on it so we can call against it rapidly throughout our code without recreating the object.
Private thisBEDB As Database
'#Description("This allows us to call directly against linked tables.")
Public Function thisBackend() As Database
' For MS-ACCESS table
If (thisBEDB Is Nothing) Then
With DBEngine
Set thisBEDB = .OpenDatabase(Mid(CurrentDB.TableDefs("Settings").Connect, 11), False, False, "")
End With
End If
Set thisBackend = thisBEDB
End Function
Now we can use the backend handle to make the code in your example work as expected.
Private Sub lst_MainList_AfterUpdate()
Dim theDB As DAO.Database
Dim theProposalsTable As DAO.Recordset
Set theDB = CurrentDb
Set theProposalsTable = thisBackend.OpenRecordset("tbl_PROPOSAL", dbOpenTable)
theSeeker theProposalsTable, Me.lst_PPpg_MainList.Value
End Sub
Sub theSeeker(ByRef rstTemp As Recordset, intSeek As Integer)
Dim theBookmark As Variant
Dim theMessage As String
With rstTemp
.Index = "PrimaryKey"
' Store current record location.
theBookmark = .Bookmark
.Seek "=", intSeek
' If Seek method fails, notify user and return to the
' last current record.
If .NoMatch Then
theMessage = "Not found! Returning to current record." & vbCr & vbCr & "NoMatch = " & .NoMatch
MsgBox theMessage
.Bookmark = theBookmark
End If
End With
End Sub

get the name of a query from an access table, then run the query

I have an access db that pulls volumes from a table of exceptions. Each volume has an ID. I've created queries to pull details, for all possible volumes, and saved each one with the same name as each volume ID. Each time the volume exceptions are pulled into this db, the volume IDs can change. So, there is a query that runs that updates the volume table with the new IDs.
Unless you know a way to do this with a query, I need to write Access VBA code that will loop through the volume table, identify the name of each query and then run those queries until it reaches the end of the table. For example, the code needs to look at the first record in the volume table, say it is 1040. This is the name of the query that needs to run. The code then needs to find the query named 1040 and run it. It is a make table query.
The table name is FacilityVolume and it has one field named Volume. The value in the field is shorttext format even though it is numeric.
I've tried a couple of different things. Here is my latest try.
Dim db as Database
Dim vol as Recordset
Dim code as QueryDef
Set db = CurrentDb()
Set vol = db.OpenRecordset("FacilityVolume")
Set Volume = vol.Fields("Volume")
Vol.MoveFirst
Do Until vol.EOF = True
If QueryDef.Name = Volume Then
DoCmd.OpenQuery
Else MsgBox("The query does not exist")
vol.MoveNext
Loop
End Sub
I've searched the internet for a few days and can't find any reference to this particular code. I'm sure other users would like to know how to do this. I'm a novice and still learning VBA so any help you can provide is greatly appreciated.
Your code will loop through, even if you found your query and you do not pass the Query-Name to the OpenQuery command... This won't work...
The collection CurrentDb.QueryDefs knows all existing queries, but there is no "Exists" or "Contains" method.
So: The approach would be a loop (as you tried it) or an Error handling.
It's quite a time ago since I've coded with VBA, but I think you could try:
On Error Resume Next
DoCmd.OpenQuery "YourQueryName"
If Err Then
MsgBox("The query does not exist!")
Err.Clear
End If
On Error Goto 0
I recommend using full DAO in VBA to accomplish your goal. DoCmd.OpenQuery is really a VBA function that mimics the Macro RunQuery action. You don't get much control or true error handling capability.
Here is a complete code function that
Gives you an example of how to select all or some records from your table that lists the queries, including the ability to only select "Active" records, and even sort them in a particular execution sequence
Handles the instances where the query name in your table does not exist
Allows you to display a message about any errors that occur
Allows you to return an exit code to the calling procedure so that you can possibly act on the results of running these queries (such as choosing not to do the next step in your code if this function encounters an error of any kind (returns a non-zero value)
Here is the code. You will need to modify the SQL statement for your correct table name and field names, but this should be a good example to get you on your way.
Public Function lsProcessQuerySet() As Long
On Error GoTo Err_PROC
Dim ErrMsg As String
Dim db As DAO.Database
Dim rstEdits As DAO.Recordset
Dim qdf As DAO.QueryDef
Dim mssql As String
Dim ReturnCode As Long
Set db = CurrentDb()
'============================
'Select the list of Queries you want to process
'============================
mssql = "SELECT tblQueryList.ID, tblQueryList.QueryName, "
mssql = mssql & "tblQueryList.QueryShortDesc "
mssql = mssql & "FROM tblQueryList "
mssql = mssql & "WHERE tblQueryList.QueryActive = True "
mssql = mssql & "ORDER BY tblQueryList.SortOrder;"
Set rstEdits = db.OpenRecordset(mssql, dbOpenDynaset)
DoCmd.Hourglass True
'============================
'Execute each query, allowing processing to continue
'if the query does not exist (an error occurs)
'============================
Do While Not rstEdits.EOF
Set qdf = db.QueryDefs(rstEdits("QueryName"))
qdf.Execute dbSeeChanges
ResumeNextEdit:
rstEdits.MoveNext
Loop
rstEdits.Close
Exit_PROC:
lsProcessQuerySet = ReturnCode
Set qdf = Nothing
Set rstEdits = Nothing
db.Close
Set db = Nothing
DoCmd.Hourglass False
Exit Function
Err_PROC:
Select Case Err.Number
Case 3265 'Item Not Found in this Collection
ReturnCode = Err.Number
ErrMsg = "Query Not Found:" & vbCrLf & vbCrLf
ErrMsg = ErrMsg & rstEdits("QueryName")
DoCmd.Hourglass False
MsgBox ErrMsg, vbOKOnly + vbCritical, "Function lsProcessQuerySet"
Resume ResumeNextEdit
Case Else
ReturnCode = Err.Number
ErrMsg = "Error: " & Err.Number & vbCrLf
ErrMsg = ErrMsg & Err.Description
DoCmd.Hourglass False
MsgBox ErrMsg, vbOKOnly + vbCritical, "Function lsProcessQuerySet"
Resume Exit_PROC
End Select
End Function
The answer of #Shnugo is already good. Just to give you a complete VBA function, this should be working for you.
Public Sub MySub()
On Error GoTo err_mySub
Dim db as Database
Dim vol as Recordset
Set db = CurrentDb()
Set vol = db.OpenRecordset("FacilityVolume", dbOpenDynaset) ' I don't know if you want to run all queries of the table "FacilityVolume".
'So maybe you could also use something like "SELECT Volume FROM FacilityVolume WHERE Volume LIKE ""*10*"""
Vol.MoveFirst
Do Until vol.EOF = True
DoCmd.OpenQuery vol!Volume
vol.MoveNext
Loop
exit_MySub:
Exit Sub
err_MySub:
If Err.Number = 7874 Then
MsgBox "The Query """ & Vol!Volume & """ wasn't found."
Resume Next
Else
MsgBox Err.Description
Resume exit_MySub
End If
End Sub

Access query need to bypass "enter parameter value" error with a msg box saying field not found

I have a simple query tied to a command button that shows a summary of the values in a particular field. It's running on a table that changes with each use of the database, so sometimes the table will contain this field and sometimes it won't. When the field (called Language) is not in the file, the user clicks the command button and gets the "Enter Parameter Value" message box. If they hit cancel they then get my message box explaining the field is not present in the file. I would like to bypass the "Enter Parameter Value" and go straight to the message if the field is not found. Here is my code:
Private Sub LangCount_Click()
DoCmd.SetWarnings False
On Error GoTo Err_LangCount_Click
Dim stDocName As String
stDocName = "LanguageCount"
DoCmd.OpenQuery stDocName, acNormal, acEdit
Err_LangCount_Click:
MsgBox "No Language field found in Scrubbed file"
Exit_LangCount_Click:
Exit Sub
DoCmd.SetWarnings True
End Sub
You can attempt to open a recordset based on the query before you run the query:
Set rs = CurrentDb.QueryDefs("query1").OpenRecordset
This will go straight to the error coding if anything is wrong with the query.
Alternatively, if it is always the language field and always in the same table, you can:
sSQL = "select language from table1 where 1=2"
CurrentDb.OpenRecordset sSQL
This will also fail and go to your error coding, but if it does not fail, you will have a much smaller recordset, one with zero records.
You can easily enough get a list of fields in a table with ADO Schemas:
Dim cn As Object ''ADODB.Connection
Dim i As Integer, msg As String
Set cn = CurrentProject.Connection
Set rs = cn.OpenSchema(adSchemaColumns, Array(Null, Null, "Scrubbed"))
While Not rs.EOF
i = i + 1
msg = msg & rs!COLUMN_NAME & vbCrLf
rs.MoveNext
Wend
msg = "Fields: " & i & vbCrLf & msg
MsgBox msg
More info: http://support.microsoft.com/kb/186246
You have a command button named LangCount. It's click event has to deal with the possibility that a field named Language is not present in your Scrubbed table.
So then consider why a user should be able to click that command button when the Language field is not present. When the field is not present, you know the OpenQuery won't work (right?) ... so just disable the command button.
See if the following approach points you to something useful.
Private Sub Form_Load()
Me.LangCount.Enabled = FieldExists("Language", "Scrubbed")
End Sub
That could work if the structure of Scrubbed doesn't change after your form is opened. If the form also includes an option to revise Scrubbed structure, update LangCount.Enabled from that operation.
Here is a quick & dirty (minimally tested, no error handling) FieldExists() function to get you started.
Public Function FieldExists(ByVal pField As String, _
ByVal pTable As String) As Boolean
Dim blnReturn As Boolean
Dim db As DAO.Database
Dim fld As DAO.Field
Dim tdf As DAO.TableDef
Set db = CurrentDb
' next line will throw error #3265 (Item not found in this collection) '
' if table named by pTable does not exist in current database '
Set tdf = db.TableDefs(pTable)
'next line is not actually needed '
blnReturn = False
For Each fld In tdf.Fields
If fld.Name = pField Then
blnReturn = True
Exit For
End If
Next fld
Set fld = Nothing
Set tdf = Nothing
Set db = Nothing
FieldExists = blnReturn
End Function