Access VBA Loop (Not Responding) - ms-access

I am looping through a recordset to carry out some basic functions or edits.
Usually with recordsets with more than 50 records, access will stop responding.
I have me.repaint before the loop command but the window always freezes and the access title bar shows: ...(Not Responding).
Any idea how to get around this?
Thanks.
Dave.
EDIT: Added Loop Code
If Me.Dirty = True Then Me.Dirty = False
Dim rs As DAO.Recordset
Set rs = Me.Guardians_Subform1.Form.Recordset
Dim strFirstName, strLastName As String
If Not (rs.EOF And rs.BOF) Then
rs.MoveFirst
Do Until rs.EOF = True
rs.Edit
strFirstName = Trim(StrConv(rs!FirstName, 3))
strLastName = Trim(StrConv(rs!LastName, 3))
If rs!FirstName <> strFirstName Then
rs!FirstName = strFirstName
End If
If rs!LastName <> strLastName Then
rs!LastName = strLastName
End If
rs.Update
rs.MoveNext
Me.Repaint
Loop
Else
MsgBox "There are no records in the recordset."
End If
Set rs = Nothing

You need to call the DoEvents-Function within the loop to pass control to the operating system to redraw your Access-GUI and to process any other Window-Messages that might need processing. By that the application will not be marked as "Not responding" in the Task Manager and the Title Bar.
Do Until rs.EOF = True
[...]
rs.MoveNext
DoEvents
Loop
There is a small performance trade off. If not calling DoEvents, the total execution time for the loop will be a little shorter, but Access will do nothing else then process your loop. Therefore it seems to be not responding.

As others have pointed out you can use the DoEvents to release your processor to do other actions before continuing. When I use the DoEvents in a loop I use a counter.
Dim iCounter as Integer
Do Until
' some code here
iCounter=iCounter+1
If iCounter = 100 then
DoEvents
iCounter=0
End if
Loop
This keeps the DoEvents from firing too often and causing your overall code to slow. Adjust the counter to whatever iteration you find appropriate.

It is not the best approach to do such edits with a recordset loop. An UPDATE query is much more efficient.
e.g.
UPDATE tblGuardians
SET FirstName = Trim(StrConv(FirstName, 3))
WHERE StrComp(FirstName, Trim(StrConv(FirstName, 3)), 0) <> 0
and the same for LastName.
This uses StrComp instead of a simple <> comparison, because the latter is case-insensitive. The third parameter 0 = vbBinaryCompare.

Related

MS Access VBA .recordcount returning 0 when records exist, and debug.print returns value

