How to find next record based on combobox "filter" - ms-access

I have a form with two comboboxes, the first (cboSet) picks a class set, the second (cboName) displays student names within that set (the ID is hidden in the 1st column). I have a button which I would like to use to find the next student in that set. I have tried implementing a next button which will go to the next record but not the correct student. Any help would be much appreciated.

When you say next student, do you mean the student with the next numerical ID or are you using some other criteria for the next student?
here's one possible solution assuming that you want to filter your form with the next student. Without more details this example is somewhat vague. Seeing what you already have would make it more precise.
in the onclick event of your button:
Dim ID as Integer
ID = me.cboName.column(0) + 1
me.filter = "[ID] = " & ID
me.filteron = true
to find the next value in your set you could do something like the following, Of course this is only generic, basic (hopefully someone correct) code, so you'll have to change it to suit your needs, but it should point you in the correct direction.
dim db as dao.database
dim rs as dao.recordset
dim nextID as interger
dim SQLquery as string
nextID = 0
SQLquery = "SELECT TOP 1 ID from COMBO_RECORD_SOURCE WHERE ID > " & me.namecombo.column(0) & " ORDER BY ID"
set db = currentdb
set rs = db.openrecordset(SQLquery,dbOpenSnapshot)
if rs.recordCount > 0 THEN
rs.movefirst //I know it's not strictly necessary, old habit I like to do..
nextID = rs!ID
end if
if nextID > 0 then
me.filter = "[ID] = " & nextID
me.filteron = true
else
msgbox "There are no more names in this set."
end if
set rs = nothing
set db = nothing

Related

Access Tab Control - Set Pages Visibilty based on Subform Records

