Rookie SQL inside of VB question - MSAccess 2003 - sql

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")

Related

Can I open a recordset using application-level features (user-defined functions, form-based parameters) in Access?

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.

Looping through all items in a MS Access Table and using each as input for a function

I am trying to figure out the best (an most efficient) way to loop through each record in my MS Access table and use a column of data as an input parameter for a another function.
Please see the code I was using below:
Public Sub FL()
Dim rs As DAO.Recordset
Set rs = CurrentDb.OpenRecordset("codes")
'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
'Save CPT code into a variable
CPT = rs!CPT
CMS_Util.psps_util_json ("CPT")
'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
End Sub
I pulled this code off SO and tried to modify it by my public sub CMS_Util.psps_util_json is not running at all. The loop of saving each CPT in the variable works fine and the CMS_Util.psps_util_json runs perfect when ran alone.
I believe my first mistake is using a Do Unitl loop versus a For Each loop but I hit a wall in what I could find.
FYI, the function that it is calling is designed to pull data from an API on CMS.gov and then calls another function that parses the JSON. I do not think that will effect the solution but I wanted to provide all the context I had. Thanks!
Working with large sets of data in applications slows down execution tremendously. Rather read the data to an array and work with the data in the array
Something like this :
Dim Data_Arry as variant
Dim rs As DAO.Recordset
Set rs = CurrentDb.OpenRecordset("codes")
Data_Array = rs.GetRows(rs.RecordCount)
Then work with data in the array, You should be able to assign back using the same logic
PS - I am not sure if my syntax above is correct.

MS Access VBA code

I am new to MS Access and would like to type EmployeeID in the text box 1 (text_in) and after the button is pressed the result query (one unique value e.g. employee first name taken from the table) is printed out in the text box2 (text_out).
So far have the following code:
Private Sub btn_get_data_Click()
Dim db As DAO.Database
Dim rs As DAO.Recordset
'declaration of database on recordset objects
Dim strSQL As String
Dim a As String
a = text_in.Value
Set db = CurrentDb
Set rs = db.OpenRecordset("Employee")
strSQL = "SELECT Employee.FirstName FROM Employee WHERE Employee.EmployeeId=" & a
Set db = Nothing
Set rs = Nothing
End Sub
I have tried searching for a solution in many places but I cannot understand the structure that is used in MS access VBA to implement query from regular SQL language.
Guys, thank you very much! It took me one more hour to sucessfully implement both solutions into my database file. As I wrote I am completely new to Ms-access I am learning using some tutorials but my level is still low. Thank you very much once again.
Private Sub btn_get_data_Click()
on error goto errorCatch
Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim strSQL As String
Set db = CurrentDb
strSQL = "SELECT Employee.FirstName FROM Employee WHERE Employee.EmployeeId=" & Me.text_in.Value & ";"
Set rs = db.OpenRecordset(strSql, DbOpenDynaset)
If rs.RecordCount > 0 Then
debug.print; rs!FirstName
Me.text_out.Value = rs!FirstName
End if
Cleanup:
Set db = Nothing
Set rs = Nothing
Exit Sub
errorCatch:
debug.print; err.Number
debug.print; Err.Description
GoTo Cleanup
End Sub
I cant tell what youre after is this. You need to be better at utilizing recordsets and how to use a string value as your sql statement.
You also didnt need to create a variable to store the textbox value in- you can use it directly in your sql statement.
Also it is very important to have good error handling as hanging recordset objects can be quite a pain in the ass.
EDIT - Ill expand on other ways to use string sql statements within VBA
DoCmd.RunSQl strSql
Or
DoCmd.Execute strSql
Both of these are great for UPDATE's or INSERT's.
Then, like Gustav pointed out, you have various D functions that are basically compact queries with some limitations. However, they often save you the trouble of having to do all the typing that is involved with opening,utlizing and closing records sets. Even though the D functions seem limited, I have often nested a few to get join results out of. Just a matter of imagination.
Look at this nifty site for function details -
https://www.techonthenet.com/access/functions/index.php
You don't need a query to do this. Just set the ControlSource of text2:
=DLookup("FirstName","Employee","EmployeeId=" & [text_in] & "")

Loop Not Running Through Entire Table

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.

Why do I get Run-time Error 91 in this MS Access code?

So, I have a MS Access database application. In this application is a main form, which contains a number of subforms. One form in particular has a drop down box that I populate with dates from a database query. When one of these dates is selected, I run a subroutine that is supposed to update a recordset on the subform with history information. Below is some edited code (just removed the large number of fields from the queries)
Private Sub pickdate_AfterUpdate()
'''''''''''''''''''''''''''''''''''''''''
' Add review history by selected date
'''''''''''''''''''''''''''''''''''''''''
Dim rs As Recordset
Set rs = CurrentDb.OpenRecordset("SELECT model, entered_date FROM history WHERE entered_date=#" & Me.pickdate.value & "# ORDER BY model DESC", dbOpenDynaset, dbSeeChanges)
If rs.BOF = False Then rs.MoveFirst
While rs.EOF = False
Forms!main!histories.Form.Recordset.AddNew
Forms!main!histories.Form.Recordset![model] = rs![model]
Forms!main!histories.Form.Recordset![entered_date] = rs![entered_date]
Forms!main!histories.Form.Recordset.Update
rs.MoveNext
Wend
End Sub
I get the error on the Forms!main!histories.Form.Recordset.AddNew line.
I have tried the following versions of that line:
Forms!main!histories.Form.Recordset.AddNew
main!histories.Form.Recordset.AddNew
histories.Form.Recordset.AddNew
Me.Form.Recordset.AddNew
Me.Recordset.AddNew
Me.AddNew
Me.main!histories.Form.Recordset.AddNew
Me!histories.Form.Recordset.Addnew
Me!main!histories.Form.Recordset.AddNew
I am literally at my wit's end trying to figure out where the issue is.
The subform has all the proper boxes to store the information. I have given them labels to match their database columns that will go into them. I've tried setting their control sources to the database column names and not setting them to anything. I've looked up a hundred different "solutions", none of which seem to either fit the problem or work.
I feel like I am overlooking something really easy.
I reckon you have problems with your names. Check all of them. Do not forget that a subform consists of two parts, the subform control and the form contained. These often have the same name, but not always. In the code you are using, you must have the name of the subform control, not the form contained. If entering data into the subform manually is not working properly, your controls are not bound.
This works for me on a sample table.
Dim rs As Recordset
Set rs = CurrentDb.OpenRecordset("SELECT atext from table1 WHERE akey=21")
If rs.BOF = False Then rs.MoveFirst
While Not rs.EOF '= False
Me.Table1_subform1.Form.Recordset.AddNew
Me.Table1_subform1.Form.Recordset!AText = rs!AText
Me.Table1_subform1.Form.Recordset.Update
rs.MoveNext
Wend
To run a query you could say:
sSQL="INSERT INTO NameOfTable (model, entered_date) " _
& "SELECT model, entered_date FROM history WHERE entered_date=#" _
& Me.pickdate.value & "#"
CurrentDB.execute, dbfailOnError
You can check the sql works in the query design window.