I have an Access table with 10 records and one field of short text. I am using the .recordcount function to return the number of records in this table. Code below:
Dim db as DAO.Database
Dim RS as DAO.Recordset
Dim recCount as Integer
Set db = CurrentDb
Set RS = db.OpenRecordset("Table Name")
RS.MoveFirst
RS.MoveLast
recCount = RS.recordcount
Debug.Print(recCount)
Dim i as Integer
i = 0
RS.MoveFirst
'Option one. Commented out when option two is active and vice verse
Do While i < 10
Debug.Print(RS(i))
i = i + 1
Loop
Do While i < 10
Debug.print(RS![Only Field Name])
i = i + 1
RS.MoveNext
Loop
recCount always prints out to be 0. Attempting to print out the records in the recordset will return the first value only of the recordset and nothing else. After reading the first record, the program throws the error "Item not found in collection." I'm unsure of what could be causing this error, as I use the exact same method with another table in another VBA module, which works just fine.
I look at solutions to this elsewhere and the only one I could find was to add a RS.moveFirst and RS.moveLast after opening, however this does not work. I think this is becuase the opened recordset does not actually contain all the records in the table.
Thanks in advance.
EDIT:
Try this:
Sub Demo_IterateRecords()
Const tblName = "YOUR TABLE NAME HERE"
Dim rs As Recordset
Set rs = CurrentDb.OpenRecordset(tblName)
With rs
.MoveLast
.MoveFirst
If MsgBox("Do you want to list all " & .RecordCount & " records?", _
vbOKCancel, "Confirmation") <> vbOK Then GoTo ExitMySub
Do While Not .EOF
Debug.Print .Fields(0), .Fields(1), .Fields(2)
rs.MoveNext
Loop
ExitMySub:
.Close
End With
Set rs = Nothing
End Sub
I used .Fields(_) because I'm not sure what your fields are called, but a better way to refer to them would be by name, like:
Debug.Print !myID, !myEmployeeName, !myEmployeeAddress
Original Answer:
Try this:
RS.MoveLast
RS.MoveFirst
recCount = RS.RecordCount
Debug.Print(recCount)
Access doesn't know how many records there are until you move through them at least once.
If you would have checked the value of RS.RecordCount after your loop, you would have got a number.
Remarks
Use the Recordcount property to find out how many records in a Recordset or TableDef object have been accessed. The RecordCount property doesn't indicate how many records are contained in a dynaset–, snapshot–, or forward–only–type Recordset object until all records have been accessed. Once the last record has been accessed, the RecordCount property indicates the total number of undeleted records in the Recordset or TableDef object. To force the last record to be accessed, use the MoveLast method on the Recordset object. You can also use an SQL Count function to determine the approximate number of records your query will return.
Important Note
Using the MoveLast method to populate a newly opened Recordset negatively impacts performance. Unless it is necessary to have an accurate RecordCount as soon as you open a Recordset, it's better to wait until you populate the Recordset with other portions of code before checking the RecordCount property.
(Source)
See also: MSDN : Recordset.RecordCount Property
I managed to fix this issue but I have no idea why this worked. Instead of creating a new table and typing in the values for the ten records, I instead used an insert query to put the values I wanted from a query into a table. Using this new table, it worked.
You could list the records while counting:
Set RS = db.OpenRecordset("Table Name")
While Not RS.EOF
Debug.Print RS![Only Field Name].Value
i = i + 1
RS.MoveNext
Loop
Debug.Print i & " records found."
Perhaps you could try something similar to this in your sub routine:
Dim db As DAO.Database
Dim RS As DAO.Recordset
Dim recCount As Integer
Set db = CurrentDb
Set RS = db.OpenRecordset("Table Name")
If Not (RS.EOF And RS.BOF) Then
RS.MoveFirst
Do Until RS.EOF = True
RS.MoveNext
Loop
MsgBox ("There are:" & " " & RS.RecordCount & " " & "records in the database")
End If
RS.Close
Set RS = Nothing

VBA Access - Checking recordset on subform load

I have an On_Load() sub which checks the records present on a subform, record by record. For example, if the subform loads with 12 records on it, I need the sub to start with the first record, run a DCount (it checks if the job number appears on a different table), then move to the next record, and check that one, etc until it reaches the last record. Here's my code at the moment:
Set rst = Me.RecordsetClone
On Error Resume Next
rst.MoveFirst
'Put code to check keyword schedule here. First get job no
Do Until Me.Specific_Job_No.Value = "00"
strSpec = Format(Me.Specific_Job_No.Value, "00")
strJob = Left(Me.Parent.JobRef.Value, 18) + strSpec
'Then check if that job no is in slot 1, then 2, etc
If DCount("*", "tblKeywordsSchedule", "[Slot1] Like ""*" & strJob & "*""") > 0 Then
Me![Added to Schedule] = True
Me![Added to Schedule].Locked = True
Else
Me![Added to Schedule] = False
Me![Added to Schedule].Locked = False
End If
'Then go to next record
rst.MoveNext
Loop
My problem is, it gets stuck on rst.MoveNext and just keeps checking the first record over and over again. What am I doing wrong?
Your problem is that you cannot lock a field individually for each record.
So the [Added to Schedule] may change its locking during the loop but will keep the setting of the last record in the loop.
I have now sorted it out myself. Found TheSmileyCoder's answer on this page:
https://bytes.com/topic/access/answers/942501-looping-through-subform-records
I was referring to a form control (Me!) rather than the recordset clone to update the strSpec and strJob strings - that was all I needed to know.
If rst.RecordCount > 0 Then
With rst
rst.MoveFirst
Do While Not .EOF
strSpec = Format(rst![Specific Job No], "00")
strJob = Left(Me.Parent.JobRef.Value, 18) + strSpec
'Then check if that job no is in slot 1, then 2, etc
If DCount("*", "tblKeywordsSchedule", "[Slot1] Like ""*" & strJob & "*""") > 0 Then
.Edit
rst![Added to Schedule] = True
.Update
Else
.Edit
rst![Added to Schedule] = False
.Update
End If
.MoveNext
Loop
End With
End If

Find DAO Record