I know how to do this, but wondering if I might be able to write a more elegant solution. I have a form with a tab control. The control has 14 pages, each one, with it's own sub form. One of the pages (pgRequirements) has a subform of requirements, with a combo control "Requirement Type". It is a continuous form, so the user can add as many requirements as they want, for the main record.
There are 9 of those requirements, which have their own tab control page / sub form. I want to set visibility of those tab control pages, based on this parent's sub form requirements. So a current main record, can have multiple sub-requirement records. If any of those match e.g. requirement type A, than page A should be visible, otherwise it should not be.
I need this code to run anytime the main form is loaded, and the detail is made visible (meaning a main record has been chosen from a find form). Also anytime a requirement record is added or removed. The below is assuming that the parent-child links on the main form to subform will limit the requirement records, to just those that are for the current main record.
Here is the simple code, that will do the job, but is probably over-written:
If Me.FKRequirementType.Column(1) = "ReqType1" Then
Me.Parent!pgReqType1.Visible = True
Else
Me.Parent!pgReqType1.Visible = False
End If
If Me.FKRequirementType.Column(1) = "ReqType2" Then
Me.Parent!pgReqType2.Visible = True
Else
Me.Parent!pgReqType2.Visible = False
End If
If Me.FKRequirementType.Column(1) = "ReqType3" Then
Me.Parent!pgReqType3.Visible = True
Else
Me.Parent!pgReqType3.Visible = False
End If
If Me.FKRequirementType.Column(1) = "ReqType4" Then
Me.Parent!pgReqType4.Visible = True
Else
Me.Parent!pgReqType4.Visible = False
End If
Thanks!
EDIT
I turned this into a public function, so I can call it from anywhere. One problem. It's not working lol (small problem). I don't get any errors, but all the tab control pages are visible. When I add a new record, most of them should be hidden. I have a tblReqType table, with all the requirement types. I added a column to this, with the exact name of it's corresponding tab control page name, so I can loop through that table, for all records where that page name is not null, and set their page visible or not, based on the current main record ID having a record-requirement (cross reference table) record for each requirement type.
This is the public function I wrote. Can anyone help me understand what I'm missing in these loops for setting the visibility true (vtrue) vs setting the visibility false (vfalse)
Public Function ShowRequirements()
Dim db As DAO.Database
Dim strRstVTrue As String
Dim rstvTrue As DAO.Recordset
Dim strRstVFalse As String
Dim rstvFalse As DAO.Recordset
Dim strFieldName As String
'Setup the recordset
Set db = CurrentDb
strRstVTrue = "SELECT tblMRecordRequirements.ID, tblMRecordRequirements.FKMC, tblReqType.txtRequirementPage " & _
"FROM tblMRecordRequirements LEFT JOIN tblReqType ON tblMRecordRequirements.FKRequirementType = tblReqType.ID " & _
"WHERE tblReqType.txtRequirementPage Is Not Null AND tblMRecordRequirements.FKMC = " & Nz(Forms!frmMRecords!ID, 0)
strRstVFalse = "SELECT tblReqType.ID, tblReqType.txtRequirementPage, tblMRecordRequirements.FKMC " & _
"FROM tblReqType LEFT JOIN tblMRecordRequirements ON tblReqType.ID = tblMRecordRequirements.FKRequirementType " & _
"WHERE tblReqType.txtRequirementPage Is Not Null AND tblMRecordRequirements.FKMC <> " & Nz(Forms!frmMRecords!ID, 0)
Set rstvTrue = CurrentDb.OpenRecordset(strRstVTrue, dbOpenDynaset, dbSeeChanges)
Set rstvFalse = CurrentDb.OpenRecordset(strRstVFalse, dbOpenDynaset, dbSeeChanges)
strFieldName = "txtRequirementPage"
Do While Not rstvTrue.EOF
Forms!frmMRecords.tbMRecordSubs.Pages(rstvTrue.Fields(strFieldName)).Visible = True
Loop
Do While Not rstvFalse.EOF
Forms!frmMRecords.tbMRecordSubs.Pages(rstvFalse.Fields(strFieldName)).Visible = False
Loop
End Function
If anyone can help me figure out my stupidity, you deserve an up vote, a check mark, and a cookie.
EDIT again
Below is updated code for the public function. I fixed the rs for the true query, and I added in the MoveNext for the loops.
Public Function ShowRequirements()
Dim db As DAO.Database
Dim strRstVTrue As String
Dim rstvTrue As DAO.Recordset
Dim strRstVFalse As String
Dim rstvFalse As DAO.Recordset
Dim strFieldName As String
'Setup the recordset
Set db = CurrentDb
strRstVTrue = "SELECT tblMRecordRequirements.ID, tblMRecordRequirements.FKMC, tblReqType.txtRequirementPage " & _
"FROM tblMRecordRequirements LEFT JOIN tblReqType ON tblMRecordRequirements.FKRequirementType = tblReqType.ID " & _
"WHERE tblReqType.txtRequirementPage Is Not Null AND tblMRecordRequirements.FKMC = " & Nz(Forms!frmMRecords!ID, 0)
strRstVFalse = "SELECT tblReqType.ID, tblReqType.txtRequirementPage, tblMRecordRequirements.FKMC " & _
"FROM tblReqType LEFT JOIN tblMRecordRequirements ON tblReqType.ID = tblMRecordRequirements.FKRequirementType " & _
"WHERE tblReqType.txtRequirementPage Is Not Null AND tblMRecordRequirements.FKMC <> Is Null"
Set rstvTrue = CurrentDb.OpenRecordset(strRstVTrue, dbOpenDynaset, dbSeeChanges)
Set rstvFalse = CurrentDb.OpenRecordset(strRstVFalse, dbOpenDynaset, dbSeeChanges)
strFieldName = "txtRequirementPage"
Do While Not rstvTrue.EOF
Forms!frmMRecords.tbMRecordSubs.Pages(rstvTrue.Fields(strFieldName)).Visible = True
rstvTrue.MoveNext
Loop
Do While Not rstvFalse.EOF
Forms!frmMRecords.tbMRecordSubs.Pages(rstvFalse.Fields(strFieldName)).Visible = False
rstvFalse.MoveNext
Loop
End Function
EDIT REDUX
I think I may have it worked out, but let me know what you all think. I really appreciate all your thoughts on this, as I know you all have a lot of experience not just in figuring out these kinds of challenges, but ensuring the code is good and not prone to issues.
Here is where I am at:
Public Function ShowRequirements()
Dim db As DAO.Database
Dim db2 As DAO.Database
Dim strRstVTrue As String
Dim rstvTrue As DAO.Recordset
Dim strRstVFalse As String
Dim rstvFalse As DAO.Recordset
Dim strFieldName As String
strFieldName = "txtRequirementPage"
Set db = CurrentDb
Set db2 = CurrentDb
strRstVTrue = "SELECT tblReqType.txtRequirementPage " & _
"FROM tblReqType LEFT JOIN tblMRecordRequirements ON tblMRecordRequirements.FKRequirementType = tblReqType.ID " & _
"WHERE tblReqType.txtRequirementPage Is Not Null AND tblMRecordRequirements.FKMC = " & MCID
strRstVFalse = "SELECT tblReqType.txtRequirementPage " & _
"FROM tblReqType LEFT JOIN tblMRecordRequirements ON tblMRecordRequirements.FKRequirementType = tblReqType.ID " & _
"WHERE tblMRecordRequirements.ID Not In (Select ID From [tblMRecordRequirements] WHERE [tblMRecordRequirements]![FKMC] = " & MCID & _
") AND tblReqType.txtRequirementPage Is Not Null;"
Set rstvTrue = db.OpenRecordset(strRstVTrue, dbOpenDynaset, dbSeeChanges)
Set rstvFalse = db2.OpenRecordset(strRstVFalse, dbOpenDynaset, dbSeeChanges)
Do While Not rstvTrue.EOF
Forms!frmMRecords.tbMRecordSubs.Pages(rstvTrue.Fields(strFieldName)).Visible = True
rstvTrue.MoveNext
Loop
Do While Not rstvFalse.EOF
Forms!frmMRecords.tbMRecordSubs.Pages(rstvFalse.Fields(strFieldName)).Visible = False
rstvFalse.MoveNext
Loop
End Function
I need this code to run anytime the main form is loaded, and the detail is made visible (meaning a main record has been chosen from a find form). Also anytime a requirement record is added.
Put the code you shared inside a sub procedure and call the sub procedure from Form_Load(), Form_Current(), Form_AfterInsert() event handler, etc.
As for elegance, I'd focus on maintainability and efficiency rather than looks, but concise code is also nice. 1) You can use a With block to reduce redundant object method calls, but that'll only work for one reference at a time. 2) Instead create another variable to temporarily hold a value/object from a series of dot property-accessors. 3) It looks like pages and column values are already numbered with a consistent naming pattern, so leverage that in a loop. 4) Comparison operations in VBA are largely Boolean operations, so they return True or False. The result of an entire Boolean expression can be assigned to another Boolean variable/property. (Boolean operations can also return Null... which is usually, but not always, treated like False. If you're certain that your data doesn't have Null values, then you can simplify the code and ignore this issue. If the data can contain null, then you need to adjust the code appropriately.)
Me.Parent!pgReqType1 is calling the default property of the Parent form which is Controls, which default property is Item. The bang operator ! passes the code text as a string into the collection Item method. In short, it is equivalent to Me.Parent.Controls.Item("pgReqType1").
Dim i as integer
Dim ctls as Controls
Dim reqValue as string
Set ctls = Me.Parent.Controls
reqValue = Me.FKRequirementType.Column(1)
For i = 1 to 4
ctls.Item("pgReqType" & i).Visible = (reqValue = "ReqType" & i)
Next i
About all I can do is translate the specific code snippet you show. I have the feeling there is probably more to it than this, because the code snippet you shared ensures that there will only be one tab visible: It is testing the same column value multiple times which could only have one value. Bug? Incomplete example?
This really goes against my better judgement of Stack Overflow principles --to not answer multi-part, continuing debugging questions-- but I really want a cookie.
How could the posted code have ever worked, since you are not moving through either recordset? There are no MoveNext calls. That means that both recordsets are empty or an error is being thrown that is being ignored somewhere (i.e. On Error Resume Next). Otherwise, it should lock up Access with infinite loops. Sometimes you can stop the code with Ctrl+Break, but not always successful in Access.
More precise table schema is required to properly interpret your data, but I'll make some assumptions. You state that tblReqType contains all requirement types. I'll assume that tblMRecordRequirements contains rows only for requirements which are "applied" (a.k.a. "on", "selected") for the ID value in tblMRecordRequirements.FKMC. Assuming the converse, if there are no rows in tblMRecordRequirements with ID in tblMRecordRequirements.FKMC for a given tblMRecordRequirements.FKRequirementType, then the requirement is not "applied" to that ID.
Does every row in tblReqType have a value in txtRequirementPage, or do some rows have null values? Also, can multiple requirements have the same page specified? Or is it a true one-to-one requirement-to-page mapping with no null values?
First of all why would the first query not be an INNER JOIN, since I assume that only records that match in both tables should be returned for the Visible = True condition? Depending on your answers above, this would probably make the condition tblReqType.txtRequirementPage Is Not Null unnecessary in the first query.
Simply reversing a LEFT JOIN will not return what you want especially if you select all other ID values ( tblMRecordRequirements.FKMC <> Nz(Forms!frmMRecords!ID, 0) ). All that does is give you the requirements for every other ID values. Not only will that be inefficient since it could return many, many irrelevant records, it could be likely that over all other ID values that every possible requirement would be applied, so that the second query will essentially cause all requirements to be invisible.
Further picky observations:
If Forms!frmMRecords!ID is null, then you might as well not even execute the queries. You should check that value for null separately and perform appropriate actions rather than letting that specific condition fall through the other code, even if the end side effect is what you desire. It makes the code harder to debug and interpret properly. In other words write code that does "If ID is null, set all pages to visible = false, then exit the sub (i.e. skip other code)"
It's more efficient to get a read-only snapshot rather than an fully-updatable Dynaset recordset: Too much overhead just for looping through without data manipulation.
Proper break points, debug output and error handling code can help identify bad code. It is worth tracing through results of recordsets manually to inspect values and proper SQL syntax.
Try this:
Public Sub ShowRequirements()
Dim db As DAO.Database
Dim iID As Long '? Assuming long integer
Dim strSQL As String
Dim rsTabs As DAO.Recordset
On Error Resume Next
iID = -1 '* Set to bogus value
If Not IsNull(Forms!frmMRecords!ID) Then
iID = Forms!frmMRecords!ID
End If
If iID = -1 Or Err.Number <> 0 Then
'* Problem accessing ID control on form (empty recordset, new record row, etc.)
'* or it is null
'Set all tab pages to Visible = False?
Exit Sub
End If
On Error GoTo Catch
'* Setup the recordset
Set db = CurrentDb
'* Use embedded query (replacable with saved query) for filtering on ID values.
'* This is critical so that the LEFT JOIN does not return or filter records
'* based on other ID values.
strSQL = _
"SELECT tblReqType.ID, tblReqType.txtRequirementPage, (IDReq.FKRequirementType Is Not Null) As ShowTab " & _
" FROM tblReqType LEFT JOIN" & _
" (SELECT MReq.FKRequirementType FROM tblMRecordRequirements AS MReq " & _
" WHERE MReq.FKMC = " & iID & ") AS IDReq" & _
" ON tblReqType.ID = IDReq.FKRequirementType" & _
" WHERE tblReqType.txtRequirementPage Is Not Null"
Set rsTabs = db.OpenRecordset(strRstVTrue, dbOpenSnapshot, dbReadOnly)
Do While Not rsTabs.EOF
Forms!frmMRecords.tbMRecordSubs.Pages(rsTabs!txtRequirementPage).Visible = rsTabs!ShowTab
rsTabs.MoveNext '* Advance recordset. (Avoid infinite loops.)
Loop
CloseAll:
On Error Resume Next
'* Best habit to explicitly close recordsets and database connections, even when not necessary (since they'll close automatically when they go out of scope)
If Not rsTabs Is Nothing Then
rsTabs.Close
Set rsTabs = Nothing
End If
Set db = Nothing
Exit Sub
Catch:
'* At least report error for development
Debug.Print "ShowRequirements(): Error: " & Err.Number & ": " & Err.Description
'* Show MsgBox or update form status control?
'* Set all tab pages to Visible = False?
'* Form state could be misleading without proper user notification and/or error handling
Resume CloseAll
End Sub

