Validating data against a prior record

GaP42

Active member
Local time
Tomorrow, 02:38
Joined
Apr 27, 2020
Messages
555
I have a Motor Vehicle Travel Diary (tblMVLog) table with records, as the parent to Trip Log table. Apart from in-Travel Diary record validation in BeforeUpdate such as MVLogStartDate (Diary start date) is before the MVLogEndDate (Diary End Date), I would also like to check that, if the record is a new record, that the MVLogStart Date is Greater than the MVLogEnd date of the previous record, and that the OdometerStart is greater than or equal to the OdometerEnd of the previous record.

Should such checks be in the BeforeUpdate event? I have tried using AfterUpdate and a recordset to compare the newest record and the prior one, however, as edits might be done in earlier records this can fail - the user may be editing an earlier record and so the comparison may not be against the newest record in tblMVLog and the previous one in MVlogEndDate order. It seems that I need to do this in BeforeUpdate if the record being updated is a new record. However this record has not been committed so how do I use a recordset (or other method) to compare the new record values and the last record.

This is the code I was trying in AfterUpdate

Code:
Dim intMVID, prevRecODEnd As Integer
Dim prevRecEndDt As Date
Dim strSQL As String
Dim db As dao.Database
Dim rs As dao.recordSet

intMVID = Me.MVID
If DCount("MVLOgID", "tblMVLOG", "MVID = " & intMVID) > 1 Then ' check if there are at least 2 records to enable checks

    Set db = CurrentDb
    
    strSQL = "SELECT tblMVLog.MVLogID, tblMVLog.MVID, tblMVLog.LogSheetEndDate, tblMVLog.OdometerSheetEnd, tblMVLog.LogSheetstartDate, tblMVLog.OdometerSheetStart" & _
            " FROM tblMVLog" & _
            " WHERE MVID = " & intMVID & _
            " ORDER BY tblMVLog.LogSheetEndDate DESC;"

    Set rs = db.OpenRecordset(strSQL)

   ' If the rs is empty, exit.
    If rs.EOF Then Exit Sub  

    With rs
        .MoveFirst ' the most recent record by logsheetenddate
        .MoveNext ' Navigate to previous record to get previous values for comparison
        prevRecEndDt = ![LogSheetEndDate]
        prevRecODEnd = ![OdometerSheetEnd]
        .MoveFirst  ' back to the most recent record
        If ![LogSheetstartDate] < prevRecEndDt Then
            MsgBox "The Start date for this Travel Diary is before the end date for the previous Travel Diary. " & _
            "This is not permitted. Adjust the start date", vbInformation, "Start date is earlier than end date of previous record"
            Me.txtStartDate.SetFocus
            GoTo exit_afterUpdate
        End If
        
        If ![OdometerSheetStart] < prevRecODEnd Then
            MsgBox "The odometer value entered is less than the previous Travel Diary odometer end value. " & _
            "This must be corrected. Adjust the odometer at start value.", vbInformation, "Start reading is less than end reading of previous record"
            Me.OdometerSheetStart.SetFocus
            GoTo exit_afterUpdate
        End If
       End With
      
        rs.Close
        db.Close
    
       Set rs = Nothing
       Set db = Nothing
End If

Appreciate your advice.
 
AfterUpdate and BeforeUpdate should be self explanatory?

You need the BeforeUpdate event.
If the checks do not satisfy your process, then you cancel that event.

 
Thanks Gasman - as in my post - i think i was saying that:
"It seems that I need to do this in BeforeUpdate if the record being updated is a new record. However this record has not been committed so how do I use a recordset (or other method) to compare the new record values and the last record."
And as indicated, I have not seen similar examples of how the validation is done compared to other records, i was wary of whether I was using the correct approach. I am working thru the changes needed to the code above to place into the BeforeUpdate event.
The modifications to the validation code are to check if the record is a new record, change to include Cancel = true statements, and navigation/checking the recordset. Will check through it tomorrow (i hope).
 
