tags:

views:

626

answers:

6
 set recordsetname = databasename.openrecordset(SQLString)
    if recordsetname.bof <> true and recordsetname.eof <> true then
    'do something
    end if

2 questions :

  1. the above test can evaluate to false incorrectly but only extremely rarely (I've had one lurking in my code and it failed today, I believe for the first time in 5 years of daily use-that's how I found it). Why very occasionally will one of bof/eof be true for a non-empty recordset. It seems so rare that I wonder why it occurs at all.

  2. Is this a foolproof replacement:

    if recordsetname.bof <> true or recordsetname.eof <> true then
    

Edit to add details of code :

Customers have orders, each order begins with a BeginOrder item and end with an EndOrder item and in between are the items in the order.

The SQL is:

' ids are autoincrement long integers '
SQLString = "select * from Orders where type = OrderBegin or type = OrderEnd"           

Dim OrderOpen as Boolean
OrderOpen = False

Set rs = db.Openrecordset(SQLString)
If rs.bof <> True And rs.eof <> True Then
    myrec.movelast
    If rs.fields("type").value = BeginOrder Then
         OrderOpen = True
    End If
End If

If OrderOpen F False Then
    'code here to add new BeginOrder Item to Orders table '
End If

ShowOrderHistory 'displays the customer's Order history '

In this case which looks this this

BeginOrder
Item a
Item b
...
Item n
EndOrder

BeginOrder
Item a
Item b
...
Item n
EndOrder

BeginOrder
Item a
item b
...
Item m

BeginOrder     <----should not be there as previous order still open
A: 

The pattern I have always used is:

Set rs = db.OpenRecordset(...)

Do while Not rs.EOF

    ' Rest of your code here.

    rs.MoveNext
Loop

I have never seen this fail (yet!). This is described here: How to: Detect the Limits of a DAO Recordset

As an aside, Allen Browne's VBA Traps: Working with Recordsets might be of interest.

Mitch Wheat
thanks Mitch, I believe thatyour answer implies that bof is never true for a non empty recordset (since no movefirst ) and it further implies that eof is never true or else the loop will not be entered so it's the same as my one that I thought would never fail but which appears to have.
jjb
Can you narrow down the exact circumstances? I've never seen this fail with DAO (2.7 +)
Mitch Wheat
Mitch, I'm no genius at logic so correct me if I'm wrong. Let bof be T1 and 'NOT bof' F1 and eof T2 and 'NOT eof' F2 So my conditional that appeared to fail is (F1 AND F2) Allen Browne advocates 'Not (T1 AND T2)' which encompasses 3 possibilities (F1 AND F2), (F1 AND T1) and (T1 AND F2). So it's like he assuming that either bof or eof can occur in a non empty recordset. Incidentally I believe that 'NOT( T1 AND T2)' is equivalent to (F1 OR F2). Is this correct?
jjb
the circumstances were a shared .mdb file with 6 concurrent connections, it was a simple select on a single table with a value for one column in the where clause. Really simple and should have returned a few records. Glaring and immediately obvious anomaly followed and all attempts to reproduce based on well remembered sequence of steps have failed
jjb
using dao 3.5 i think
jjb
+2  A: 

The documentation clearly states that, if you open a Recordset that has no records:

  • BOF will be true
  • EOF will be true
  • RecordCount will be 0

For a non-empty Recordset, neither BOF and EOF are true until you move beyond the first or last record.

Could it be that, from time to time, someone else could have added/deleted a record to one of the tables in the recordset you're just opening and change the resultset?
It could be the result of a race condition.

Rather than use BOF or EOF, you can test on Recordcount: it's always 0 if the recordset is empty.
If the recordset is not empty, it will usually return 1 right after the recordset has been open; Recordcount isn't an expensive operation in that case.
The only way to really return the actual number of records is to issue a MoveLast before calling Recordcount to force all records to be loaded.

Usually, if I need to iterate through a resultset in read-only fashion:

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

Set db = CurrentDB()
Set rs = db.OpenRecordSet("...", dbOpenForwardOnly)
If Not (rs Is Nothing) Then
    With rs
       Do While Not .EOF
            ' Do stuff '
            .MoveNext
       Loop
       .Close
    End With
    Set rs = Nothing
End If
Set db = Nothing

If I don't need to iterate through records but just test if anything was returned:

Set rs = db.OpenRecordSet("...", dbOpenForwardOnly)
If Not (rs Is Nothing) Then
    With rs
       If .RecordCount > 0 Then
          ' We have a result '
       Else
          ' Empty resultset '
       End If
       .Close
    End With
    Set rs = Nothing
End If
Set db = Nothing

It's pretty defensive and you have to adapt to your circumstances, but it works correctly every time.

Regarding your 2nd question, testing (BOF Or EOF) after opening the recordset should be more foolproof than the And version, although I'd use Recordcount myself.

Edit following your revised question:

