I have been searching for days for ways on how to implement an audit trail in my access 2010 database. There are plenty of solutions out there that work great when the form is bound, but I have several forms that are unbound and perform certain critical functions I wish to have an audit trail on (they are unbound due to having to edit different tables depending on user input, functions performed through VB and SQL scripting, so binding them to a table would not work). But there seems to be no easy solutions on this type of auditing without doing weeks and weeks worth of custom coding. Does anyone have any ideas on how to do this? Is there a way to audit all activity without having to bind a form? Can't I just have code that monitors a table's changes without having to go though code on the back side of the forms?
I have recently done this!
Each form has code to write changes to a table.
The Audit Trail gets a bit tricky when you lose Screen.ActiveForm.Controls as the reference - which happens if you use a Navigation Form.
It is also using Sharepoint lists so I found that none of the published methods were available.
I (often) use a form in the middle as a display layer and I find it has to fire the Form_Load code in the next forms down the line as well.
Once they are open they need to be self sustaining.
Module Variable;
Dim Deleted() As Variant
Private Sub Form_BeforeUpdate(Cancel As Integer)
'Audit Trail - New Record, Edit Record
Dim rst As Recordset
Dim ctl As Control
Dim strSql As String
Dim strTbl As String
Dim strSub As String
strSub = Me.Caption & " - BeforeUpdate"
If TempVars.Item("AppErrOn") Then
On Error Resume Next 'On Error GoTo Err_Handler
Else
On Error GoTo 0
End If
strTbl = "tbl" & TrimL(Me.Caption, 6)
strSql = "SELECT * FROM tblzzAuditTrail WHERE DateTimeMS = #" & GetTimeUTC & "#;"
Set rst = dbLocal.OpenRecordset(strSql)
For Each ctl In Me.Detail.Controls
If ctl.ControlType = acTextBox Or ctl.ControlType = acComboBox Then
If ctl.Name <> "DateUpdated" Then
If Nz(ctl.Value) <> Nz(ctl.OldValue) Then
If Me.NewRecord Then
With rst
.AddNew
!DateTimeMS = GetTimeUTC()
!UserID = TempVars.Item("CurrentUserID")
!ClientID = TempVars.Item("frmClientOpenID")
!RecordID = Me.Text26
!ActionID = 1
!TableName = strTbl
!FieldName = ctl.ControlSource
!NewValue = ctl.Value
.Update
End With
Else
With rst
.AddNew
!DateTimeMS = GetTimeUTC()
!UserID = TempVars.Item("CurrentUserID")
!ClientID = TempVars.Item("frmClientOpenID")
!RecordID = Me.Text26
!ActionID = 2
!TableName = strTbl
!FieldName = ctl.ControlSource
!NewValue = ctl.Value
!OldValue = ctl.OldValue
.Update
End With
End If
End If
End If
End If
Next ctl
rst.Close
Set rst = Nothing
Exit Sub
Err_Handler:
Select Case Err.Number
Case 3265
Resume Next 'Item not found in recordset
Case Else
'Unexpected Error
MsgBox "The following error has occurred" & vbCrLf & vbCrLf & "Error Number: " & _
Err.Number & vbCrLf & "Error Source: " & strSub & vbCrLf & "Error Description: " & _
Err.Description, vbExclamation, "An Error has Occured!"
End Select
rst.Close
Set rst = Nothing
End Sub
Private Sub Form_Delete(Cancel As Integer)
Dim ctl As Control
Dim i As Integer
Dim strTbl As String
strTbl = "tbl" & TrimL(Me.Caption, 6)
ReDim Deleted(3, 1)
For Each ctl In Me.Detail.Controls
If ctl.ControlType <> acLabel Then
' Debug.Print .Name
If ctl.Name <> "State" And ctl.Name <> "Pcode" Then
If Nz(ctl.Value) <> "" Then
Deleted(0, i) = ctl.ControlSource
Deleted(1, i) = ctl.Value
Deleted(2, i) = Me.Text26
' Debug.Print Deleted(0, i) & ", " & Deleted(1, i)
i = i + 1
ReDim Preserve Deleted(3, i)
End If
End If
End If
Next ctl
End Sub
Private Sub Form_AfterDelConfirm(Status As Integer)
Dim rst As Recordset
Dim ctl As Control
Dim strSql As String
Dim strTbl As String
Dim i As Integer
Dim strSub As String
strSub = Me.Caption & " - AfterDelConfirm"
If TempVars.Item("AppErrOn") Then
On Error Resume Next 'On Error GoTo Err_Handler
Else
On Error GoTo 0
End If
strTbl = "tbl" & TrimL(Me.Caption, 6)
strSql = "SELECT * FROM tblzzAuditTrail WHERE DateTimeMS = #" & GetTimeUTC() & "#;"
Set rst = dbLocal.OpenRecordset(strSql)
'Audit Trail - Deleted Record
If Status = acDeleteOK Then
For i = 0 To UBound(Deleted, 2) - 1
With rst
.AddNew
!DateTimeMS = GetTimeUTC()
!UserID = TempVars.Item("CurrentUserID")
!ClientID = TempVars.Item("frmClientOpenID")
!RecordID = Deleted(2, i)
!ActionID = 3
!TableName = strTbl
!FieldName = Deleted(0, i)
!NewValue = Deleted(1, i)
.Update
End With
Next i
End If
rst.Close
Set rst = Nothing
Exit Sub
Err_Handler:
Select Case Err.Number
Case 3265
Resume Next 'Item not found in recordset
Case Else
'Unexpected Error
MsgBox "The following error has occurred" & vbCrLf & vbCrLf & "Error Number: " & _
Err.Number & vbCrLf & "Error Source: " & strSub & vbCrLf & "Error Description: " & _
Err.Description, vbExclamation, "An Error has Occured!"
End Select
rst.Close
Set rst = Nothing
End Sub
Related
I developed an access database to log jobs throughout a production process. Every record has an order, machine, start time, end time among other characteristics of the job. When an order is logged, it is saved in the database along with the machine name, start time and job status (running or idle). When the order is completed, the record is searched using a recordset and "end time" is saved. If the machine is not being utilized, like between shifts, the machine should have an "idle" status.
The purpose of OpenRecMassUpdate is to add an 'end time' to all the incomplete records (those with an order, start time but without end time). This code is used at the end of shift so that all the records could be closed with one click.
After executing this subroutine, the machines that were assigned to an order are now without a status. As a result, I needed another subroutine to add "idle" statuses to all these machines. That is the purpose of MassIdleUpdate. It creates an idle record for every machine that was previously used and status closed using OpenRecMassUpdate.
The problem I am facing is that MassIdleUpdate creates multiple records at random times. When I run analysis on the database, I found some records that were created 3, 4 or more times.
Option Compare Database
Dim dbsn As DAO.Database
Dim rstn As DAO.Recordset
Dim SQLqueryn As String
Dim recordcount As Integer
Dim tempstat As String
Dim stat1 As Integer
Public Sub OpenRecMassUpdate()
On Error GoTo ErrorHandler
recordcount = 1
tempstat = "Idle"
stat1 = 0
Set dbsn = CurrentDb
SQLqueryn = "SELECT * FROM kettleLog WHERE KettleStatus <> """ & tempstat & _
""" And KettleLogic = " & stat1
Set rstn = dbsn.OpenRecordset(SQLqueryn)
With rstn
If Not .BOF And Not .EOF Then
.MoveLast
.MoveFirst
While (Not .EOF)
.Edit
.Fields("KettleFinish") = Now()
.Fields("KettleLogic") = -1
.Fields("EndOfShift") = 1
.Update
.MoveNext
recordcount = recordcount + 1
Wend
MsgBox recordcount - 1 & " records were updated as a result of the end of the shift"
recordcount = 1
Else
End If
.Close
End With
dbsn.Close
ExitSub:
Set dbsn = Nothing
Set rstn = Nothing
Exit Sub
ErrorHandler:
MsgBox "Error #: " & Err.Number & vbCrLf & vbCrLf & Err.Description
Resume ExitSub
End Sub
Public Sub MassIdleUpdate()
Dim tempKettle As String
On Error GoTo ErrorHandler
Set dbsn = CurrentDb
SQLqueryn = "SELECT * FROM kettleLog WHERE EndOfShift = 1"
Set rstn = dbsn.OpenRecordset(SQLqueryn)
With rstn
If Not .BOF And Not .EOF Then
.MoveLast
.MoveFirst
For i = 1 To FindRecordCount(SQLqueryn)
tempKettle = .Fields("Kettle")
.Edit
.Fields("EndOfShift") = 3
.Update
.AddNew
.Fields("Kettle") = tempKettle
.Fields("KettleStatus") = "Idle"
.Fields("WorkOrder") = 0
.Fields("KettleStart") = Now()
.Fields("KettleLogic") = 0
.Fields("EndOfShift") = 2
.Update
.MoveNext
Next
End If
.Close
End With
tempKetlle = ""
dbsn.Close
i = 1
ExitSub:
Set dbsn = Nothing
Set rstn = Nothing
Exit Sub
ErrorHandler:
MsgBox "Error #: " & Err.Number & vbCrLf & vbCrLf & Err.Description
Resume ExitSub
End Sub
Instead of looping through all your records counting them and setting the values individually, do it all in one shot. An RDBMS (even Access) is designed for this kind of bulk update.
Public Sub OpenRecMassUpdate()
On Error GoTo ErrorHandler
Dim tempStat As String
tempStat = "Idle"
Dim stat1 As Long
stat1 = 0
Set dbsn = CurrentDb
Dim timeStamp As Date
timeStamp = Now()
SQLqueryn = "UPDATE KettleLog " & _
" SET KettleFinish = #" & timeStamp & "#, " & _
" KettleLogic = -1, " & _
" EndOfShift = 1 " & _
" WHERE KettleStatus <> """ & tempStat & """" & _
" AND KettleLogic = 0"
Set rstn = dbsn.OpenRecordset(SQLqueryn)
rstn.Close
SQLqueryn = "SELECT Count(*) " & _
" FROM KettleFinish " & _
" WHERE KettleFinish = #" & timeStamp & #", " & _
" AND KettleLogic = -1 " & _
" AND EndOfShift = 1"
Set rstn = dbsn.OpenRecordset(SQLqueryn)
If Not rstn.BOF And Not rstn.EOF Then
rstn.MoveLast
Dim recordcount As Long
recordcount = rstn.recordcount
End If
MsgBox recordcount & " records were updated as a result of the end of the shift"
rstn.Close
dbsn.Close
ExitSub:
Exit Sub
ErrorHandler:
MsgBox "Error #: " & Err.Number & vbCrLf & vbCrLf & Err.Description
Resume ExitSub
End Sub
Note: I'm used to ADO syntax, not DAO, so there might be a minor tweak or two needed, but this should get you started
This will do what your OpenRecMassUpdate() procedure was doing in precisely 2 SQL queries instead of that time consuming loop.
You can also do the same thing to Sub MassIdleUpdate().
As a matter of fact, with a little creativity, you could probably combine the two of them into one, though keeping them separate reduces complexity, improves readability and, thus, future maintainability.
Thanks to #Freeman who guided me in the right direction. Here's my solution to the issue I had. The code has been tested in my sandbox using different scenarios and it works.
Public Sub OpenRecMassUpdate1()
On Error GoTo ErrorHandler
Dim tempStat As String
tempStat = "Idle"
Dim stat1 As Long
stat1 = 0
Set dbsn = CurrentDb
Dim timeStamp As Date
timeStamp = Now()
SQLqueryn = "UPDATE KettleLog " & _
" SET KettleFinish = #" & timeStamp & "#, " & _
" KettleLogic = -1, " & _
" EndOfShift = 1 " & _
" WHERE KettleStatus <> """ & tempStat & """" & _
" AND KettleLogic = 0"
dbsn.Execute SQLqueryn, dbFailOnError
SQLqueryn = "SELECT Count(*) " & _
"AS RecCount " & _
" FROM KettleLog " & _
" WHERE KettleLogic = -1 " & _
" AND EndOfShift = 1"
Set rstn = dbsn.OpenRecordset(SQLqueryn)
If Not rstn.BOF And Not rstn.EOF Then
Dim recordcount As Long
recordcount = rstn![RecCount]
End If
MsgBox recordcount & " records were updated as a result of the end of the shift"
rstn.Close
dbsn.Close
ExitSub:
Exit Sub
ErrorHandler:
MsgBox "Error #: " & Err.Number & vbCrLf & vbCrLf & Err.Description
Resume ExitSub
End Sub
Public Sub MassIdleUpdate1()
On Error GoTo ErrorHandler
Dim TempKettle As String
Set dbsn = CurrentDb
SQLqueryn = "SELECT * " & _
" FROM KettleLog " & _
" WHERE EndOfShift = 1"
Set rstn = dbsn.OpenRecordset(SQLqueryn)
rstn.MoveLast
Dim rcrdcnt As Long
rcrdcnt = rstn.recordcount
ReDim machs(rcrdcnt) As String
'MsgBox rcrdcnt
rstn.MoveFirst
If Not rstn.BOF And Not rstn.EOF Then
For i = 0 To rcrdcnt - 1
machs(i) = rstn.Fields("Kettle")
rstn.MoveNext
Next
End If
SQLqueryn = "UPDATE KettleLog " & _
" SET EndOfShift = 3 " & _
" WHERE EndOfShift = 1 "
dbsn.Execute SQLqueryn, dbFailOnError
For j = 0 To rcrdcnt
SQLqueryn = "INSERT INTO KettleLog (Kettle, KettleStatus, WorkOrder, KettleStart,
KettleLogic, EndOfShift) " & _
" VALUES ( '" & machs(j) & "' , 'Idle', '0', #" & Now() & "#, '0', '2')"
MsgBox SQLqueryn
dbsn.Execute SQLqueryn, dbFailOnError
machs(j) = ""
Next
rstn.Close
dbsn.Close
ExitSub:
Exit Sub
ErrorHandler:
MsgBox "Error #: " & Err.Number & vbCrLf & vbCrLf & Err.Description
Resume ExitSub
End Sub
The issue I am having is connect my Save_Record (suppose to save and push info) & btnExit (saves & exits) to push the information inputted on the form to be pushed out to the web database. The only way it will push the information is if I exit out, come back in and make a small change then it will push it out.
How can I achieve this without doing this?
Option Compare Database
Option Explicit
'-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**
Private Sub btnCancel_Click() '==**== undo changes
On Error Resume Next
RunCommand acCmdUndo
Err = 0
End Sub
'-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**
Private Sub btnExit_Click()
DoCmd.Close
End Sub
Private Sub cmdViewPDF_Click()
On Error GoTo Err_cmdViewPDF_Click
Dim stDocName As String
Dim stLinkCriteria As String
stDocName = "frm_Images"
DoCmd.OpenForm stDocName, , , stLinkCriteria
Exit_cmdViewPDF_Click:
Exit Sub
Err_cmdViewPDF_Click:
MsgBox Err.Description
Resume Exit_cmdViewPDF_Click
End Sub
'-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**
Private Sub Form_AfterUpdate()
'DoCmd.Save
End Sub
Private Sub Form_BeforeUpdate(Cancel As Integer)
[DT_MOD] = Now()
[MstrWinUser] = Forms!frFilter!txtWinUser
End Sub
'-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**
Private Sub MOREoperInfo_Click()
Dim DocName As String
Dim LinkCriteria As String
DoCmd.Save
DocName = "frmsearch"
DoCmd.OpenForm DocName, , , LinkCriteria
End Sub
'-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**-**
'Save record and close
Private Sub Save_Record_Click()
On Error GoTo Err_Save_Record_Click
[DT_MOD] = Now()
[MstrWinUser] = Forms!frmFilter!txtWinUser
'MsgBox "Saving frm_View"
'Save the record
'If there was a record already in txt box, we will assume it has been created in web
If Trim(txtEOCIncident.Value & vbNullString) = vbNullString Then
Me.txtEOCPushFlag = "0"
RunCommand acCmdSaveRecord
DoCmd.Save
Else
'SET EOC PUSH FLAG
Me.txtEOCPushFlag = "1"
RunCommand acCmdSaveRecord
DoCmd.Save
'TRY TO UPDATE WEB DATA
If (pushEOCData(Me.ID)) Then
'CLEAR PUSH FLAG AND SET PUSHDATE
Me.txtEOCPushFlag = "0"
Me.txtEOCPushDate = Now()
RunCommand acCmdSaveRecord
DoCmd.Save
End If
End If
Exit_Save_Record_Click:
Exit Sub
Err_Save_Record_Click:
MsgBox Error$
Resume Exit_Save_Record_Click
End Sub
'*** 08-20-13 New code. Creates PDF of the Report.
Private Sub btnSaveasPDF_Click()
On Error GoTo Err_btnSaveasPDF_Click
Dim db As Database, rs As Recordset
Dim vFN As String, vPATH As String, vFile As String
Dim blRet As Boolean
Dim stDocName As String
Dim strConstantName As String
stDocName = "rpt_Out"
'added 2014-09-02, MAB
strConstantName = "REPORT_FOLDER"
vPATH = Trim(DLookup("[ConstantValue]", "dbo_Constants", "[ConstantName] = '" & [strConstantName] & "'"))
If Forms!frm_View![DIST] = "1" Or Forms!frm_View![DIST] = "2" Or Forms!frm_View![DIST] = "3" Or Forms!frm_View![DIST] = "4" Then
vFN = Forms!frm_View![ID] & "_" & Forms!frm_View![Typeofreport] & "_" & Month(Now()) & "_" & Day(Now()) & "_" & Year(Now()) & "_" & Hour(Now()) & Minute(Now()) & ".pdf"
'vPATH = "\\...\...\Reports\"
vFile = vPATH & vFN
Else
MsgBox "Please enter your number." & vbCrLf & "It must be a single digit.", vbOKOnly Or vbInformation, "*****"
End If
'write to IMAGES
Set db = CurrentDb
Set rs = db.OpenRecordset("IMAGES")
rs.AddNew
rs!SPL_FILENAME = vFN
rs!SPL_ID = [Forms]![frm_View]![ID]
rs!SPL_MOD_DT = Format$(Now(), "mm/dd/yyyy")
rs!SPL_DOC_TYP = Forms!frm_View![DOC_TYP]
rs!SPL_FILELOC = vFile
rs!SPL_ACTIVE = "-1"
rs.Update
rs.Close
DoCmd.OpenReport "rpt_OUT", acViewPreview
DoCmd.OutputTo acReport, stDocName, acFormatPDF, vFile
MsgBox "An image of this report has been saved.", vbOKOnly Or vbInformation, "*****"
Exit_btnSaveasPDF_Click:
Exit Sub
Err_btnSaveasPDF_Click:
MsgBox Err.Description
Resume Exit_btnSaveasPDF_Click
End Sub
********************PUSHEOCDATA*******************************************
'* iSpillID is the value of SPILL_ID from Spills table for the
'* the record you want to update
'*
'*******************************************************************************
Public Function pushEOCData(iSpillID As Integer) As Boolean
Dim db As Database
Dim sSql As String
Dim strStoredProcSql As String
Dim qdef As DAO.QueryDef
Dim sEOCIncident As String
Dim rs As Recordset
Dim iEOCSpillID, iEOCMat1ID, iEOCMat2ID As Long
Dim iReturn As Integer
Dim dAmount1 As Double
Dim dAmount2 As Double
Dim sSpillResponse As String
Dim sMaterialResponse As String
Dim sEOCSpillID, sEOCMat1ID, sEOCMat2ID As String
Dim iFoundAt As Long
Dim iStart As Long
Dim sTemp As String
Dim bReturn As Boolean
Dim objSpillNode As IXMLDOMNode
Dim objMaterialsNodes As IXMLDOMNodeList
Dim objMaterialNode As IXMLDOMNode
pushEOCData = True
initWEBEoc
iEOCSpillID = -1
iEOCMat1ID = -1
iEOCMat2ID = -1
'RETRIEVE INCIDENT NUMBER FROM DATABASE and associated data
'from the database. Note: this is a stored procedure
Set db = CurrentDb
Set qdef = CurrentDb.CreateQueryDef("")
qdef.Connect = CurrentDb.TableDefs("usysWellHistory").Connect
qdef.ReturnsRecords = True
'
' This stored procedure is written to return a record set with
' fields named the same as in webeoc.
'
'strStoredProcSql = "EXEC RBDMS.SPILL_QUERY #N_SPILLID=" & iSpillID
strStoredProcSql = "{CALL RBDMS.SPILL_QUERY (" & iSpillID & ")}"
qdef.sql = strStoredProcSql
Set rs = qdef.OpenRecordset
'Check to see if the recordset actually contains rows
If Not (rs.EOF And rs.BOF) Then
rs.MoveFirst 'Unnecessary in this case, but still a good habit
sEOCIncident = rs!INCIDENTID_NUMBER
If Len(sEOCIncident) < 1 Then
pushEOCData = False
Exit Function
End If
'Get the data from webeoc.
'business rules say that we have to have data
'in webeoc to continue
iReturn = getSpillNode(sEOCIncident, objSpillNode)
If iReturn < 1 Then
'MsgBox "No WEBEoc data exists for EOC Incident: " & sEOCIncident
pushEOCData = False
Exit Function
End If
'grab the data id from the spill data, we will need this later to
'update the materials
sEOCSpillID = objSpillNode.Attributes.getNamedItem("dataid").Text
iEOCSpillID = CLng(sEOCSpillID)
If iEOCSpillID < 1 Then
pushEOCData = False
Exit Function
End If
bReturn = updateEOCSpillData(rs, objSpillNode)
If bReturn = False Then
pushEOCData = False
Exit Function
End If
dAmount1 = rs!AMOUNT1
dAmount2 = rs!AMOUNT2
'Get the materials records from webeoc for this incident number.
iResult = getMaterialNodes(sEOCSpillID, objMaterialsNodes)
If dAmount1 > 0 Then
'If webeoc has one or more materials records for the incident,
'grab the first record and update it with dbase values
If objMaterialsNodes.LENGTH > 0 Then
Set objSpillNode = objMaterialsNodes.Item(0)
'update materials
updateEOCMaterialData rs, objSpillNode, 1
Else
'insert materials
insertEOCMaterialData rs, 1, sEOCSpillID
End If
End If
If dAmount2 > 0 Then
'if webeoc has more than one materials record for the incident,
'grab the second record and update it.
If objMaterialsNodes.LENGTH > 1 Then
Set objSpillNode = objMaterialsNodes.Item(1)
'update materials
updateEOCMaterialData rs, objSpillNode, 2
Else
'insert materials
insertEOCMaterialData rs, 2, sEOCSpillID
End If
End If
Else
'MsgBox "No data needs to be pushed to webeoc for this spill."
pushEOCData = True
Exit Function
End If
rs.Close
End Function
I have a table Students with the following fields: Voornaam, Achternaam and Foto. The fields Voornaam and Achternaam are filled in with the students firstname and lastname. The field Foto (Picture) is empty. Because I don't want to manually add every picture of the students I wanted to do it with some code.
I have a form where I put the records and I have a button to load the photos in the empty fields. I also have a textbox where I could say where he has to look for the photos.
This is my code:
Sub cmdLoad_Click()
Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim query As String
Dim MyFolder As String
Dim MyPath As String
Dim MyFile As String
'On Error GoTo ErrorHandler
Set db = CurrentDb
query = "Select * FROM tblStudents"
Set rs = db.OpenRecordset(query, dbOpenDynaset)
MyFolder = Me!txtFolder
'Wanneer er geen items zijn. Sluiten
If rs.EOF Then Exit Sub
With rs
Do Until rs.EOF
MyPath = MyFolder & "\" & [Voornaam] & " " & [Achternaam] & ".jpg"
MyFile = Dir(MyPath, vbNormal)
rs.Edit
[Foto].Class = "Paint.Picture"
[Foto].OLETypeAllowed = acOLEEmbedded
[Foto].SourceDoc = MyPath
[Foto].Action = acOLECreateEmbed
rs.Update
rs.MoveNext
Loop
End With
rs.Close
db.Close
Set rs = Nothing
Set db = Nothing
Exit Sub
ErrorHandler: MsgBox "Test Error #: " & Err.Number & vbCrLf &
vbCrLf & Err.Description End Sub
I iterate on the results of the table. At every record I edit it and I want to add the picture to the foto field, but here's the problem.
When I click the button to load in, I get the following error:
a error occurred while microsoft access was communicating with the ole
server or activex control
.
When I debug it's on this line where it goes wrong:
[Foto].Action = acOLECreateEmbed
I've tried to find a solution, but so far I couldn't find it. I hope the problem is a bit clear. Or is there a better solution?
I store my user photos, documents etc as BLOB.
Avoids the overhead of OLE embed or link;
http://www.ammara.com/articles/imagesaccess.html
To load;
Private Sub cmdLoadImageClient_Click()
Dim strFile As String
Dim strname As String
strname = Form_subfrmClientDetailsAAClient.FirstName & Form_subfrmClientDetailsAAClient.Surname
strFile = fGetFile("Image", "*.gif; *.jpg; *.jpeg; *.png")
If Len(strFile) > 0 Then
If InsertBLOB("tblzBLOBClientPics", CStr(TempVars!frmClientOpenID), strname, "ClientPic", strFile) Then Call ShowImageClient
End If
End Sub
To delete;
Private Sub cmdDeleteImageClient_Click()
Dim strname As String
Dim i As Integer
strname = Form_subfrmClientDetailsAAClient.FirstName & Form_subfrmClientDetailsAAClient.Surname
i = MsgBox("Do you want to Delete the Image for; " & strname & "?", vbOKCancel, "Beresford Financial.")
Select Case i
Case vbOK
dbLocal.Execute "DELETE FROM tblzBLOBClientPics WHERE ClientID = '" & CStr(TempVars!frmClientOpenID) & "' AND ClientName = '" & strname & "' AND BLOBDesc = 'ClientPic'"
Me.ProfilePicClient.Picture = ""
Case vbCancel
End Select
End Sub
To view;
Public Sub ShowImageClient()
Dim strTemp As String
Dim strname As String
On Error GoTo errHere
Me.ProfilePicClient.Picture = ""
strTemp = CurrentProject.Path & "\Temp.jpg"
strname = Nz(Form_subfrmClientDetailsAAClient.FirstName) & Nz(Form_subfrmClientDetailsAAClient.Surname)
If ExtractBLOB("tblzBLOBClientPics", CStr(TempVars!frmClientOpenID), strname, "ClientPic", strTemp) Then
If Len(Dir(strTemp)) > 0 Then
Me.ProfilePicClient.Picture = strTemp
Kill strTemp
End If
End If
Exit Sub
errHere:
MsgBox "Error " & Err & vbCrLf & Err.Description
End Sub
BLOB Functions;
Option Compare Database
Option Explicit
Function InsertBLOB(tblBLOB As String, ClientID As String, ClientName As String, strDesc As String, strFileName As String) As Boolean
'Inserts BLOB into table tblzBLOBDocuments
On Error GoTo CloseUp
Dim objStream As Object 'ADODB.Stream
Dim objCmd As Object 'ADODB.Command
Dim varFileBinary
'Empty any matching record
CurrentDb.Execute "DELETE FROM " & tblBLOB & " WHERE ClientID = '" & ClientID & "' AND ClientName = '" & ClientName & "' AND BLOBDesc = '" & strDesc & "'"
Set objStream = CreateObject("ADODB.Stream")
objStream.Type = 1 'adTypeBinary
objStream.Open
objStream.LoadFromFile strFileName
varFileBinary = objStream.Read
objStream.Close
Set objStream = Nothing
Set objCmd = CreateObject("ADODB.Command")
With objCmd
.CommandText = "PARAMETERS paramID Text(255), paramTable Text(255), paramDesc Text(255), paramExtn Text(5), paramFile LongBinary;" & _
"INSERT INTO " & tblBLOB & " (ClientID, ClientName, BLOBDesc, FileExtn, BLOB) " & _
"SELECT paramID, paramTable, paramDesc, paramExtn, paramFile"
.CommandType = 1 'adCmdText
.Parameters.Append .CreateParameter("paramID", 200, 1, 255, ClientID)
.Parameters.Append .CreateParameter("paramTable", 200, 1, 255, ClientName)
.Parameters.Append .CreateParameter("paramDesc", 200, 1, 255, strDesc)
.Parameters.Append .CreateParameter("paramExtn", 200, 1, 5, right(strFileName, Len(strFileName) - InStrRev(strFileName, ".")))
.Parameters.Append .CreateParameter("paramFile", 205, 1, 2147483647, varFileBinary)
Set .ActiveConnection = CurrentProject.Connection
.Execute , , 128
End With
InsertBLOB = True
CloseUp:
On Error Resume Next
Set objStream = Nothing
Set objCmd = Nothing
End Function
Function ExtractBLOB(tblBLOB As String, ClientID As String, ClientName As String, strDesc As String, ByRef strFileName As String) As Boolean
'Extracts specified BLOB to file from table tblzBLOBDocuments
Dim strSql As String
Dim rst As Object 'ADODB.Recordset
Dim objStream As Object 'ADODB.Stream
Set rst = CreateObject("ADODB.Recordset")
strSql = "SELECT FileExtn, BLOB FROM " & tblBLOB & " WHERE ClientID = '" & ClientID & "' AND ClientName = '" & ClientName & "' AND BLOBDesc = '" & strDesc & "'"
rst.Open strSql, CurrentProject.Connection, 1, 3
If rst.RecordCount = 0 Then
GoTo CloseUp
End If
Set objStream = CreateObject("ADODB.Stream")
With objStream
.Type = 1 'adTypeBinary
.Open
.Write rst.Fields("BLOB").Value
If Not IsNull(rst!FileExtn) Then
strFileName = Left(strFileName, InStrRev(strFileName, ".")) & rst!FileExtn
End If
.SaveToFile strFileName, 2 'adSaveCreateOverWrite
End With
ExtractBLOB = True
CloseUp:
On Error Resume Next
rst.Close
Set rst = Nothing
Set objStream = Nothing
End Function
Filepicker;
Function fGetFile(strType As String, strExt As String, Optional strPath As String)
With Application.FileDialog(3) ' 3=msoFileDialogFilePicker 4=msoFileDialogFolderPicker
' .Filters.Add "Excel Files", "*.xls, *.xlsx, *.xlsm", 1
.Filters.Add strType, strExt, 1
If strPath <> "" Then
.InitialFileName = strPath ' start in this folder
End If
.AllowMultiSelect = False
.Show
If .SelectedItems.Count > 0 Then
' MsgBox .SelectedItems(1)
fGetFile = .SelectedItems(1)
End If
End With
End Function
tblzBLOBClientPics;
ClientID Short Text
ClientName Short Text
BLOBDesc Short Text
FileExtn Short Text
BLOB OLE Object
Just getting to grips some VBA (this stuff's new to me so bear with us!)
From query ContactDetails_SurveySoftOutcomes, I'm trying to first find a list of all the unique values in the DeptName field in that query, hence the rsGroup Dim storing a Grouped query on the DeptName field.
I'm then going to use this grouped list as way of cycling through the same query again, but passing through each unique entry as a filter on the whole recordset and export each filtered recordset to its own Excel spreadsheet... see the Do While Not loop.
My code's tripping up on the DoCmd.TransferSpreadsheet ... rsExport part. I'm a bit new to this, but I guess my Dim name rsExport for the recordset isn't accepted in this method..?
Is there an easy fix to the code I've already started or should I be using a completely different approach to achieve all this?
Code:
Public Sub ExportSoftOutcomes()
Dim rsGroup As DAO.Recordset
Dim Dept As String
Dim myPath As String
myPath = "C:\MyFolder\"
Set rsGroup = CurrentDb.OpenRecordset("SELECT ContactDetails_SurveySoftOutcomes.DeptName " _
& "FROM ContactDetails_SurveySoftOutcomes GROUP BY ContactDetails_SurveySoftOutcomes.DeptName", dbOpenDynaset)
Do While Not rsGroup.EOF
Dept = rsGroup!DeptName
Dim rsExport As DAO.Recordset
Set rsExport = CurrentDb.OpenRecordset("SELECT * FROM ContactDetails_SurveySoftOutcomes " _
& "WHERE (((ContactDetails_SurveySoftOutcomes.DeptName)='" & Dept & "'))", dbOpenDynaset)
DoCmd.TransferSpreadsheet acExport, acSpreadsheetTypeExcel9, rsExport, myPath & Dept & "\" & Dept & " - Soft Outcomes Survey.xls", True
rsGroup.MoveNext
Loop
End Sub
Fixed Code:
Public Sub ExportSoftOutcomes()
Dim rsGroup As DAO.Recordset
Dim Dept As String
Dim myPath As String
myPath = "C:\MyFolder\"
Set rsGroup = CurrentDb.OpenRecordset("SELECT ContactDetails_SurveySoftOutcomes.DeptName " _
& "FROM ContactDetails_SurveySoftOutcomes GROUP BY ContactDetails_SurveySoftOutcomes.DeptName", dbOpenDynaset)
Do While Not rsGroup.EOF
Dept = rsGroup!DeptName
Dim rsExportSQL As String
rsExportSQL = "SELECT * FROM ContactDetails_SurveySoftOutcomes " _
& "WHERE (((ContactDetails_SurveySoftOutcomes.DeptName)='" & Dept & "'))"
Dim rsExport As DAO.QueryDef
Set rsExport = CurrentDb.CreateQueryDef("myExportQueryDef", rsExportSQL)
DoCmd.TransferSpreadsheet acExport, acSpreadsheetTypeExcel9, "myExportQueryDef", myPath & Dept & "\" & Dept & " - Soft Outcomes Survey.xls", True
CurrentDb.QueryDefs.Delete rsExport.Name
rsGroup.MoveNext
Loop
End Sub
You're right that your rsGroup parameter is wrong, Access expects a table name or select query.
Try this code:
strExport = "SELECT * FROM ContactDetails_SurveySoftOutcomes " _
& "WHERE (((ContactDetails_SurveySoftOutcomes.DeptName)='" & Dept & "'))"
Set qdfNew = CurrentDb.CreateQueryDef("myExportQueryDef", strExport)
DoCmd.TransferSpreadsheet acExport, acSpreadsheetTypeExcel9, "myExportQueryDef", myPath & Dept & "\" & Dept & " - Soft Outcomes Survey.xls", True
CurrentDb.QueryDefs.Delete qdfNew.Name 'cleanup
Hope that works
try this hope this will help you
Function Export2XLS(sQuery As String)
Dim oExcel As Object
Dim oExcelWrkBk As Object
Dim oExcelWrSht As Object
Dim bExcelOpened As Boolean
Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim iCols As Integer
Const xlCenter = -4108
'Start Excel
On Error Resume Next
Set oExcel = GetObject(, "Excel.Application") 'Bind to existing instance of Excel
If Err.Number <> 0 Then 'Could not get instance of Excel, so create a new one
Err.Clear
On Error GoTo Error_Handler
Set oExcel = CreateObject("excel.application")
bExcelOpened = False
Else 'Excel was already running
bExcelOpened = True
End If
On Error GoTo Error_Handler
oExcel.ScreenUpdating = False
oExcel.Visible = False 'Keep Excel hidden until we are done with our manipulation
Set oExcelWrkBk = oExcel.Workbooks.Add() 'Start a new workbook
Set oExcelWrSht = oExcelWrkBk.Sheets(1)
'Open our SQL Statement, Table, Query
Set db = CurrentDb
Set rs = db.OpenRecordset(sQuery, dbOpenSnapshot)
With rs
If .RecordCount <> 0 Then
'Build our Header
For iCols = 0 To rs.Fields.Count - 1
oExcelWrSht.Cells(1, iCols + 1).Value = rs.Fields(iCols).Name
Next
With oExcelWrSht.Range(oExcelWrSht.Cells(1, 1), _
oExcelWrSht.Cells(1, rs.Fields.Count))
.Font.Bold = True
.Font.ColorIndex = 2
.Interior.ColorIndex = 1
.HorizontalAlignment = xlCenter
End With
oExcelWrSht.Range(oExcelWrSht.Cells(1, 1), _
oExcelWrSht.Cells(1, rs.Fields.Count)).Columns.AutoFit 'Resize our Columns based on the headings
'Copy the data from our query into Excel
oExcelWrSht.Range("A2").CopyFromRecordset rs
oExcelWrSht.Range("A1").Select 'Return to the top of the page
Else
MsgBox "There are no records returned by the specified queries/SQL statement.", vbCritical + vbOKOnly, "No data to generate an Excel spreadsheet with"
GoTo Error_Handler_Exit
End If
End With
' oExcelWrkBk.Close True, sFileName 'Save and close the generated workbook
' 'Close excel if is wasn't originally running
' If bExcelOpened = False Then
' oExcel.Quit
' End If
Error_Handler_Exit:
On Error Resume Next
oExcel.Visible = True 'Make excel visible to the user
rs.Close
Set rs = Nothing
Set db = Nothing
Set oExcelWrSht = Nothing
Set oExcelWrkBk = Nothing
oExcel.ScreenUpdating = True
Set oExcel = Nothing
Exit Function
Error_Handler:
MsgBox "The following error has occured" & vbCrLf & vbCrLf & _
"Error Number: " & Err.Number & vbCrLf & _
"Error Source: Export2XLS" & vbCrLf & _
"Error Description: " & Err.Description _
, vbOKOnly + vbCritical, "An Error has Occured!"
Resume Error_Handler_Exit
End Function
DoCmd.TransferSpreadsheet expects its third parameter to be a String (variable or literal) specifying the name of a table or query. So, instead of opening a DAO.Recordset you could create a DAO.QueryDef named something like "forExportToExcel" with the same SQL code, then use that name in the TransferSpreadsheet call.
I have the following code:
Public Function BuildSQL(stQueryName As String, stWhereClause As String) As String
On Error GoTo Err_BuildSQL
Dim SQLcmd As String
Dim intPos As Integer
Dim db As Database
Dim qryOrig As QueryDef
Set db = CurrentDb()
Set qryOrig = db.QueryDefs(stQueryName)
SQLcmd = qryOrig.SQL
intPos = InStr(SQLcmd, "WHERE")
If intPos > 0 Then
SQLcmd = Left(SQLcmd, intPos - 1)
End If
intPos = InStr(SQLcmd, ";")
If intPos > 0 Then
SQLcmd = Left(SQLcmd, intPos - 1)
End If
If Not (stWhereClause = "") Then
SQLcmd = Trim(SQLcmd) & " WHERE " & stWhereClause & ";"
Else
SQLcmd = Trim(SQLcmd) & ";"
End If
BuildSQL = SQLcmd
Exit_BuildSQL:
Set qryOrig = Nothing
Set db = Nothing
Exit Function
Err_BuildSQL:
MsgBox Err.Description
Resume Exit_BuildSQL
End Function
Private Sub SandBox_Click()
On Error GoTo Err_SandBox_Click
Dim db As Database
Dim rs As Recordset
Dim stSQL As String
Dim stFrmName As String
Dim stQryName As String
Dim stSQLWhere As String
Dim stIDList As String
stFrmName = "Libri"
stQryName = "Libri_All_Query"
'Define WHERE clause
stSQLWhere = ""
If Not (IsNull([Forms]![Libreria]![Editore]) Or [Forms]![Libreria]![Editore] = "") Then
stSQLWhere = stSQLWhere & "Libri_Editori.Editore = '" & [Forms]![Libreria]![Editore] & "'"
End If
If Not (IsNull([Forms]![Libreria]![CognomeAutore]) Or [Forms]![Libreria]![CognomeAutore] = "") Then
If (stSQLWhere = "") Then
stSQLWhere = stSQLWhere & "Autori.Cognome = '" & [Forms]![Libreria]![CognomeAutore] & "'"
Else
stSQLWhere = stSQLWhere & " AND Autori.Cognome = '" & [Forms]![Libreria]![CognomeAutore] & "'"
End If
End If
'Here several more fields of the search form will be checked and added
stSQL = BuildSQL(stQryName, stSQLWhere)
'*** Code in question!
Set db = CurrentDb()
Set rs = db.OpenRecordset(stSQL)
If Not (rs.EOF And rs.BOF) Then
stIDList = "("
rs.MoveFirst
Do Until rs.EOF = True
If (stIDList = "(") Then
stIDList = stIDList & rs.Fields(0)
Else
stIDList = stIDList & ", " & rs.Fields(0)
End If
rs.MoveNext
Loop
stIDList = stIDList & ")"
Else
Err.Description = "Errore! Recordset vuoto."
Resume Err_SandBox_Click
End If
DoCmd.OpenForm stFrmName, , , , acFormReadOnly
Access.Forms(stFrmName).RecordSource = "SELECT * FROM Libri WHERE Libri.ID IN " & stIDList
'**** End code in question
Exit_SandBox_Click:
Set db = Nothing
Set rs = Nothing
Exit Sub
Err_SandBox_Click:
MsgBox Err.Description
Resume Exit_SandBox_Click
End Sub
This code works as I want but "looks" slow even with a test DB with only a few records in each table.
I believe the time is spent (how can I check if this is true?) in the loop between comments.
Is there a more basic, obvious and efficient way to filter the form than creating a recordset and looping through it as I am doing?
The form "Libri" is a big one with several subform to be able to see all the data of a Book.
The query "Libri_All_Query" is a join of almost all tables in the DB and the code shown is executed from a form where I plan to add all possible search fields.
Forms have a filter property:
stWhereClause = "Title Like '" & Me.txtSearch & "*'"
Me.Filter = stWhereClause
Me.FilterOn = True
The filter should be constructed in a similar way to a WHERE statement. There are some limitations compared with Where. You may wish to check with DCount that records will be returned.
EDIT
If you want a set of records where a subform contains only certain records, you need something on these lines:
SELECT b.Title
FROM Books b
WHERE b.ID IN (
SELECT j.BookID FROM BooksAuthorJunction j
INNER JOIN Authors a ON j.AuthorID = a.ID
WHERE a.Author Like "Arn*")
There are advantages in building more that one form, books as a main form and authors as a subform, then authors as a main form and books as a subform. It is often easier on the user.