If this is a mileage log, there is no need to store start and end odometer values, just the end odometer reading for each record. The start reading for each record would be the end value of previous record and can be retrieved by query for report output. Review http://allenbrowne.com/subquery-01.html#AnotherRecord

Use TOP 1 to pull only one record into recordset.

Or can use DMax() to pull most recent date or reading from table.

Did you test code for the case of empty db and entering first mileage record?
 
Last edited:
Here is your problem. When comparing against a previous record... there IS no previous record - unless you take steps to assure that the records are presented in order. You see, Access isn't based on LIST theory, it is based on SET theory where ALL RECORDS in the recordset are treated at once (or at least, the implementation of SQL makes it look that way.)

In practical terms, if your .RecordSource is a raw table, you have no inherent order to records. "Next" and "Previous" are variable at best. IF the .RecordSource is a query with an ORDER BY as part of the mix, however, there CAN be such a thing as "previous" or "next."

Gasman has given you a couple of useful articles and good advice, but you still need to understand how to find what you need. Here is a thought for you. You have a form open to a record in a recordset. The record is not saved prior to an "Update" internal action, for which there is a BeforeUpdate event (which can be cancelled, which in turn cancels the internal update). At this "BeforeUpdate" time you can do the test. But HOW do you do the test, you ask?

There are actually TWO recordsets open behind your form. The one that feeds the form is the primary recordset. But there is one more open recordset accessed via Me.RecordSetClone - and it is ALSO open to the same table or query but is a separate channel. You can perform a Me.RecordSetClone.FindFirst or .FindNext or whatever, so that you can define your search criteria any way necessary to perform a lookup of other data from the same table. You can navigate that clone as you wish. It gets reset at the next main-form navigation.

At the point of the BeforeUpdate event, you have the current values on the form as one comparand, and you can use the data from the cloned recordset for the other comparand. Just remember that finding the desired "previous" record is more difficult if the primary .RecordSource doesn't include ordering. You can still do this without ordering, but it makes the .FindFirst or .FindNext sequence harder to write.
 
Thanks Gasman - as in my post - i think i was saying that:
"It seems that I need to do this in BeforeUpdate if the record being updated is a new record. However this record has not been committed so how do I use a recordset (or other method) to compare the new record values and the last record."
And as indicated, I have not seen similar examples of how the validation is done compared to other records, i was wary of whether I was using the correct approach. I am working thru the changes needed to the code above to place into the BeforeUpdate event.
The modifications to the validation code are to check if the record is a new record, change to include Cancel = true statements, and navigation/checking the recordset. Will check through it tomorrow (i hope).
I would not use a recordset, unless you have more than two fields to check.
However you have the current data in the form, you compare that to the last record data, be it a recordset or DlookUp() and decide whether it passes validation or not.
If it passes, only then do you save the record in whatever method you prefer.
 
If this is a mileage log, there is no need to store start and end odometer values, just the end odometer reading for each record. The start reading for each record would be the end value of previous record and can be retrieved by query for report output. Review http://allenbrowne.com/subquery-01.html#AnotherRecord

Use TOP 1 to pull only one record into recordset.

Or can use DMax() to pull most recent date or reading from table.

Did you test code for the case of empty db and entering first mileage record?
Thanks June7 - I had considered that option (for the log), however I have to support the case where private vehicles are used (as volunteers) for which the log is not continuous - only those trips that are sanctioned are recorded in a diary and reimbursable. The other alternative would be to make the gap a "dummy" record of purpose "Private" - and so rely upon the previous record for continuity. However the rules around creating a dummy record after creating a real one seemed a little too obtuse.

For a travel diary, again because volunteers do not use their vehicles in every period and therefore do not submit a travel diary, the diary records are not always continuous.
 
Last edited:
Understood. I had in mind a log for company vehicles or for your own vehicle. But if this is actually for mileage reimbursement to others, certainly different situation.
 
