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

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

Related

Find data location based on Primarykey ID

I am very new to access and vba so apologies in advance for glaring conceptual gaps.
Basically I have a button that, when clicked, prompts a user to enter an ID number. The ID entered will correspond to one of my primary keys in a specific table (I have some checks in place to make sure the entered ID is valid).
I need my code to identify the row number of the ID entered and be able to populate a specific column in that row. I know Access does not refer to data locations using rows/cols but that is the best way I thought to describe it.
I am using Index/Seek/NoMatch to make sure the entered ID number is valid and I have been trying to find a way to also use those functions to generate the data location but have not had any luck. Any help would be greatly appreciated.
I think DLookup() function will work for your case. Suppose you have following dataset in table tblEmpInfo where EmpID is primary key field.
If you want to return employee name by entering employee id in a inputbox then use DLookup() like below-
Private Sub cmdDataLookup_Click()
Dim strEmpName As String, InputID As String
InputID = InputBox("Enter employee ID:", "Input ID")
strEmpName = DLookup("EmpName", "tblEmpInfo", "EmpID= '" & InputID & "'")
MsgBox strEmpName
End Sub
If you want to update any name by entering EmpID then use UPDATE SQL statement. Try below.
Private Sub cmdUpdateInfo_Click()
Dim strSQL As String, InputID As String
InputID = InputBox("Enter employee ID:", "Input ID")
strSQL = "UPDATE tblEmpInfo Set EmpName= 'Updated Value' WHERE EmpID= '" & InputID & "'"
CurrentDb.Execute strSQL
End Sub
Note: There is no direct way to get row number and column number of a Field in access but you can apply some trick by VBA to get row and column number of a Field. I will suggest to ask a separate question to get that.

Displaying a field associated with a max date value record

I am working on an MS Access database and have been trying to get an unbound field on a main navigation form to display the LastUserChange that is associated with the record that was last updated.
I have used DMax() to identify the record that was most recently updated, but I can't seem to get the user ID associated with that record to display. I have a field in the table with the date timestamp that stores the user ID with it, so the data is saved in the same table. The code that I have been working on is as follows:
Private Sub Form_Load()
Dim strSQL As String
strSQL = "SELECT tblstatusupdate.LastUserChange" & _
"FROM tblstatusupdate " & _
"WHERE tblstatusupdate.LastChangeDate = DMax("LastChangeDate", "tblStatusUpdate")"
DoCmd.RunSQL strSQL
Me.LastUpdateBy = strSQL
End Sub
The code that I used to get the date of the most recently updated record is:
= DMax("LastChangeDate", "tblStatusUpdate")
Can someone please help me?
Often a mistake of new users in MS Access, DoCmd.RunSQL is reserved for action queries (i.e., INSERT, DELETE, UPDATE, ALTER, CREATE) and not SELECT queries that return a resultset.
However, for your needs consider running nested domain functions in VBA without any SQL calls. DLookUp looks up user with the criteria that its change date matches the max value of table using Dmax. Date literals must be enclosed with # characters and not quotes.
Me.LastUpdatedBy = DLookUp("LastUserChange", "tblStatusUpdate", "LastChangeDate = #" _
& DMax("LastChangeDate", "tblStatusUpdate") & "#")

SQL statement for combobox row source

I'm trying to define a SQL statement to use as the Row Source for a ComboBox on an MSAccess form. The SQL should select records from a table tblI where a particular table field matches a variant parameter varS set by the user; however, if varS is Null or not present in another table tblS, the SQl should select all records in tblI.
I can code the first parts of this (varS matches or is null):
SELECT tblI.ID, tblI.S FROM tblI WHERE ((tblI.S = varS) OR (varS Is Null)) ORDER BY tblI.ID;
Where I'm struggling is incorporating the final element (varS not present in tblS). I can code a test for the absence of varS in tblS:
Is Null(DLookup("[tbls.ID]","tblS","[tblS.ID]= " & varS))
but I can't work out how to incorporate this in the SQL statement. Should this work?
SELECT tblI.ID, tblI.S FROM tblI WHERE tblI.S = varS OR varS Is Null OR DLookup("[tbls.ID]","tblS","[tblS.ID]= " & varS) Is Null ORDER BY tblI.ID;
When run as a query it returns every record in tblS no matter the value of varS.
Table structure:
tblI contains 2 fields, Autonumber ID and Long S
tblS contains 1 field, Autonumber ID
My own approach to this problem would be something like this:
Private Sub SetComboSource(vID as Variant)
Dim sSQL as String
sSQL = "SELECT tblI.ID, tblI.S " & _
"FROM tblI "
If IsNull(vID) = False Then
If IsNumeric(vID) = True Then
If DCount("ID", "tblS", "ID = " Clng(vID)) > 0 Then
sSQL = sSQL & "WHERE tblI.S = " & CLng(vID)
End If
End If
End If
sSQL = sSQL & " ORDER BY tblI.ID"
Me.cboComboBox.RowSource = sSQL
End Sub
BTW, I recommend you give your tables and fields more descriptive names and then use aliasing in your SQL, especially for table names. I also think it's best to avoid using Variant variables. I usually use Longs for something like this and I take a value less than 1 to mean that the user didn't select anything, or selected ALL, or whatever meaning you want to derive from it. In other words, my ID's are always a number greater than zero and an ID of less than 1 in a variable means that the ID is empty. Which I use as a signal to create a new record, or to return all records, or whatever meaning you want to derive from it in the given context.
The following should work;
SELECT tblI.ID, tblI.S
FROM tblI
WHERE tbl.ID=varS
OR varS NOT IN(SELECT ID from tblS)

GUI using VBA in access. Recordset from query

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.

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().