Is it possible to use DoCmd.GoToRecord or DoCmd.FindRecord in order to quickly find a record in a table, edit the record and get the focus on that record (I want to start looping from that record later)?
I believe such method (if applicable) would be faster than looping through the entire recordset (especially with a large recordset).
Assuming the Primary key is 9999 (Fields(0) = 9999), I have tried:
Dim rs as DAO.Recordset
Set rs = CurrentDb.OpenRecordset("Tbltest")
DoCmd.FindRecord "9999", acEntire, True, acSearchAll, True
Debug.Print rs.Fields(0)
I get "1"; the method failed.
With a DAO.Recordset, you use the rs.FindFirst and rs.FindNext methods.
Set rs = CurrentDb.OpenRecordset("Tbltest", dbOpenDynaset)
lngValue = 9999
rs.FindFirst "myPrimaryKey = " & lngValue
' start loop from there
If Not rs.NoMatch Then
Do While Not rs.EOF
Debug.Print rs(0)
rs.MoveNext
Loop
End If
If it's a local table, there is also the rs.Seek method, but if there is a chance that the table will some day be linked from a backend or server database, I suggest sticking with the Find methods.

How to write VBA with Do While Loop?

I have a table with 3 fields: DONOR_CONTACT_ID, RECIPIENT_CONTACT_ID, ORDER_NUMBER. I want to sort DONOR_CONTACT_ID in ascending order which I did with my query Q_RECIPIENT_SORT. Then I want to use temporary variables to check to see if the records have the same DONOR_CONTACT_ID and then display a message if they do (Most of the records have the same DONOR_CONTACT_ID). My program does everything it is supposed to, but at the end it always gets an error that says "No Current Record". Here is my code:
Option Compare Database
Option Explicit
Function UsingTemps()
Dim dbs As DAO.Database
Dim rst As DAO.Recordset
Dim strTemp1 As Long
Dim strTemp2 As Long
DoCmd.SetWarnings False
DoCmd.OpenQuery ("Q_RECIPIENT_SORT")
DoCmd.OpenTable ("T_RECIPIENT_SORT")
DoCmd.SetWarnings True
Set dbs = CurrentDb
Set rst = dbs.OpenRecordset("T_RECIPIENT_SORT", dbOpenTable)
rst.MoveFirst
strTemp1 = rst!DONOR_CONTACT_ID
rst.MoveNext
strTemp2 = rst!DONOR_CONTACT_ID
Do While Not (rst!DONOR_CONTACT_ID = rst.EOF)
If strTemp1 = strTemp2 Then
MsgBox ("Equal")
Else
MsgBox ("Not equal")
End If
strTemp1 = strTemp2
rst.MoveNext
strTemp2 = rst!DONOR_CONTACT_ID
Loop
Set dbs = Nothing
End Function
I think the problem is with the following lines:
rst.MoveNext
strTemp2 = rst!DONOR_CONTACT_ID
I think it is trying to move to the next record when there are no more records left. Probably something wrong with my logic. But I've been staring at it for a while and my changes haven't worked. I need another set of eyes to take a look at it.
Any help is appreciated!
Consider what happens when your recordset loop is on the last row, and you then do this ...
rst.MoveNext
strTemp2 = rst!DONOR_CONTACT_ID
MoveNext positions the recordset at EOF --- no record is "current". So, in the next line, the code attempts to store the value from the current row's DONOR_CONTACT_ID to strTemp2. However, since you're at EOF, no record is "current", so Access complains "No Current Record".
I think this version will avoid that error. Test the logic to make sure it also does what you need.
rst.MoveFirst
strTemp1 = rst!DONOR_CONTACT_ID
rst.MoveNext
'strTemp2 = rst!DONOR_CONTACT_ID
'Do While Not rst!DONOR_CONTACT_ID = rst.EOF
Do While Not rst.EOF
strTemp2 = rst!DONOR_CONTACT_ID
If strTemp1 = strTemp2 Then
MsgBox "Equal"
Else
MsgBox "Not equal"
End If
strTemp1 = strTemp2
rst.MoveNext
'strTemp2 = rst!DONOR_CONTACT_ID
Loop
The general idea is as this:
Set rst = dbs.OpenRecordset("T_RECIPIENT_SORT", dbOpenDynaset)
Do Until rst.EOF
'do or check what you want
'....
rst.MoveNext
Loop
rst.Close

Code to loop through all records in MS Access