Always use the BeforeUpdate event for validation. The AfterUpdate runs AFTER the record has been saved and so is useless for this purpose.

Since all you need is the previous mileage, just use a dLookup() to find the mileage for the latest record for this vehicle. But you must let the user override it if there is a gap. So, if the user entered a number just verify that it is >= the last saved value.
 
Well, I have used recordsetclone to check the previous (date order) record - it works and shows the message to correct the error. However record navigation locks up. When the correction is made, and the user attempts to move to a different record using the navigation control, it does not allow it. If I (user) select a different tab (eg parent) it accepts the change and on returning to the Travel Diary tab and the associated subform containing the MV Travel Diary records, I am able to move from record to record. I don't understand why this would happen. The updated code relating to the tests against the prior record is shown below. Checked further, I now find an edit on the date control and others, on any Travel Diary record in the subform results in the same. Stepping through, the validations are assessed, and the before update finishes - attempting to then use navigation again runs through the beforeupdate and leaves me on the same record. Progress to the next/previous record is prevented. Why?

The BeforeUpdate event for the subform that is the Travel Diary (single form) has the following code

Code:
Private Sub Form_BeforeUpdate(Cancel As Integer)

    Dim intMVID
    Dim prevRecODEnd As Single
    Dim prevRecEndDt As Date
    Dim db As dao.Database
    Dim rs As dao.recordSet
   
If IsNull(Me.Parent!MVID) Then
    MsgBox "A vehicle record must exist before trying to create a Travel Diary for the vehicle!", vbOKOnly, "No vehicle record"
    Cancel = True
    Me.Undo
    Me.Parent!txtRegistion.SetFocus
    GoTo Exit_BeforeUpdate
End If

' in-record data validation

If IsNull(Me.LogRecStatus) Then
    MsgBox "A Travel Diary record must be assigned a status - usually SUBMITTED is the initial status.", vbInformation, "Select a status"
    Cancel = True
    Me.cboRecStatus.SetFocus
    GoTo Exit_BeforeUpdate
End If

If IsNull(txtStartDate) Or IsNull(txtEndDate) Then
    MsgBox "A Travel Diary must have a Start Date AND an End Date", vbInformation, "Start Date and End Date required"
    Cancel = True
    Me.txtEndDate.SetFocus
    GoTo Exit_BeforeUpdate
End If

If Me!txtStartDate > Me!txtEndDate Then
    MsgBox "The End date (" & Me.txtEndDate & ") MUST be AFTER the Start date (" & Me.txtStartDate & ") " & _
    "for the Travel Diary record to be valid.", vbInformation, "Start date MUST be after End date"
    Cancel = True
    Me.txtEndDate.SetFocus
    GoTo Exit_BeforeUpdate
End If

If IsNull(Me.OdometerSheetStart) Or Me.OdometerSheetStart = "" Or IsNull(Me.OdometerSheetEnd) Or Me.OdometerSheetEnd = "" Then
    MsgBox "The Travel Diary must have values for both Odometer start AND Odometer End.", vbInformation, "Odometer reading missing"
    Cancel = True
    Me.OdometerSheetEnd.SetFocus
    GoTo Exit_BeforeUpdate
End If

If Me.OdometerSheetStart >= Me.OdometerSheetEnd Then
    MsgBox "The Travel Diary odometer start value (" & Me.OdometerSheetStart & ") " & _
    "MUST be less than the odometer end value (" & Me.OdometerSheetEnd & ").", vbInformation, "The Start must be less than the End!"
    Cancel = True
    Me.OdometerSheetEnd.SetFocus
    GoTo Exit_BeforeUpdate
End If

Me.cboRateMethod.SetFocus
If IsNull(Me.cboRateMethod) Or Me.cboRateMethod = "" Then
    MsgBox "A value for Rate Method must be chosen from the list", vbInformation, "Chose a Rate Method"
    Cancel = True
    Me.cboRateMethod.SetFocus
    GoTo Exit_BeforeUpdate
