Access Recordset Looping

Rocket Man Loop

One of the most common things that all Access developers do is loop through recordsets to read them, edit them, …

I thought I’d give a few code samples for those of you that are just starting off.

The Basic Loop

At the core of it , the most basic example of a recordset loop would be

Dim rs                    As DAO.Recordset
Set rs = CurrentDb.OpenRecordset("SELECT * FROM YourTableName")
With rs
    Do While Not .EOF
        'Do something here with each record
        .MoveNext
    Loop
End With
rs.Close
Set rs = Nothing

A simple example might be:

'Output the first name of each contact in a messagebox
Dim rs                    As DAO.Recordset

Set rs = CurrentDb.OpenRecordset("SELECT * FROM Contacts")
With rs
    Do While Not .EOF
        MsgBox ![FirstName]
        .MoveNext
    Loop
End With
rs.Close
Set rs = Nothing

Reporting Empty Recordsets

The code above will simply not do anything in the event of an empty recordset.  If you actually want to detect the fact that there are no records returned to be processed and act accordingly (say to notify the user), then you need to add a simple test, such as:

    Dim rs                    As DAO.Recordset

    Set rs = CurrentDb.OpenRecordset("SELECT * FROM YourTableName")
    With rs
        If .RecordCount <> 0 Then
            Do While Not .EOF
                'Do something here with each record
                .MoveNext
            Loop
        Else
            'There are no records returned!
            MsgBox "No records to process!", vbInformation Or vbOKOnly, "No Records"
        End If
    End With
    rs.Close
    Set rs = Nothing

Some developers prefer to test both .BOF and .EOF instead, so something along the lines of:

    Dim rs                    As DAO.Recordset

    Set rs = CurrentDb.OpenRecordset("SELECT * FROM YourTableName")
    With rs
        If Not (.BOF And .EOF) Then
            Do While Not .EOF
                'Do something here with each record
                .MoveNext
            Loop
        Else
            'There are no records returned!
            MsgBox "No records to process!", vbInformation Or vbOKOnly, "No Records"
        End If
    End With
    rs.Close
    Set rs = Nothing

Getting An Accurate Record Count

I’m sure you’re saying to yourself that’s simple, you already showed us how with .RecordCount! Well, you’d be right and wrong at the same time!

You see, there an issue with the RecordCount and the way OpenRecordset works.  Access won’t necessarily load all the records at once, thus .RecordCount can reliably tell you if there are records, or not, but won’t necessarily give you the right total count.  To get the proper count, you must first force Access to navigate to the last record, then retrieve the count.  This can be done by doing:

    Dim rs                    As DAO.Recordset
    Dim lRecordCount          As Long

    Set rs = CurrentDb.OpenRecordset("SELECT * FROM YourTableName")
    With rs
        .MoveLast
        lRecordCount = .RecordCount
        .MoveFirst
        If lRecordCount <> 0 Then
            Do While Not .EOF
                'Do something here with each record
                .MoveNext
            Loop
        Else
            'There are no records returned!
            MsgBox "No records to process!", vbInformation Or vbOKOnly, "No Records"
        End If
    End With
    rs.Close
    Set rs = Nothing

Notice how I perform a .MoveLast prior to using .RecordCount and then immediately perform a .MoveFirst to go back to the start so I can then perform my loop.

Another alternative would be to loop through the records backwards, by doing something like:

    Dim rs                    As DAO.Recordset
    Dim lRecordCount          As Long

    Set rs = CurrentDb.OpenRecordset("SELECT * FROM YourTableName")
    With rs
        .MoveLast
        lRecordCount = .RecordCount
        If lRecordCount <> 0 Then
            Do While Not .BOF
                'Do something here with each record
                .MovePrevious
            Loop
        Else
            'There are no records returned!
            MsgBox "No records to process!", vbInformation Or vbOKOnly, "No Records"
        End If
    End With
    rs.Close
    Set rs = Nothing

but, personally, I usually tend to prefer the former approach.

Inserting Records

You can easily use a loop to insert new records into a recordset, the basic concept being

    Dim rs                    As DAO.Recordset

    Set rs = CurrentDb.OpenRecordset("SELECT FirstName, LastName, CreatedOn FROM Contacts WHERE 1=0", , dbAppendOnly)
    With rs
        .AddNew
        ![FirstName] = "John"
        ![LastName] = "Wayne"
        ![CreatedOn] = Now()
        .Update
    End With
    rs.Close
    Set rs = Nothing

Updating Records

Updating existing records is equally straightforward and is done by doing something along the lines of:

    Dim rs                    As DAO.Recordset

    Set rs = CurrentDb.OpenRecordset("SELECT CreatedOn FROM Contacts WHERE CreatedOn Is Null")
    With rs
        Do While Not .EOF
            .Edit
            ![CreatedOn] = Now()
            .Update
            .MoveNext
        Loop
    End With
    rs.Close
    Set rs = Nothing