From the bit of code you added to your question, I see a couple of issues, the main one being that your SQL Statement is missing and ORDER BY clause.
The problem is that you are expecting the resultset to be in the Begin Order followed by End Order sequence but your SQL Statement doesn't guarantee you that.
In most cases, since you're using an autoincrement as ID, the database engine will return the data in that natural order, but there is no guarantee that:

  • It's always going to happen that way
  • That the original data was saved in the expected sequence, resulting in IDs that are in the 'wrong' order.

So, whenever you have expectations about the sequence of the resultset, you must explicitly order it.

I would also refactor this bit of code:

' ids are autoincrement long integers '
SQLString = "select * from Orders where type = OrderBegin or type = OrderEnd"           

Dim OrderOpen as Boolean
OrderOpen = False

Set rs = db.Openrecordset(SQLString)
If rs.bof <> True And rs.eof <> True Then
   myrec.movelast
    If rs.fields("type").value = BeginOrder Then
        OrderOpen = True
    End If
End If

Into a separate function similar to:

' Returns true if the given CustID has a Open Order, '
' false if they are all closed.'
Public Function IsOrderOpen(CustID as Long) As Boolean
    Dim result as Boolean
    result = False

    Dim sql as String
    ' Here I assume that the Orders table has a OrderDateTime field that '
    ' allows us to sort the order in the proper chronological sequence '
    ' To avoid loading the complete recordset, we sort the results in a way '
    ' that will return the last used order type as the first record.'
    sql = sql & "SELECT Type " 
    sql = sql & "FROM Orders "
    sql = sql & "WHERE ((type = OrderBegin) OR (type = OrderEnd)) "
    sql = sql & "      AND (CustID=" & CustID & ")"
    sql = sql & "ORDER BY OrderDateTime DESC, Type DESC;"

    Dim db as DAO.Database
    Dim rs as DAO.Recordset
    Set db = CurrentDB()
    Set rs = db.Openrecordset(sql, dbOpenForwardOnly)

    If Not (rs Is Nothing) Then
        If rs.RecordCount > 0 Then
            result = (rs!type = BeginOrder)
        End If
        rs.Close
    End If

    Set rs = Nothing
    Set db = Nothing

    IsOrderOpen = result
End Function

This would make the whole thing a bit more robust.

Renaud Bompuis
Thanks very much for this
jjb
Renaud, you probably won't believe me but there is a "order by ID" in the SQL. The reason I saying that is that for me the core issue is whether a non-empty recordset can open with the pointer pointing at either bof or eof and I just want to rule out the option that maybe the records were returned in incorrect order. The problem I have is that if this isn't the case I can't see any answer in my code to why this occurred. I googled this and came across 1 or 2 other old examples where this seems to have happened to others. I also ran a 800,000 recordset simulation and it didn't happen once
jjb
I was really just hoping to be able to close this bug off with an explanation that it was caused by the expressionrs.bof <> true and rs.eof <> trueevaluating to false when there were actualy records present.Then I could sleep easier!
jjb
A: 

This is DAO, right? I'm more an ADO man myself but IIRC there are circumstances (dynaset?) where you need to navigate EOF in order for the final number of rows to be assessed. Could it be in this state that EOF is true, BOF is false (because it hasn't been navigated yet) but as soon as BOF is navigated it is true (obviously) and EOF remains true. Presumably the initial state when zero rows are expected is supposed to be instantaneous but a once-in-five-years freak timing incident means you captured it in a really early initial state?

onedaywhen
A DAO recordset's recordcount cannot be counted on to be accurate. If you need an accurate recordcount, you do a .MoveLast. But that does *not* take the record pointer past the end of the recordset, so this should have nothing to do with it.
David-W-Fenton
"But that does *not* take the record pointer past the end of the recordset" -- ah yes, to do that you must invoke the little-known .MovePastEOF method which does take you past the end of the recordset, where you find yourself in Hilbert's Grand Hotel where all the amplifiers go to 11.
onedaywhen
It takes you to the last record, and when you're on the last record, you're not EOF. If you then issued a MoveNext, that would make EOF true. So, I don't quite get why you're making light of my point, as it's patently true.
David-W-Fenton
To paraphrase Spinal Tap: You're on EOF on your recordset. Where can you go from there? Where? Nowhere. Exactly. What we do is, if we need that extra push over the cliff, you know what we do? We take the record pointer past the end of the recordset. _Why don't you just make *that* point the EOF of your recordset?_ [pause] This pointer goes past the end of the recordset.
onedaywhen
To be less obscure: you said, "But that does *not* take the record pointer past the end of the recordset" This is indeed true because, if you take the time to think about it, that is impossible. If there were a navigable point beyond the end of the recordset then one could make a good case for calling *this* the end of the recordset. This could proceed infinitely, like accommodating guests in Hilbert's paradoxical Grand Hotel. Instead, recordsets are finite and end at the EOF; there are no means to navigate past, no point beyond that you can reach.
onedaywhen
+1  A: 