I need a code to loop through all the records in a table so I can extract some data. In addition to this, is it also possible to loop through filtered records and, again, extract data? Thanks!
You should be able to do this with a pretty standard DAO recordset loop. You can see some examples at the following links:
http://msdn.microsoft.com/en-us/library/bb243789%28v=office.12%29.aspx
http://www.granite.ab.ca/access/email/recordsetloop.htm
My own standard loop looks something like this:
Dim rs As DAO.Recordset
Set rs = CurrentDb.OpenRecordset("SELECT * FROM Contacts")
'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
Do Until rs.EOF = True
'Perform an edit
rs.Edit
rs!VendorYN = True
rs("VendorYN") = True 'The other way to refer to a field
rs.Update
'Save contact name into a variable
sContactName = rs!FirstName & " " & rs!LastName
'Move to the next record. Don't ever forget to do this.
rs.MoveNext
Loop
Else
MsgBox "There are no records in the recordset."
End If
MsgBox "Finished looping through records."
rs.Close 'Close the recordset
Set rs = Nothing 'Clean up
In "References", import DAO 3.6 object reference.
private sub showTableData
dim db as dao.database
dim rs as dao.recordset
set db = currentDb
set rs = db.OpenRecordSet("myTable") 'myTable is a MS-Access table created previously
'populate the table
rs.movelast
rs.movefirst
do while not rs.EOF
debug.print(rs!myField) 'myField is a field name in table myTable
rs.movenext 'press Ctrl+G to see debuG window beneath
loop
msgbox("End of Table")
end sub
You can interate data objects like queries and filtered tables in different ways:
Trhough query:
private sub showQueryData
dim db as dao.database
dim rs as dao.recordset
dim sqlStr as string
sqlStr = "SELECT * FROM customers as c WHERE c.country='Brazil'"
set db = currentDb
set rs = db.openRecordset(sqlStr)
rs.movefirst
do while not rs.EOF
debug.print("cust ID: " & rs!id & " cust name: " & rs!name)
rs.movenext
loop
msgbox("End of customers from Brazil")
end sub
You should also look for "Filter" property of the recordset object to filter only the desired records and then interact with them in the same way (see VB6 Help in MS-Access code window), or create a "QueryDef" object to run a query and use it as a recordset too (a little bit more tricky). Tell me if you want another aproach.
I hope I've helped.
Found a good code with comments explaining each statement.
Code found at - accessallinone
Sub DAOLooping()
On Error GoTo ErrorHandler
Dim strSQL As String
Dim rs As DAO.Recordset
strSQL = "tblTeachers"
'For the purposes of this post, we are simply going to make
'strSQL equal to tblTeachers.
'You could use a full SELECT statement such as:
'SELECT * FROM tblTeachers (this would produce the same result in fact).
'You could also add a Where clause to filter which records are returned:
'SELECT * FROM tblTeachers Where ZIPPostal = '98052'
' (this would return 5 records)
Set rs = CurrentDb.OpenRecordset(strSQL)
'This line of code instantiates the recordset object!!!
'In English, this means that we have opened up a recordset
'and can access its values using the rs variable.
With rs
If Not .BOF And Not .EOF Then
'We don’t know if the recordset has any records,
'so we use this line of code to check. If there are no records
'we won’t execute any code in the if..end if statement.
.MoveLast
.MoveFirst
'It is not necessary to move to the last record and then back
'to the first one but it is good practice to do so.
While (Not .EOF)
'With this code, we are using a while loop to loop
'through the records. If we reach the end of the recordset, .EOF
'will return true and we will exit the while loop.
Debug.Print rs.Fields("teacherID") & " " & rs.Fields("FirstName")
'prints info from fields to the immediate window
.MoveNext
'We need to ensure that we use .MoveNext,
'otherwise we will be stuck in a loop forever…
'(or at least until you press CTRL+Break)
Wend
End If
.close
'Make sure you close the recordset...
End With
ExitSub:
Set rs = Nothing
'..and set it to nothing
Exit Sub
ErrorHandler:
Resume ExitSub
End Sub
Recordsets have two important properties when looping through data, EOF (End-Of-File) and BOF (Beginning-Of-File). Recordsets are like tables and when you loop through one, you are literally moving from record to record in sequence. As you move through the records the EOF property is set to false but after you try and go past the last record, the EOF property becomes true. This works the same in reverse for the BOF property.
These properties let us know when we have reached the limits of a recordset.