End If

Me.cboReimbursementRate.SetFocus
If IsNull(Me.cboReimbursementRate) Or Me.cboReimbursementRate = "" Then
    MsgBox "A value for the rate of reimburement must be chosen, even if reimbursement is not to be based on the distance travelled.", vbInformation, "Select a rate of reimbursement"
    Me.cboReimbursementRate.SetFocus
    GoTo Exit_BeforeUpdate
End If

Me.cboRecStatus.SetFocus
If Me.cboRecStatus.Text = "Reviewed" Then
    If IsNull(Me!ReviewedBy) Then
        MsgBox "The person who is designated to review this vehicle Travel Diary needs to be nominated.", vbInformation, "Nominate who is to review the Travel Diary"
        Cancel = True
        Me.ReviewedBy.SetFocus
        GoTo Exit_BeforeUpdate
    End If
End If

If Not IsNull(Me.LogRecStatus) Then
    Me.cboRecStatus.SetFocus
    If Me.cboRecStatus.Text = "Approved" Then
        If IsNull(Me!ApprovedBy) Then
            MsgBox "The person who is designated to approve this vehicle Travel Diary needs to be nominated.", vbInformation, "Nominate who is to approve the Travel Diary"
            Cancel = True
            Me.ApprovedBy.SetFocus
            GoTo Exit_BeforeUpdate
        End If
        If IsNull(Me.ReviewedByDate) Or IsNull(Me!ReviewedBy) Then
            MsgBox "For the Travel Diary record to be APPROVED it must have been reviewed. The record is missing the " & _
            "name of who reviewed the record or the date the review was done.", vbInformation, "Reviewed by and review date are required."
            Cancel = True
            Me.ReviewedBy.SetFocus
            GoTo Exit_BeforeUpdate
        End If
    End If

    If Me.cboRecStatus.Text = "Completed" Then
        If IsNull(Me!ApprovedBy) Or IsNull(Me!ApprovedbyDate) Then
            MsgBox "For the Travel Diary record to be COMPLETED it must have been approved. The record is missing the " & _
            "name of who approved the record or the date approval was given.", vbInformation, "Approved by and approval date are required"
            Cancel = True
            Me.ApprovedBy.SetFocus
            GoTo Exit_BeforeUpdate
        End If
        If IsNull(Me.ApprovedAmount) Then
            MsgBox "For a travel diary record to be COMPLETED an entry is required for the approved amount (including $0)", vbInformation, "Enter Approved Amount"
            Cancel = True
            Me.ApprovedAmount.SetFocus
            GoTo Exit_BeforeUpdate
        End If
        If IsNull(Me.ReviewedByDate) Or IsNull(Me!ReviewedBy) Then
            MsgBox "For the Travel Diary record to be COMPLETED it must have been reviewed. The record is missing the " & _
            "name of who reviewed the record or the date the review was done.", vbInformation, "Reviewed by and review date are required."
            Cancel = True
            Me.ReviewedBy.SetFocus
            GoTo Exit_BeforeUpdate
        End If
    End If
End If

