GUI using VBA in access. Recordset from query - vba

Making GUI in Access with VBA (first time i saw it in this semester and it looks unusual for me). I got table Authors where i got columns author_id, last_name, first_name and table Books with columns author_id, book_name.
I got button on Form which on click should ask user to input author last name and then search and show all books of this author.
So I trying to find author id from Authors table and then from Books table show all books where author.author_id is equal to books.author_id.
I was thinking that i need to create temp query which contained author_id value and after that create record set with this query using SQLquery like "SELECT [Books].[book_name] AS [Bookname] FROM [Books] WHERE [Books].[author_id] = [test].[ID]" But i stucked here - I trying to just check if this thing working but it says there is an error 3061
Private Sub authorlist_Click()
Dim dbs As Database, authorsRS, booksRS As Recordset, queryStr, idbynameQuery, srchASurStr, strOutput, srId As String, qdf As QueryDef
Set dbs = CurrentDb()
srchASurStr = InputBox("Input author surname, please", , , 100, 100)
strQuery = "SELECT [Authors].[author_id] AS [ID] FROM [Authors] WHERE [Authors].[last_name] = " & srchASurStr & ""
Set authorsRS = dbs.OpenRecordset(strQuery, dbOpenSnapshot)
With dbs
Set qdf = .CreateQueryDef("test", strQuery)
DoCmd.OpenQuery "test"
.QueryDefs.Delete "test"
End With
End Sub
So could you help me please to understan what's wrong? And is there maybe more simple way to show all books of some author (maybe without using SQL querys)?