That said, this is an inefficient approach and running a query would be better, such as:

CurrentDb.Execute "UPDATE Contacts SET Contacts.CreatedOn = Now() WHERE Contacts.CreatedOn Is Null", dbFailOnError

Deleting Records

The same is true of Deleting records, the basic syntax being

    Dim rs                    As DAO.Recordset

    Set rs = CurrentDb.OpenRecordset("SELECT * FROM Contacts WHERE CreatedOn =#2/24/2022 10:08:51#")
    With rs
        Do While Not .EOF
            .Delete
            .MoveNext
        Loop
    End With
    rs.Close
    Set rs = Nothing

Once again, a query would be best:

CurrentDb.Execute "DELETE FROM Contacts WHERE CreatedOn = #2/24/2022 10:08:51#", dbFailOnError

Best Practices

Most of the common query best practices apply here.

Only Grab What You Need!

For instance, in my examples, it doesn’t make sense to return the entire table’s content if all I wanted was the First and Last names of the contacts!  So

Set rs = CurrentDb.OpenRecordset("SELECT * FROM Contacts")

would be better written

Set rs = CurrentDb.OpenRecordset("SELECT [FirstName], [LastName] FROM Contacts")

Only Grab What You Need (2)!

I’ve seen numerous times in forum posts where users loop through a recordset looking for a value and then performing some action on it, something like

    Dim rs                    As DAO.Recordset

    Set rs = CurrentDb.OpenRecordset("SELECT * FROM Contacts")
    With rs
        If .RecordCount <> 0 Then
            Do While Not .EOF
                If ![ContactId] = Me.ID Then
                    'Do Something
                End If
                .MoveNext
            Loop
        Else
            'There are no records returned!
            MsgBox "No records to process!", vbInformation Or vbOKOnly, "No Records"
        End If
    End With
    rs.Close
    Set rs = Nothing

This is incredibly inefficient!  Instead, apply  your filter directly on the OpenRecordset, like:

    Dim rs                    As DAO.Recordset

    Set rs = CurrentDb.OpenRecordset("SELECT * FROM Contacts WHERE ContactId = " & Me.ID)
    With rs
        If .RecordCount <> 0 Then
            Do While Not .EOF
                'Now you will only loop through matching records
                .MoveNext
            Loop
        Else
            'There are no records returned!
            MsgBox "No records to process!", vbInformation Or vbOKOnly, "No Records"
        End If
    End With
    rs.Close
    Set rs = Nothing

Be Aware Of Nulls

Many times we will use recordset loop to read data, say to push the data to another recordset, create an Excel WorkSheet, generate a Word Mail Merge, … just be careful as the returned value could easily be a NULL value which can break your code quite easily.

Thus, depending on the field definition and what you are doing exactly, code like

Dim sFirstName As String
sFirstName = ![FirstName]

would be better off written as

Dim sFirstName As String
sFirstName = Nz(![FirstName], "")

as sFirstName being a String variable cannot handle a NULL value ever being passed to it.  Another option would be to dim sFirstName as a Variant instead.

Is A Recordset Loop The Right Tool For The Job?

This is an issue far too often overlooked.

People dive head first into coding without properly evaluating what it is they have chosen to do exactly.  Far too often I see people using loops to run through entire tables to find matches and update each match one by one.  This is simply horrible, often slow and progressively gets slower as the table gets more and more records over time.

Remember:

An Ounce of Prevention is Worth a Pound of CureBen Franklin

So think long and hard if you truly need a loop mechanism or whether a simple Insert/Update/Delete query wouldn’t be much better! (I’ll tell you a secret, it quite often is!).

Which Library Should I Be Using?

If you look over a lot of the samples that have been around for a long time (since nothing on this front has changed in years) or look at various posts, you will often see mention of setting the “Microsoft DAO X.X Object Library” which is used when dimming DAO variables, things like:

Dim db as DAO.Database
Dim rs as DAO.RecordSet

While that was accurate many moons ago, since the release of Access 2007+ (15+ years now) you should really be using the newer “Microsoft Office XX.X Access database engine Object Library” (the ACE library) instead.  So remove the DAO reference and instead include the ACE library instead.  You don’t need to change anything in your code whatsoever, just switch the reference and compile your code again.

Inserting New Records

If you are looking for how you can Insert new records, you may wish to look over my article

A Few Resources on the Subject

One response on “Access Recordset Looping

  1. J Stribling

    Daniel,

    Wow. I wish I had found your site sooner. I have been writing VBA for over 20 years, and I can still learn much from you. Your code is solid and your tips on optimization are excellent.