' Record validation compared to previous record (date order) - the test is relevant where the record is created manually
' The new Travel diary button creates a record that conforms automatically, which can then be edited, but it exists as a record (?)
If Me.NewRecord = True Then

    intMVID = Me.MVID
    If DCount("MVLOgID", "tblMVLOG", "MVID = " & intMVID) >= 1 Then
                        ' check there is at least 1 existing record for the Vehicle to enable checks
        Set db = CurrentDb
               
        Set rs = Me.RecordsetClone      '  read only copy of the form recordset (? the query used to populate the form - filter?)
   
       ' If the rs is empty, exit.
        If rs.EOF Then
                GoTo Exit_rsCleanup
        Else
            rs.Filter = "MVID = " & intMVID
            rs.Sort = "LogSheetendDate DESC"
                   
            With rs
   
                .MoveFirst ' the most recent record by logsheetenddate
                prevRecEndDt = ![LogSheetEndDate]
                prevRecODEnd = ![OdometerSheetEnd]
                If Me.txtStartDate < prevRecEndDt Then
                    MsgBox "The Start date for this Travel Diary is before the end date: " & prevRecEndDt & " for the previous Travel Diary. " & _
                    "This is not permitted. Adjust the start date", vbInformation, "Start date is earlier than end date of previous record"
                    Cancel = True
                    Me.txtStartDate.SetFocus
                    GoTo Exit_rsCleanup
                End If
               
                If Me.OdometerSheetStart < prevRecODEnd Then
                    MsgBox "The odometer at the start is less than the previous Travel Diary odometer end (" & prevRecODEnd & ") " & _
                    "This must be corrected. Adjust the odometer at start value.", vbInformation, "Start km is less than end km of previous record"
                    Cancel = True
                    Me.OdometerSheetStart.SetFocus
                    GoTo Exit_rsCleanup
                End If
            End With
        End If
Exit_rsCleanup:   ' last thing to do from when rs created
            rs.Close
            db.Close
            
            Set rs = Nothing
            Set db = Nothing
           
    End If
End If

    Me.DateLastEdited = Now()
    Me.ctlUser = GetNetworkUserName()
       
Exit_BeforeUpdate:

    Exit Sub

Error_BeforeUpdate:
    TSCs_ReportUnexpectedError "Form_BeforeUpdate", "frmsubMVLog", ""
    Resume Exit_BeforeUpdate

End Sub
 
I have changed a few things - instead of using recordsetclone I have used me.recordset.clone for example, and extended the checking to apply to immediate prior and following records where they exist (code below for reference). I think this is now solved.
Re the use of the standard previous / next navigation control - I have hidden the control and found introducing Previous/Next buttons on the form with a Me.Dirty check True to False allowed the navigation. Still don't know why the standard navigation did not do this.
Code:
Set db = CurrentDb

Set rsc = Me.recordSet.Clone         '  clone of the record set - to navigate to and read next and previous record values

intMVID = Me.MVID
intMVLogID = Me.MVLogID     '  the current record id (? does this include the new record not yet committed?)
    
If Me.NewRecord = True Then

    If DCount("MVLogID", "tblMVLog", "MVID = " & intMVID) >= 1 Then
                        ' check there is at least 1 existing record for the Vehicle to enable checks
      
        If Not (rsc.EOF And rsc.BOF) Then
                GoTo Exit_rsCleanup     ' If the rsc is empty, exit.
        Else ' at least one record to check against.
            rsc.Filter = "MVID = " & intMVID
            rsc.Sort = "LogSheetendDate DESC"
            rsc.FindFirst "MVlogID = " & intMVLogID
            varBookmark = rsc.Bookmark
                    
            With rsc
    
                .MoveFirst ' the most recent record by logsheetenddate before newrecord is added
                prevRecEndDt = ![LogSheetEndDate]
                prevRecODEnd = ![OdometerSheetEnd]
                If Me.txtStartDate < prevRecEndDt Then
                    MsgBox "The Start date for this Travel Diary is before the end date: " & prevRecEndDt & " for the previous Travel Diary. " & _
                    "This is not permitted. Adjust the start date", vbInformation, "Start date is earlier than end date of previous record"
                    Cancel = True
                    Me.txtStartDate.SetFocus
                    GoTo Exit_rsCleanup
                End If
                
                If Me.OdometerSheetStart < prevRecODEnd Then
                    MsgBox "The odometer at the start is less than the previous Travel Diary odometer end (" & prevRecODEnd & ") " & _
                    "This must be corrected. Adjust the odometer at start value.", vbInformation, "Start km is less than end km of previous record"
                    Cancel = True
                    Me.OdometerSheetStart.SetFocus
                    GoTo Exit_rsCleanup
                End If
            End With
        End If
    End If
    