@Renaud Bompuis's answer is quite good. Let me emphasize the point that the DAO Recordcount is never zero for a non-empty recordset, and that is the only thing I ever test in determining if a recordset has returned records. I use .EOF for looping through the records, but don't start stepping through the records until I've already tested if there are records returned.

David-W-Fenton
Looks like testing recordcount is the way to go for dao recordsets then.Just one question, do you think it is possible or have you ever seen a case where a dao recordset has been opened with the pointer pointing to the bof or the eof when the recordset is not empty?
jjb
I don't test BOF and EOF with recordsets EVER, so, no, I've never seen that. My point is that you're worrying about the WRONG THING and thus running the wrong test. Secondly, you're testing two properties when you can get the same information from testing one. Seems like a complete no-brainer to me -- lose BOF and EOF as methods for testing for an empty DAO recordset.
David-W-Fenton
Thanks david. I take your point but it was the cause of a specific error that was worrying me. I was pinning my hopes on the evaluation of (rs.bof <> true AND rs.eof <> true) being 'wrong'.Incidentally as I plan to move migrate from Jet to SQL server at some stage I'll have to move from DAO to ADO and I don't want to introduce tests that are DAO specific where I can help it. As I understand it the value of recordcount for an empty ADO recordset may be different from the DAO equivalent. So I'll probably go with NOT(rs.bof and rs.eof) to keep the code more widely applicable?
jjb
If you're going to use ODBC linked tables with SQL Server, then you had best stick with DAO. If you're not going to use ODBC, then you might as well chuck your whole app and start over from scratch.
David-W-Fenton
Suggesting that rewriting the whole code base would be necessary to allow a change from DAO to ADO sounds really extreme.
jjb
That's what you have to do to get the "benefit" of switching. ADO with SQL Server is quite useful, but only with an unbound app. If you're using bound forms you've got to have linked tables (or bind to ADO recordsets, which can lead to all sorts of oddities). Access shines with bound forms, and thus, the best way to use it is with linked tables. That means Jet/ODBC, and that means DAO is the best data interface. If you are hostile to bound forms, then I say you're hostile to the basic design of Access and should use something else entirely.
David-W-Fenton
DWF almost a year has gone by and I still am using DAO and bof/eof rather than recordcount but I still plan to move at some stage :)Anyway re-reading this has just evoked this random thought. Why do you say that recordcount is more reliable than using bof/eof? As you say you only step through the records once you've established that the recordcount > 0. Maybe if your code is defensively well written you will never observe it when it happens. What Donald Rumsfield would call "an unknown unknown"? BTW I've not seen this error since and only very few times ever
jjb
Philosophically speaking, a single test is superior to a supposedly equivalent pair of tests. Also, it's quite clear from the existence of this question that there are empty recordsets that don't test True for both EOF and BOF at the same time. Seems like a no-brainer to me.
David-W-Fenton
A: 

Here's a possible solution

It could be that your form or module has gotten corrupted. Export/Import the affected module or form, or try the /decompile option. In my case a query was coming back empty when it shouldn't have, but I think the core problem could be similar.

DGM
+1  A: 

I occasionally come across the exact same bug in access (had it today in Access 2007 linked to a sql server back end) where the statement

if rst.bof and rst.eof

evaluates to false despite rst representing an empty recordset. When it happened, VBA started and the debugger in the immediate pane showed that, indeed rst.bof was true and rst.eof was true, so it seems to happen for a millisecond and then is corrected, but after one has tested the logic.

Dan
Have you read the other answers? Testing .Recordcount=0 for an empty DAO recordset is more reliable and simpler. Also, you might consider the difference between TRUE and NOT FALSE. Give that FALSE is 0 in all Boolean representations, testing for NOT FALSE is always going to work more reliably than testing for TRUE.
David-W-Fenton
Dan, Your situation is similar but different. Mine was where "bof <> true and eof <> true" evaluated to false when the set was not empty.This has only ever happened to me when I used boolean AND rather than OR in the expression I posted. Still our situations look related. Is it us or Access!!DWF: That's a very interesting observation and given that "if rs.bof" is more concise than "if rs.bof <> false" I'd guess that there are not many people doing it. In my own case I got into the habit (for no good reason) of writing "bof <> true" rather than "bof = false". Maybe I need to change that.
jjb
My view is that this is a bug deep in access where an asynchronous process is returning control to our VBA code before it has properly set bof and eof. I think the reason that certain constructs such as if rst.bof <> false and rst.eof <> FALSE may work on occasion when others, such as if not rst.eof is that this code is compiled differently and might alter the timing. Although the bof/eof test "should" work, my experience is that it is, albeit rarely, prone to failure. I replaced all my empty recordset tests with if rst.recordcount<=0 (per Allen Browne) and this is working for DAO.
Dan