String values in an SQL statement need to be surrounded with single-quotes (') or double-quotes ("):
SELECT author_id FROM authors WHERE last_name = "Smith"
If written without the quotes:
SELECT author_id FROM authors WHERE last_name = Smith
Smith will be understood to be a field name and not a string value. So your code should look something like this:
'Chr returns a string from a character code. 34 is the code for "
strQuery = "SELECT author_id FROM authors WHERE last_name = " & Chr(34) & srchASurStr & Chr(34)
In VBA, you can escape double-quotes with a string by doubling them:
strQuery = "SELECT author_id FROM authors WHERE last_name = """ & srchASurStr & """"
SQL injection: Keep in mind that if the user inputs a string with " in it, there will probably be an error, as the resulting SQL statement has invalid syntax:
SELECT author_id FROM authors WHERE last_name = "Smi"th"
The right way to avoid this problem is to use parameters.
Some notes:
You can reference a form control within a query: [Forms]![FormName]![ControlName]. Thus, you can create a saved query that filters based on a form textbox, instead of using an inputbox.
Consider using a combobox to have the user select from a list, instead of having the user type free text. The combobox can have multiple columns, with the value of the combobox being the first column (author_id) and the displayed value being another expression (last_name or last_name & " " & first_name). If you set the ColumnWidths property to 0 (for the first column), the next column will be displayed
If you prefer to use a textbox, consider using the LIKE operator in your query, to display all authors whose last_name contains the user string:
SELECT author_id FROM authors WHERE last_name LIKE "%sm%"
will return Smith, Smythe, and Asmodeus.

I suggest you set up a form and subform. The form can contain author details and the subform can contain books by that author, you can further add a textbox that the user can fill in with part of the author name. You can then apply a filter to the main form to show all the authors with that name.
Me.Filter = "Author Like '*" & Me.Search & "*'"
Me.FilterOn = True
There are a number of variations on this, the user could select names from a combo or listbox. The form could be a continuous form with a filter / search in the header and so on.

Related

SQL Injection from a textbox

I'm new to MS Access.
So, I wrote a SQL query(query name = qryEmployeeInfo) which shows employee information. The query outputs two columns. The employee ID(header name = employee_ID) and the corresponding employee address(header name = employee_address).
My Access form has a text box(text box name = txtEmployeeID) that I want the user to be able to enter the employee_ID into and have it output the corresponding employee_address into another text box (text box name = txtEmployeeAddress). I also want the employee_address to be in the format of a string variable so I can perform other VBA checks on it later(for example - if LIKE "California" THEN...something).
I want to write what (I think) is called an injection SQL query so that I can pull the address data from the query for that specific employee_ID. I believe the format should look like this:
Dim rs As Recordset
Set rs = CurrentDb.OpenRecordset("select employee_address from qryEmployeeInfo where employee_ID = "' & txtEmployeeID & "'", dbOpenDynaset)
Do I have this written correctly?
If so, then how do I get that output into a string variable format(variable name = strEmployeeAddress)?
After I get the employee address into a string variable format I want to simply use txtEmployeeAddress.value = strEmployeeAddress to populate the employee address text box. Again, I also want the employee_address to be in the format of a string variable so I can perform other VBA checks on it later(for example - if LIKE "California" THEN...something).
Any help you could provide would be greatly appreciated.
If employee_ID is a number field, remove apostrophe delimiters.
If employee_ID is a text field, move first apostrophe so it is within quote marks.
"select employee_address from qryEmployeeInfo where employee_ID = '" & txtEmployeeID & "'"
Then txtEmployeeAddress = rs!employee_address
However, instead of opening a recordset object, could just use DLookup.
txtEmployeeAddress = DLookup("employee_address", "qryEmployeeInfo", "employee_ID =" & txtEmployeeID)
Or even better, eliminate the VBA. The DLookup() expression can be in textbox ControlSource property, just precede with = sign.
However, domain aggregate functions can perform slowly. So instead of textbox, use a combobox for selecting employee. Include employee information in combobox RowSource. Expression in textbox references column of combobox: =combobox.Column(1). Issue is combobox has limit on how many characters can be pulled into column so if field is memo (long text) type, this approach is not feasible and should use DLookup.
The address will be available in textbox for use as long as this form is open. If you want the address to be available to other procedures even after form is closed, then need to set a global or public variable. Such variable must be declared in a module header to make it available to multiple procedures. TempVars are another way to hold values for future use. I have never used them.

Passing multiple criteria to query in access via VBA code

I am working on a Access database that I inherited from the person working on it before me. I am novice at this and learning as I go along.
Background:
I am working with a database of experimental data.
I am trying to create a search and export form which will allow the the user to search for a particular data set and then export that to an excel sheet. It will also let the user filter the search so as to write only specific values into the excel file.
My data base has 2 main sub tables differentiating the kind of experiments.
I use a list box that allows the user to choose which table he/she wants to search.
based on this selection two additional listboxes get populated with the relevant options
the options are "Author Name" and a "Number ID"
Upto this point everything works
if table 1 is selected one set of author names and number ids are populated in the list boxes and the same is true if table 2 is selected.
the author name and also the number id list boxes are simple multiselect.
I loop over the list box selection to get all the values selected
I then create the SELECT and IN statement.
This works like a charm for the number id (when i execute with only 1 list box).
However I get an error if i try to create a query statement as follows:
SELECT * FROM tbl1 [Number_ID] IN (a,b,...) AND
SELECT * FROM tbl1 [AUTHOR_NAME] IN ("xyz", "abc",....)
where Number_ID is an Integer and
Author_Name is text
Solution:
strSQL = "SELECT * FROM tbl1 WHERE "
strWhere = "[Number_ID] IN (1, 2, 3, ...)"
strSQL = strSQL & strWhere
strSQL1 = " AND "
strWhere1 = "[Author_Name] IN ("xyz", "abc", ...)"
strSQL = strSQL & strSQL1 & strWhere1
This generates a query string as follows
"SELECT * FROM tbl1 WHERE [Number_ID] IN (1, 2, 3, ...) AND [Author_Name] IN
("xyz", "abc", ...)"
which can be passed to your qryDef variable to create a query.

Access 2010 set variable to single field of a table

I feel like this should be easy, but I haven't been able to come up with a clear answer for this after searching off and on all day.
I have a users table that has email addresses in it.
I have a combo box that references this table.
All I want to do, is set the email address field of the selected user to a string, so I can then do things with that.
just trying to get a string back from a sql query like :
"SELECT emailAddress FROM tblUsers WHERE id = " & Me.cmbUser.Value & ""
Can someone point me in the right direction here?
You can use Combo Box to return email address after user select user from list.
Assume your user tblUsershas User and Email fields
Set your combo box property:
1.Row Source = SELECT User, Email FROM tblusers
2.Column Count = 2
3.Bound Column = 0 (0 is the first column and 1 is the second column which is email)
4.Column width = x";0" (x is your combox box width)
You can get email address from me.combobox.column(1). Both me.combobox.value and me.comboxbox.column(0) is selected User
If you actually want to execute the query, you have multiple options:
You can use DLookup:
TL;DR:
DLookup("Column", "Table", "Col=Value") will execute SELECT Column FROM Table WHERE Col=Value and return the first row.
For the query from your question, you need to use DLookup like this:
Dim mail As String
mail = Nz(DLookup("emailAddress", "tblUsers", "id = " & Me.cmbUser.Value))
You can load your SQL query with a Recordset.
This makes more sense than DLookup if you need more user data from the table than just the email adress.
Dim mail As String
Dim phone As String
Dim RS As DAO.Recordset
Set RS = CurrentDb.OpenRecordset("SELECT emailAddress, phoneNumber FROM tblUsers WHERE id = " & Me.cmbUser.Value)
If Not RS.EOF Then
mail = Nz(RS("emailAddress"))
phone = Nz(RS("phoneNumber"))
End If
RS.Close
Set RS = Nothing
(in order for this to work, your Access database needs a reference to any version of the Microsoft DAO Object Library...this should already be the case in most newer Access versions)
Note the usage of Nz in both examples - this is necessary if the mail adress can be NULL. Without Nz, the code would crash because of setting a string variable to NULL.

How to use a INSERT INTO SELECT query in Access VBA?

I have one table called Student, which contains information on a student such as their name.
Another table called Exam which has a date the exam was taken and the name of a student as the primary key.
I have a form that can be used to select multiple students from a list box that will then be inserted into the Exam table on the date selected.
I believe my syntax is correct because if I use Access's query builder and copy/paste my SQL query and get rid of the form stuff it will work as expected.
The error I get when I try to run it from VBA is that Exam.Exam_Date is unknown and to check my spelling. I spell it how it is spelled in the table.
Is it possible to use an INSERT INTO SELECT query within VBA in Access?
Here is my code:
Private Sub add_Click()
Dim Students As String
Dim i As Integer
Dim dbs As DAO.Database
Dim SQL As String
SQL = ""
Students = "'"
For i = 0 To Me.StudentListBox.ListCount - 1
'check to see if students name is selected
If Me.StudentListBox.Selected(i) = True Then
'list student names in a string separated by commas
Students = Students + CStr(Me.StudentListBox.ItemData(i)) & "','"
End If
Next
If IsNull(Me.ExamDate) Then 'check if user entered an Exam date
MsgBox "Please select a date for the Exam."
ElseIf Students = "'" Then 'check if user selected ant Students
MsgBox "Please select Students to add to an Exam."
Else
'remove trailing comma
Students = Left(Students, Len(Students) - 2)
'sql query to add list of Students to an Exam on specified date
SQL = "INSERT INTO Exam (Exam.Exam_Date, Exam.Student_Name) SELECT '" & CDate(Me.ExamDate) & "', Students.Full_Name FROM Students WHERE Students.Full_Name IN (" & Students & ");"
DoCmd.RunSQL SQL
End If
End Sub
I understand you may have already found your issue but I do want to point out some other items.
It is a very interesting setup building the WHERE IN clause. Alternatively, you could have iterated an INSERT INTO ... VALUES inside the For/Next loop:
INSERT INTO Exam (Exam_date, Student_Name)
VALUES(#" & Me.ExamDate & "#, '" & Me.StudentListBox.ItemData(i) & "')
Also check your Exam_Date field. From your query it looks like you retain date as a string field but if date/time field, VBA queries require # # instead of single quotes. Also no need for conversion functions, CStr or CDate, if already formatted to these date types by the form.
Finally, for database design recommendation, you should use StudentID inside the Exam table instead of relating both tables by Full_Name: better indexing, primary/foreign key referential integrity, data storage efficiency. Plus, if names have quotes no need for escaping or misspellings and integer values is safer in managing data between tables (i.e., duplicates, lookup).

MS access SELECT INTO in vba

I'm having some issues with some functionality of my application. There is a particular instance where I have an instance of a 'pending class' on a form for an administrator to review. The form is populated with students associated with this pending class. After their grades are finished, I have a button at the footer that will delete this class from my 'pending' table and add the grades to all of the students. This works.
However, I want to essentially copy this pending class, which just has the class name, date, and teacher to a completed class table before it's deleted from pending. Since no data about this class other than the primary key(class number) persists throughout this form, I can't populate the other fields(class name, date) of the row into my completed class table.
I am trying a "SELECT INTO" operation in VBA to get these values. It's going like this:
dim cname as String
dim classdate as Date
dim pid as integer
dim teacher as String
dim qry as String
pid = [Forms]![frmClasses]![txtID]
qry = "Select className INTO cname FROM tblPending WHERE tblPending.id = " & " ' " & pid & " ' " & ";"
db.execute qry
debug.print qry
debug.print cname
From here, I do the same operations for each other variable, build my INSERT query, and execute it. The problem is-- my select into's are not working. Debug.print shows that the local variables were never initialized from the SELECT INTO statement. Any thoughts?
First, having all classes in one table and just setting a "NotPending" or "Completed" column would be better.
Having two identical tables for classes and moving values from one into the other to indicate status changes is bad database design.
If you really need to do this by using two tables and copying rows, then you need an INSERT INTO query (and not SELECT INTO), as already mentioned by Remou in the comments, because SELECT INTO creates a new table (or overwrites an existing one with the same name, if already there).
The syntax for INSERT INTO looks like this:
INSERT INTO CompletedClassTable (ClassName, Teacher)
SELECT ClassName, Teacher FROM tblPending WHERE id = 123
And finally, you asked this in a comment:
So SELECT INTO is completely different in Access than Oracle? In Oracle and PL/SQL, you can select a row into a variable OR a table. In Access can you not select into a variable?
To load a row into a variable, you need to use a Recordset.
Example code to load your query into a Recordset and output the ClassName field:
Dim RS As DAO.Recordset
Set RS = CurrentDb.OpenRecordset("SELECT * FROM tblPending WHERE id = 123")
If Not RS.EOF Then
Debug.Print RS("classname")
End If
RS.Close
Set RS = Nothing
Seems you want to retrieve a text value, className, from tblPending where tblPending.id matches the value found in your text box, txtID, and store that text value in a string variable named cname.
If that interpretation is correct, you needn't bother with a query and recordset. Just use the DLookup Function to retrieve the value, similar to this untested code sample.
Dim cname As String
Dim pid As Integer
Dim strCriteria As String
pid = [Forms]![frmClasses]![txtID]
strCriteria = "id = " & pid
cname = Nz(DLookup("className", "tblPending", strCriteria), vbNullString)
Debug.Print "cname: '" & cname & "'"
Notes:
I assumed the data type of the id field in tblPending is numeric. If it is actually text data type, change strCriteria like this:
strCriteria = "id = '" & pid & "'"
DLookup() returns Null if no match found. Since we are assigning the function's return value to a string variable, I used Nz() to convert Null to an empty string. Alternatively, you could declare cname As Variant (so that it can accept a Null value) and get rid of Nz().