Access 2013, Extract number from field to use in search

Have a sales proposal access database for work that has a field that you can put a earlier corresponding proposal number as a reference. If you click on a button under that field it will take you directly to that earlier record. There are times we have a prefix in front of the number A-12345, E-12345 or it might just be 12345.
I need to be able to grab just the number without the letter and - for the search to work correctly. Thanks
Here is the image of my screen
Assuming you have a table with columns Proposal and Reference and a single form with controls txtReference and txtProposal, put this code to the On_Click event of your form button (I'm using DAO):
Dim strProposal As String
Dim i As Integer
Dim rs As DAO.Recordset
If Len(Nz(Me.txtReference, "")) < 1 Then
MsgBox "No reference number entered"
Else
For i = 1 To Len(Me.txtReference)
If IsNumeric(Mid(Me.txtReference, i, 1)) Then
strProposal = strProposal & Mid(Me.txtReference, i, 1)
End If
Next
End If
Set rs = Me.RecordsetClone
rs.MoveFirst
rs.FindFirst "Proposal = '" & StrProposal & "'"
If rs.NoMatch Then
MsgBox "Original proposal not found"
Else
Me.Bookmark = rs.Bookmark
Me.txtProposal.SetFocus
End If
rs.Close
Set rs = Nothing

Get Record based on form textbox value

I am trying to get a record based on the value contain within the textbox on a form. i.e i type in the information into the textbox and other values associated with that value are returned to other textbox on the form.
I thought this would be easy but can't seem to get it to work.
Currently I was trying
Dim rst As DAO.Recordset
Dim SQL As String
Dim SQL2 As String
SQL = "SELECT tblmytbl.[IDCODE]"
"FROM tblmytbl " & _
"WHERE (((tblmytbl.[IDCODE]) = forms!myform!mybox.value "
Set db = CurrentDb
Set rst = db.OpenRecordset(SQL)
If Not ((rst.BOF = True) And (rst.EOF = True)) Then
Forms!myform!Text102 = rst.Fields("[Name]")
Forms!myform!Text103 = rst.Fields("[Surname]")enter code here
Note: The search information is alphanumeric and i have tried without the .value
Any help would be appreciated.
Thanks
The SQL you send to the server can't access the form. However, you can concatenate the value into the string that you send like:
" WHERE (((mytable.myfield) = '" & FixQuotes(Forms!myform!mybox.value) & "') " & _
Note, you may need to defend yourself against SQL injection, a simple (but not complete) defense would be something like:
Public Function FixQuotes(input as string) As String
FixQuotes = Replace(input,"'","''")
End Function
EDIT:
Based on your updated code, there's quite a number of changes you need to make. Beyond my statement above, the .OpenRecordset only applies to full tables, you can't use it with a SELECT statement. Instead, you have to instantiate a QueryDef. On top of that, you try to reference fields you didn't include in the query. Also, you can simplify the expression Forms!myform! to Me (which could help if you want to reuse the code somewhere else) So your code should look something like this:
Dim db as Database 'always dim everything, you should use Option Explicit'
Dim rst as Recordset 'DAO is the default anyway'
Dim qdf as QueryDef 'this object is required for queries'
Set db = CurrentDb
'prepare single-use query, to return the values you're going to use
'as mentioned before, the query doesn't have access to the form
'we can use Me since it references the form'
' use TOP 1 since you only expect 1 record'
Set qdf = db.CreateQueryDef("","SELECT TOP 1 Name,Surname FROM tblmytbl " & _
"WHERE IDCODE = '" & FixQuotes(Me.mybox.value) & "';")
Set rst = qdf.OpenRecordset(DbOpenForwardOnly)
'forwardonly since you only care about the first record'
If Not rst.EOF Then 'ForwardOnly has to start at the first record'
Me.Text102.Value = rst!Name
Me.Text103.Value = rst!Surname
'I highly suggest giving these boxes better names'
Else
'no record found'
End if
rst.Close
qdf.Close
db.Close 'close these objects, it can sometimes cause memory leaks otherwise'

How would I make a form which searches for values in all tables of a database in access

I am trying to make a form which searches for the value inside all of the tables in the database (there are more than 1 table). The result will be displayed as the name of the table which this appears in. If someone can help me that will be nice.
In short, I have a form with a textbox and button. I enter the search string (for example 183939) and click on the button. It searches the value (183939) inside all the fields in the tables in the database, and if the value is found, then it displays the name of the table that it appears in. Thanks for the help.
I think this is a bad idea because it could take a very long time, and provide confusing results due to also searching system tables... but the following function will return an array of all table names containing the search term or nothing if it wasn't found. Calling example is such: theTables = containingTable("hello") where theTables is a variant. A limitation is that this will fail for multi-valued fields.
Function containingTables(term As String)
Dim db As Database
Dim tds As TableDefs
Dim td As TableDef
Set db = CurrentDb
Set tds = db.TableDefs
For Each td In tds
For Each f In td.Fields
On Error Resume Next
If DCount("[" & f.Name & "]", "[" & td.Name & "]", "[" & f.Name & "] LIKE '*" & term & "*'") Then
If Err.Number <> 0 Then
Debug.Print Err.Number, Err.Description
Err.Clear
On Error GoTo 0
Else
containingTables = containingTables & td.Name & ","
Exit For
End If
End If
Next
Next
Set tds = Nothing
Set db = Nothing
'Alternate Version
if Len(containgingTables) then containingTables = Left(containingTables, Len(containingTables) - 1)
'Original Version
'if Len(containgingTables) then containingTables = Split(Left(containingTables, Len(containingTables) - 1), ",")
End Function
To display the results with the alternate version, just use: Msgbox(containingTables(searchTerm)) where searchTerm is whatever you are searching.
Me as well i don't know why you would want to do something like that...
I think the solution posted by Daniel Cook is correct, i just took a slightly different approach. Do you need to match the exact value like I do? Anyway, here's my code:
Function searchTables(term as String)
Dim T As TableDef
Dim Rs As Recordset
Dim Result() As String
Dim Counter
Counter = 0
For Each T In CurrentDb.TableDefs
If (Left(T.Name, 4) <> "USys") And (T.Attributes = 0) Then
Set Rs = T.OpenRecordset
While Not Rs.EOF
For Each Field In Rs.Fields
If Rs(Field.Name) = term Then
Counter = Counter + 1
ReDim Preserve Result(Counter)
Result(Counter) = T.Name & "," & Field.Name
End If
Next
Rs.MoveNext
Wend
Rs.Close
End If
Next
If Counter = 0 Then
searchTables = Null
Else
searchTables = Result
End If
End Function
You should filter out duplicated values, in case the function matches multiple times the same filed in the same table.

Cannot Update. Database or Object is Read-only after Requery

Okay, cannot find anything online about this error, so here goes.
Using Access 2003. I have a form that has a combobox dropdown. Users choose from the dropdown, and the VBA code looks at a particular table to see if data already exists. If it finds it, it displays the data associated with the record. If it does not find it, it adds a new record to the table, then tries to display the new data. The problem is that when using the following code, it does not display the new data, unless I add in Me.Requery. However, if I do that, it then gives me the above error if I try to do the same process again.
So, how can I get the data to display after adding without the above error?
Here is the relevant code...
' Find the record that matches the control.
Dim rs As DAO.Recordset
Set rs = Me.Recordset.Clone
'if no record found, then add a default record
If IsNull(Combo119) Then
Set rs = CurrentDb.OpenRecordset("SELECT * FROM [Master Reject Data]")
rs.AddNew
rs.Fields("production Date") = Date
rs.Fields("Work Order #") = Combo119.Column(0)
rs.Fields("Product Type") = ""
rs.Fields("Shift") = ""
rs.Fields("Line") = ""
rs.Fields("# Produced") = 0
rs.Fields("Reject Type") = ""
rs.Fields("Reject Quantity") = 0
rs.Fields("Reject Category") = "Rejection"
Dim tRec As Long
tRec = Combo142.ItemData(0)
rs.Fields("Report Number") = tRec
rs.Update
Me.Requery 'this is the line I added to try to get the data to appear
Set rs = Me.Recordset.Clone
rs.FindFirst "[Report Number] = " & tRec 'navigate to the newly added record
Else
rs.FindFirst "[Report Number] = " & Nz(Me![Combo119], 0) 'navigate to the record with the requested report number
End If
If Not rs.EOF Then Me.Bookmark = rs.Bookmark
Sorry, found the issue after looking at it again. The problem is that the Me.Requery statement needs to be Me.Recordset.Requery. After doing that and adding Me.Refresh, it works as intended.
To avoid requerying I think you should open the recordset specifying that it should be a dynaset
CurrentDb.OpenRecordset("SELECT * FROM [Master Reject Data]", dbOpenDynaset) - in case you want to make it a bit neater. as for the small bug, I see you've already found yourself