Else ' record is not a new record (ie an edit of an existing record)
        If DCount("MVLOgID", "tblMVLOG", "MVID = " & intMVID) >= 1 Then
                ' check there is at least 1 existing record for the Vehicle to enable checks
       ' If the rs is empty, exit.
        If rsc.EOF Then
                GoTo Exit_rsCleanup
        Else
            rsc.Filter = "MVID = " & intMVID  ' make sure the cloned recordset uses the current filter for vehicle
            rsc.Sort = "LogSheetendDate DESC" ' make sure records are sorted on the same basis as in the form
            rsc.FindFirst "MVLogID = " & intMVLogID   ' FindFirst to go to the record that is the current record on the form.
                                    '  Next is more recent than current, prev is older
            varBookmark = rsc.Bookmark
            With rsc
                Do While Not .EOF
                    .MoveNext  ' in date order - this is the older record (if it exists .. if not then EOF)
                    If .EOF Then
                        Exit Do ' reached EOF so exit this test
                    End If      ' read values to test
                    prevRecEndDt = ![LogSheetEndDate]
                    prevRecODEnd = ![OdometerSheetEnd]
                    
                    If Me.txtStartDate < prevRecEndDt Then
                        MsgBox "The Start date for this Travel Diary is before the end date: " & prevRecEndDt & " for the previous Travel Diary. " & _
                        "This is not permitted. Adjust the start date", vbInformation, "Start date is earlier than end date of previous record"
                        Cancel = True
                        Me.txtStartDate.SetFocus
                        GoTo Exit_rsCleanup
                    End If
                    
                    If Me.OdometerSheetStart < prevRecODEnd Then
                        MsgBox "The odometer at the start is less than the previous Travel Diary odometer end (" & prevRecODEnd & ") " & _
                        "This must be corrected. Adjust the odometer at start value.", vbInformation, "Start km is less than end km of previous record"
                        Cancel = True
                        Me.OdometerSheetStart.SetFocus
                        GoTo Exit_rsCleanup
                    End If
                    Exit Do
                Loop  ' only test one prior record - based on date order
                .MovePrevious ' return to starting position in cloned rs then exist loop
                Do While Not .BOF   ' check when moving to previous - that the next record exists
                    .MovePrevious ' in date order - this is a newer record (if exists - if not then BOF)
                    If .BOF Then
                        Exit Do ' no newer record to read values from - therefore skip test
                    End If
                    nxtRecStartDt = ![LogSheetstartDate]
                    nxtRecODSt = ![OdometerSheetStart]
                    
                    If Me.txtEndDate > nxtRecStartDt Then
                        MsgBox "The end date (" & Me.LogSheetEndDate & ") for this Travel Diary is after the Start date (" & nxtRecStartDt & ") for the next Travel Diary." & _
                        vbCrLf & "This is not allowed - they cannot overlap. Please correct the end date.", _
                        vbOKOnly, "End date is after the start date of the next Diary"
                        Cancel = True
                        Me.txtEndDate.SetFocus
                        GoTo Exit_rsCleanup
                    End If
                    
                    If Me.OdometerSheetEnd > nxtRecODSt Then
                        MsgBox "The kms at the end of this Diary record (" & Me.OdometerSheetEnd & ") is greater than the kms at the " & _
                        "start (" & nxtRecODSt & "} of the Diary following this record." & vbCrLf & "This is not allowed - fix the odometer end value.", _
                        vbOKOnly, "End diary km is less than start diary km of the next diary"
                        Cancel = True
                        Me.OdometerSheetEnd.SetFocus
                        GoTo Exit_rsCleanup
                    End If

                    Exit Do
                Loop
                .MoveNext ' return to starting position - not for any purpose
            End With
        End If
    End If
    
Exit_rsCleanup:   ' cleanup recordsets
        rsc.Close
        db.Close

        Set rsc = Nothing
        Set db = Nothing
End If
 

Users who are viewing this thread

Back
Top Bottom