So I have a system built in which it sets a few different flags and so on and so forth, but one of the things I want to do is take the contents of a staging table and send it over to another table used for tracking. I'm trying to do it using an insert into loop but I simply cannot figure out how to make it work as intended.
Private Sub Form_Load()
DoCmd.SetWarnings False
DoCmd.OpenQuery ("qryDeleteEmail")
Dim db As Object
Dim rst As Object
Dim test As Object
Set db = Application.CurrentDb
Set rst = db.OpenRecordset("qryDate")
Set test = db.OpenRecordset("tblEmailTemp")
If Me.RecordsetClone.RecordCount = 0 Then
MsgBox ("No delinquent accounts. No email will be generated.")
Me.Refresh
DoCmd.Close acForm, "qryDate", acSaveNo
DoCmd.CancelEvent
Else
rst.MoveFirst
Do Until rst.EOF
rst.Edit
rst!NeedsEmail = 1
rst.Update
rst.MoveNext
Loop
'DoCmd.Requery
'rst.Close
DoCmd.RunMacro ("StagingTable")
test.MoveFirst
Do Until test.EOF
CurrentDb.Execute "Insert Into EmailTracking (Account, ExpirationDate)" & _
"Values ('" & AccountName & "', '" & ExpirationDate & "')"
test.MoveNext
Loop
test.Close
rst.MoveFirst
Do Until rst.EOF
rst.Edit
rst!EmailSent = 1
rst.Update
rst.MoveNext
Loop
'DoCmd.Requery
rst.Close
DoCmd.RunMacro ("Close")
'DoCmd.OpenQuery ("qryDeleteEmail")
End If
Exit Sub
End Sub
What's happening right now is it's copying the first record of the staging table twice. For instance I have an account name A and an account name S, but instead of inserting the record for A and the record for S, it is simply inserting A twice.
Any help would be greatly appreciated!
Create and test a simpler procedure which is narrowly focused on the issue you're trying to solve. Unfortunately, I'm not sure what that issue is. I'll suggest this anyway ...
Public Sub TestLoopThruTable()
Dim db As DAO.database
Dim test As DAO.Recordset
Dim strInsert As String
DoCmd.SetWarnings True ' make sure SetWarnings is on
Set db = CurrentDb
Set test = db.OpenRecordset("tblEmailTemp")
Do While Not test.EOF
strInsert = "INSERT INTO EmailTracking (Account, ExpirationDate)" & vbCrLf & _
"VALUES ('" & AccountName & "', '" & ExpirationDate & "')"
Debug.Print strInsert
'db.Execute strInsert, dbFailOnError
test.MoveNext
Loop
test.Close
Set test = Nothing
Set db = Nothing
End Sub
Notice in your original version there was no space between ExpirationDate) and Values. I used a line break (vbCrLf) instead of a space, but either will keep the db engine happy.
I made sure SetWarnings is on. In your code, you turned it off at the start but never turned it back on again. Operating with SetWarnings off suppresses important information which you could otherwise use to understand problems with your code.
As that code loops through the recordset, it simply creates an INSERT statement and displays it for each row. You can view the output in the Immediate window (go there with the Ctrl+g keyboard shortcut). Copy one of those INSERT statements and test by pasting into SQL View of a new Access query. If it fails there, figure out what you need to change to satisfy the db engine. If the INSERT succeeds, try executing them from your code: enable the db.Execute line by removing the single quote from the start of that line.
The way you wrote the VALUES clause, it appears [ExpirationDate] is a text field. However if its data type is actually Date/Time, don't include quotes around the value you're inserting; use the # date delimiter instead of quotes.
Also make sure to include Option Explicit in the Declarations section of your code module like this:
Option Compare Database
Option Explicit
I mentioned that point because in an earlier version of this question you showed Option Compare but not Option Explicit. Trying to troubleshoot code without Option Explicit is a waste of time IMO.
I am not sure to understand what you are trying to do here; it is hard to understand what ErrorHandler is doing in the Else statement (even if commented).
As far as looping through a recordset goes, I advice you to read a little bit about the basis of VBA programmation in MS-Access. You can start by reading the articles below. It is a quick introduction about VBA recordsets and then the most common mistakes in VBA.
http://allenbrowne.com/ser-29.html
http://www.techrepublic.com/blog/10things/10-mistakes-to-avoid-when-using-vba-recordset-objects/373
It should help you improving your code.
Related
I want users to be able to provide a query they made in the GUI, using a combo box, and then load that query into a recordset to do further processing on it. This fails if the query contains a user-defined function or form-based parameter.
My code looks like this:
Private Sub cmbSelectionColumn_AfterUpdate()
Dim r As DAO.Recordset
Set r = CurrentDb.OpenRecordset("SELECT DISTINCT " & EscapeSQLIdentifier(Me.cmbSelectionColumn.Value) & " FROM " & EscapeSQLIdentifier(Me.cmbSelectionTable.Value))
Do While Not r.EOF
'Do stuff
r.MoveNext
Loop
End Sub
Where cmbSelectionColumn is a user-selected column, and cmbSelectionTable is a user-selected table or query, and EscapeSQLIdentifier is a function that escapes and adds brackets to ensure the field and tablename are safe. This mostly works fine, but it fails in multiple cases, such as involving pass-through queries, user-defined functions, and form-based parameters.
Is there a way I can create a recordset from any query that works in Access, without having to worry about this?
Yes, there is, but you will have to do some trickery.
Forms support these queries just fine. And forms have a .RecordsetClone property that allows us to retrieve the recordset.
To allow us to retrieve the recordset from code, we're going to create a new blank form, and add a module to it (in fact, any form with a module will do). We'll name it frmBlank.
Then, we can adjust the code to use this form to retrieve the recordset.
Private Sub cmbSelectionColumn_AfterUpdate()
Dim r As DAO.Recordset
Dim frm As New Form_frmBlank
frm.RecordSource = "SELECT DISTINCT " & EscapeSQLIdentifier(Me.cmbSelectionColumn.Value) & " FROM " & EscapeSQLIdentifier(Me.cmbSelectionTable.Value)
Set r = frm.RecordsetClone
Do While Not r.EOF
'Do stuff
r.MoveNext
Loop
End Sub
This allows us to retrieve the recordset. The form will not pop up (since we haven't set .Visible to True), and will close once the code is done running since there is no active reference to it. I haven't yet seen any tables or queries that do work in Access, but do not work with this approach, and the performance penalty is minor. It does make for odd code and an odd blank form with blank module that will cause your database to malfunction when deleted.
The following may present an alternative approach to opening DAO recordsets which reference form-based parameters:
Dim db As DAO.Database
Dim pr As DAO.Parameter
Set db = CurrentDb
With db.CreateQueryDef("", "SELECT DISTINCT " & EscapeSQLIdentifier(Me.cmbSelectionColumn.Value) & " FROM " & EscapeSQLIdentifier(Me.cmbSelectionTable.Value))
For Each pr In .Parameters
pr.Value = Eval(pr.Name)
Next pr
With .OpenRecordset
If Not .EOF Then
.MoveFirst
Do Until .EOF
' Do stuff
.MoveNext
Loop
End If
.Close
End With
End With
Here, since references to objects outside of the scope of the query (such as references to form controls) become query parameters whose parameter name matches the original reference, the parameter name is evaluated to yield the value held by the form control, and the parameter value is then updated to the result of this evaluation.
The problem I'm facing:
I try to check if inserted text from multiple text boxes is already existing in a table before saving the records to avoid duplicates.
I created a form to enter new members and save them into a table. The key to avoid duplicates is to check the combination of given name, last name and birth date with existing records. (It's most likely that there won't be two person with all three criteria matching)
I have no problem to check the existence for only one text box by setting the focus on the desired box and use the SQL query IF EXISTS...
But since I would need to set focus on several text boxes(IMO) the problem occurs.
Is there a way to set focus on multiple text boxes?
The idea would be to use an IF EXISTS...AND EXISTS statement and I would need to implement the .SetFocus statement for each text box before checking its existence.
I hope you get my point and I would be glad if someone could share some knowledge. :)
Thanks in advance
There seems to be some missing information in order to find the best solution to your problem. so the below response will be based on assumptions as to how your form is working.
I'm assuming you are using an unbound form with unbound text boxes? if this is the case, then you must have a button that is the trigger for checking/adding this information to your table. lets say your command button is called "Save". You can use the following code without the need to .setfocus to any textbox.
Private Sub Save_Click()
Dim db as DAO.Database
Dim rst as DAO.Recordset
Dim strSQL as string
set db = currentdb 'This is the connection to the current database
'This is the SQL string to query the data on your table
strsql = "SELECT * " & _
"FROM [Yourtablename] " & _
"WHERE ((([YourTableName].[FirstName]) ='" & me.FormFirstNameField & "' AND ([YourTableName].[LastName]) ='" & me.FormLastNameField & "' AND ([YourTableName].[DOB]) =#" & me.FormDOBField & "#));"
set rst = db.openrecordset(strsql) 'This opens the recordset
if rst.recordcount <> 0 then
'Enter code to inform user information already exists
else
'Enter code if information does not exits
end if
rst.close 'Closes the recordset
set rst = nothing 'Frees memory
set db = nothing 'Frees Memory
End Sub
Let me know if this code works or if I need to make changes based on your scenario.
I'm getting a 2498 error and really don't understand why. I'm building a string in VBA and am getting the error with the following line of code...
DoCmd.OpenQuery qdfNew, acNormal
It happened with a very long string created to create the query so I simplified the code as much as possible and am still getting the error.
Here's the code...
Option Compare Database
Option Explicit
Dim dbsFootball As Database
Dim strInsertSQL, strSelectSQL, strIntoSQL, strFromSQL, strOrderSQL, strSQL As String
Dim qdfNew As QueryDef
Sub CreateFormattedData()
Set dbsFootball = CurrentDb()
strSelectSQL = ""
strIntoSQL = ""
strFromSQL = ""
strOrderSQL = ""
strSQL = ""
strSelectSQL = "SELECT [tbl_Raw_League_Data].[Season]"
strIntoSQL = "INTO [tbl_Manip Data]"
strFromSQL = "FROM [tbl_Raw_League_Data]" _
+ "LEFT JOIN Referees ON [tbl_Raw_League_Data].[Referee] = Referees.[Referee from Source Data]"
strSQL = strSelectSQL + " " + strIntoSQL + " " + strFromSQL + " " + strOrderSQL
On Error Resume Next ' If query doesn't exist, error won't stop execution
DoCmd.DeleteObject acQuery, "pgmqry_Create Table tbl_Manip"
On Error GoTo 0 ' Reset error handler
Set qdfNew = dbsFootball.CreateQueryDef("pgmqry_Create Table tbl_Manip", strSQL)
DoCmd.OpenQuery qdfNew, acNormal
End Sub
The source field, [tbl_Raw_League_Data].[Season], is a "Short Text" data type (field size = 7).
If I terminate the VBA code and run the query that was created by the code, it works fine with no apparent errors. However, it will never run the query from within the VBA code.
I was originally getting the error 2498 when using "INSERT INTO" for an append query, but realized that the table could as easily be recreated at code execution time.
I'm lost and would sure appreciate some ideas!
Thanks in advance,
Jason
You are passing the querydef object to DoCmd.OpenQuery when it expects a string referencing name of a stored query object. Consider using the querydef's Name property:
DoCmd.OpenQuery qdfNew.Name, acNormal
Alternatively, use .Execute command from database object using the SQL string variable, bypassing any need for querydef:
dbsFootball.Execute strSQL, dbFailOnError
Or with querydef object, as #HansUp suggests, where you simply execute directly since it is an action query:
qdfNew.Execute dbFailOnError
Do note above two options bring up the regular MS Access discussion, of using stored vs VBA string query. While the former is precompiled and runs through query optimizer caching best plan, the latter can have sql dynamically created (structural components that is like SELECT, FROM and JOIN clauses as both can use passed in parameters). From your code snippet consider saving SQL query beforehand without needing to build it in VBA on the fly, and call it with DoCmd.OpenQuery.
I'm working on running an append query (to change someones login password) in access, currently it checks to make sure the password matches and that they knew the original password. However, I run into a problem where if the update query: DoCmd.RunSQL "UPDATE Credentials SET Credentials.[Password] = [Forms]![LoginSubmit]![DesiredPassword] WHERE (((Credentials.Password)=[Forms]![LoginSubmit]![CurrentPassword]) AND ((Credentials.[User ID])=[Forms]![LoginSubmit]![User ID]));" updates 0 rows (meaning that the conditions were not met) it closes (I have a docmd.close following). If it does update their password it does the same, so in the end you don't know if it was successful until you try your new password.
I would like to know when it updates 0 rows so I can shoot up a MsgBox telling them it didn't work, end the sub and have them try again. If it updates 1 row I would have a MsgBox say "Success".
I know the VBA to the above except for the part that returns a value for the amount of updated rows in that query. Could someone help me out?
Thanks in advance.
Execute your UPDATE statement from a DAO.Database object variable. Afterward you can base your user notice on the variable's RecordsAffected property.
Here is a simple example, verified with Access 2007.
Dim db As DAO.Database
Dim strUpdate As String
strUpdate = "UPDATE tblFoo" & vbCrLf & _
"SET some_text = 'bar'" & vbCrLf & _
"WHERE id = 7;"
Debug.Print strUpdate
Set db = CurrentDb
db.Execute strUpdate, dbFailOnError
MsgBox db.RecordsAffected & " records affected"
Set db = Nothing
Hey guys, can anyone help me with a simple question. I have this SQL statement below in a sub in VB within access from a button click, and I am new to this.
Here is what I typed:
Private Sub Command0_Click()
Dim rs As Recordset
Dim sql As String
Dim db As Database
Set db = CurrentDb
sql = "SELECT * FROM Transactions"
Set rs = db.OpenRecordset(sql)
Do Until rs.EOF
rs.MoveNext
Loop
If Not rs.EOF Then
MsgBox "test"
End If
End Sub
Ok, so how do I populate this?? Essentially I am justing starting out with this, so I am wondering how do I take this simple code, and run it like a query so that the resulting recordset opens.
Thanks!
some other remarks and advices:
1) Always indicate which type of recordset you are using. Here it seems to be a DAO recordset, so go for a complete declaration like:
Dim rs as DAO.recordset
Runing on another computer, and depending on the declaration order of ADODB and DAO libraries, the very same code can generate a bug.
2) To avoid any disturbing error message if no record is available, you can add an extra test, something like
if rs.recordcount = 0 then
Else
rs.moveFirst
....
3) To browse the complete recordset with debug.print, you could do it this way. Just ad a 'm_debugLine' as string, and a 'fld' as DAO.Field in your declarations.
rs.MoveFirst
do while not rs.eof
m_debugLine = ""
for each fld in rs.fields
m_debugLine = m_debugLine + vbTab + fld.value
next fld
debug.print m_debugLine
rs.movenext
loop
4) you could even add a debug.print line to print out the field names before printing the data. I guess you'll find this one
Depending on what you are trying to do, you may be over-complicating this. A better approach would be to set the recordsource of the form (in the property sheet) to the transactions table then drop the fields you want on the form using the visual designer.
HOWEVER, If you really must do it this way, here is the code that will replace what you have and open a spreadsheet like view of the data in the transactions table.
Private Sub Command0_Click()
docmd.Opentable "transactions"
End Sub
If you want to limit the results to a query, then first build the query and save it then use the following code.
Private Sub Command0_Click()
docmd.OpenQuery "MyQueryName"
End Sub
To be extremely literal, your original code DID populate a recordset (in the rs object). You can access the fields by name using code in your while loop such as
debug.print rs("Field1")
You put your code inside the Do..Loop. This code will be evaluated for each record that is encountered.
Do Until rs.EOF
Msgbox "The value for MyField is " & rst!MyField
rs.MoveNext
Loop
you get at the columns of the record for the recordset like rs(0) or rs("columnname")....
if your transactions table has three columns named a, b, c you could get to it like:
rs(0)
rs(1)
rs(2)
or
rs("a")
rs("b")
rs("c")