LinkChildFields and LinkMasterFields being automatically set when changing recordsource - ms-access

In Access Properties for a subform, I have "LinkChildFields = '' " and ""LinkMasterFields = '' " (i.e. blank for both, this is what I want: no link)
When I change the Recordsource of the subform, and requery the subform, (in VBA) they are both automatically being set to a field, in my case "CaseID" (resulting in no records being displayed)
This is my recordsource:
stSQL = "SELECT qryCasesAndCards1.CaseID, qryCasesAndCards1.CaseStatusID, qryCasesAndCards1.Status, qryCasesAndCards1.CompanyName, qryCasesAndCards1.NumberOfCards, qryCasesAndCards1.FullName, qryCasesAndCards1.CaseCreatedDate, qryCasesAndCards1.CaseClosedDate, qryCasesAndCards1.CreatedBy" & _
" FROM qryCasesAndCards1 where not caseID = " & Me.CaseID & " and cardnumber in (select qryCasesAndCards1.cardnumber from qrycasesandcards1 where qryCasesAndCards1.caseID = " & Me.CaseID & ")"
I've tried substituting a simpler query "select * from qryCases1" and the problem still occurs.
In VBA, right after the requery, I am debug.print'ing ".linkchildfields" and ".linkmasterfields" and this is how I can see they are both being automatically set to "CaseID"
I have tried changing both values in Access form properties to nonsense value, resaving, changing again, resaving etc, and still no joy.
I can workaround problem by setting both those values right after the recordsource runs, but there is an unacceptable delay when doing this (about 5 seconds for each value)
One thing I'm just wondering now, is wether my form filter is being propagated to the subform? (am opening it via "docmd.open ... caseid = x" )
CaseID might have at ONE stage some long time ago been entered in to those link fields... but it's definitely not now. It's like they're locked in an Access vault somewhere and it's thinking "golly gee i'm pretty sure he wants CaseID in there gee whiz I'll get right on that!"
I've found MSAccess: Change in subform recordsource causes loss of LinkChildFields binding and I've found Linking SubReports Without LinkChild/LinkMaster but I can't get any help from them.
Thanks for any help offered: I'm tearing my hair out :)

To keep Access from automatically updating the LinkChildFields and LinkMasterFields on a subform...
Ensure that the RecordSource queries do not have matching key fieldnames.
Ensure that the Link Child Fields and Link Master Fields are blank on the SubForm object's Property Sheet.
DO NOT set the LinkChildFields and LinkMasterFields properties in code (i.e. remove any previous code like MySubForm.LinkChildFields = ""), otherwise the negative side effects may not be resolved.
If the form and/or subform are bound directly to tables, simply update one of them to query the table and add an alias for one of the key fields. For existing queries, it is possible to add an alias for one of the key field names without negative consequences in probably most cases. In my case, I only had to add "AS Acct" to a single field in the SELECT part of the subform's RecordSource SQL--no other change was necessary in the FROM, WHERE or ORDER BY clauses even though the original [Account] fieldname is referenced in each of those clauses.
SELECT Actions.[Account] As Acct, ...
Of course, this required an update to the ControlSource property for the relevant control on the form.
These changes eliminated the extra few-second lag when changing both the parent record and when switching records on the subform! Likewise, it eliminated redundant events during record navigation!
I found no other setting that keeps Access from automatically re-binding the subform via the LinkChildFields and LinkMasterFields properties. As already pointed out in the comments, it also doesn't help by resetting the link fields (e.g. LinkChildFields = "") after setting RecordSource, primarily because Access takes time to reset the fields, much of that time spent in events fired both upon the automatic assignment of the link fields and again when they are reset to blank. (e.g., Form_Current is called at least twice for each new record navigation.)

Years passed... as even on Microsoft Access 2019, changing subform .RecordSource always alters original .LinkChildFields and .LinkMasterFields, reassigning good fields will cost us 5s time for each property, we have no way to avoid this.
As a workarround, we can use always the same query, for example, qrySelTmp, as .RecordSource for the subform, at the design time
Mainform.subform.Form.RecordSource = "qrySelTmp"
When we must change .RecordSource content, we change only the definition of the Query, like this
CurrentDb.QueryDefs("qrySelTmp").SQL = "SELECT qryCasesAndCards1.CaseID, qryCasesAndCards1.CaseStatusID, qryCasesAndCards1.Status, qryCasesAndCards1.CompanyName, qryCasesAndCards1.NumberOfCards, qryCasesAndCards1.FullName, qryCasesAndCards1.CaseCreatedDate, qryCasesAndCards1.CaseClosedDate, qryCasesAndCards1.CreatedBy" & _
" FROM qryCasesAndCards1 where not caseID = " & Me.CaseID & " and cardnumber in (select qryCasesAndCards1.cardnumber from qrycasesandcards1 where qryCasesAndCards1.caseID = " & Me.CaseID & ")"
If(Mainform.subform.Form.RecordSource = "qrySelTmp") then
Mainform.subform.Form.Requery
else
Mainform.subform.Form.RecordSource = "qrySelTmp"
End if
As shown in the code, we do only a subform .Requery instead of assigning a new .RecordSource.
As Subform.Form.Requery does not alter subform properties .LinkChildFields and .LinkMasterFields, we gain about 8s for each real change of .RecordSource.
In my case with 1 Customer - Many Orders diagram, I've a gain of time at the proportion of 2s over 20s.

What I done in the past is simply REMOVE the link child/master settings.
Then in code I SET the child critera. So in the following, the link master/child SHOULD filter by tour_id, but I simply INCLUDED the criteria in the SQL and then HAMMER OUT the settings.
Dim strSql As String
' load sub-form to this tour....
strSql = "select * from qryGuidesForTour2 where Tour_id = " & Me!ID
Me.subGuides.Form.RecordSource = strSql
Me.subGuides.LinkChildFields = ""
Me.subGuides.LinkMasterFields = ""
The above was a fix and fixed any performance issues. I suppose it really depends on "how" the main form record is set, but if you are setting up the "main" form record via code, then as above simply stuff in the child forms recordset directly with the critera you need. And while in above I did blank out the master/child settings, if you ARE going to allow adding of reocrds in the sub form, then likely in above you should set the master/child fields as opposed blanking them out.

Related

Dynamic Query in a MS-Access form

I have a form in Microsoft Access (2016) where I have a dropdown list named KID. When the dropdown list looses Focus I want to run a query that would look like this:
SELECT max(Cnt)
FROM Table
WHERE KID = ValueOfDropdownList
The result of this query should be usable in a variable in the VB Code of the Event.
Unfortuantly I am restricted to Access at the Moment. Am I even using Access the right way (Choose from dropdown in form -> Event -> SQL Query in VB -> Display in form) or are there better ways to do that?
Thanks for your help in advance!
Firstly I would use the AfterUpdate event of the combo box, otherwise it will fire even if someone just tabs through the control.
Secondly if this is just to set a variable you would probably be better to use a Domain function in this case a DMax()
iVariable = DMax("Cnt","Table","[KID] = " & Me.YourComboControlName )
This assumes KID is a number field. If KID is text you would need to delimit the criteria like this;
iVariable = DMax("Cnt","Table","[KID] = '" & Me.YourComboControlName & "'" )
Lastly if you want this to update as you scroll through records you would need to add the same code to the OnCurrent event of the form. That fires when the record changes.
If it is for display purposes you can't use the same technique on a continuous form if this is setting an Unbound control, you would need to pull this in to the underlying forms recordset.
You can use DMax:
YourVariable = DMax("[Cnt]", "[Table]", "[KID] = " & Me!DropdownListName.Value & "")
In case KID is text, apply quotes:
YourVariable = DMax("[Cnt]", "[Table]", "[KID] = '" & Me!DropdownListName.Value & "'")
FYI there is another option. As the data source of your combo,
use a query returning 2 columns, like select KID, max(cnt) from table1 group by KID.
Then in your text box, retrieve your value using =combo1.columns(1).
For this to work you need to specify in the combo properties that it has 2 columns. In the formula, don't forget that those combo columns are 0 based.
This is the most efficient solution when you need to retrieve several values linked to the combo (and you don't have millions of records).

Possible to change MS Access subform fields through VBA?

I can find plenty of examples of how to return specific records in a subform through altering the underlying source query in code and re-querying, but I'm struggling to use the same principle to alter the fields that are returned.
I have the following situation:
Combobox to select 1 of 5 fields. Subform is supposed to show the selected field plus a couple of static fields, lets call them fields 6 and 7
So in the case the user selects field 1 from the dropdown, subform should show fields 1, 6 and 7. In the case they pick field 4, subform should show fields 4, 6 and 7 etc.
This is what (amongst other things) I've tried:
Set the subform up through the wizard with a query (select field1, field6, field7) as source, amend said query after combobox selection is made:
Set qd = CurrentDb.QueryDefs("myqueryname")
qd.SQL = "Select " & mycomboboxselection & ",field6,field7 from mytablename"
Form_mymainformname.mysubformname.Requery
The query itself updates fine if I run that standalone after the change, but the subform within the main form doesn't change and when I click on the subform itself from the navigation window it seems to be stuck looking for field 1 as it asks me to input a parameter value
Can anyone help with how to achieve this please?
Set the RecordSource of the subform to the SQL:
Dim SQL As String
SQL = "Select " & mycomboboxselection & ",field6,field7 from mytablename"
Me!YourSubformControl.Form.RecordSource = SQL
It will force a requery of the subform.
Ok I found a solution after a few more hours searching and trial and error.
There are two seperate problems here. Firstly creating the subform uses the newly created form as the source object rather than directly using the query, and it seems that no matter how you try to manipulate the record source of said form, it doesn't like changing the fields it was built with.
So, create the subform, change the source object from the form to your query (and delete the now pointless newly created form), then you can use the code I started with:
Set qd = CurrentDb.QueryDefs("myqueryname")
qd.SQL = "Select " & mycomboboxselection & ",field6,field7 from mytablename"
Which works! sort of....
You have to close and reopen the form every time to see the actual updates though, which obviously isn't what we're going for
The other line does nothing:
Form_mymainformname.mysubformname.Requery
It works fine for a change of records, but apparently not for a change of fields. I suspect this is a bit of a ms quirk
The below, however, works, even though I feel like it should do exactly the same as the code line above:
Form_MyForm.MySubForm.SourceObject = Form_MyForm.MySubForm.SourceObject

How to directly update a record in a database from a form number (Access 2007)

I have a job-tracking system, and there is a query that returns results of all jobs that are overdue.
I have a form that displays each of these jobs one-by-one, and has two buttons (Job has been completed, and Job not completed). Not completed simply shows the next record.
I cannot find a way to get access to the current record to update it's contents if the "Has been Completed" button is pressed, the closest I can get is the long number which represents the records position in the form.
The VBA to get the index of the record in the form is as follows.
Sub Jobcompleted(frm As Form)
Dim curr_rec_num As Long
curr_rec_num = frm.CurrentRecord
End Sub
This is my first shot at VBA, and after an hour of searching I cannot find anything to solve my problem.
Am I going about this the entirely wrong way? Working in Microsoft Access 2007
Further Info All tables are normalized
Vehicle Table: Contains vehicle_id(pk), as well as rego and model etc
Job Table: Contains job_id(pk), vehicle_id(fk) and other info about what needs to happen, as well as the next occurance date, days between each occurance of the job (all jobs repeat) and other info
Job History Table: Contains job_history_id(pk), job_id(fk), date completed and comments
When the job completed button is pressed, it should create a new entry in the job history table with the current date, any comments and the job id
This is the script I am trying to get working
Private Sub Command29_Click()
Dim strSQL1 As String
Dim strSQL2 As String
Set Rs = CurrentRs
Set db = CurrentDb
strSQL1 = "INSERT INTO completed_jobs(JOB_ID, DATE_COMPLETED, COMMENTS) VALUES " & Rs!job.ID & ", " & Date
db.Execute strSQL1, dbFailOnError
strSQL2 = "UPDATE job SET JOB_NEXT_OCCURANCE = JOB_NEXT_OCCURANCE+JOB_RECURRANCE_RATE WHERE job.ID = Rs!job.ID"
db.Execute strSQL2, dbFailOnError
End Sub
Note: Line Set Rs = CurrentRs is completely incorrect, I believe this is what I need to figure out? This is called on button-press
I am posting an image which shows the form (non-continuous).
#HansUp, I get what you are saying, but I dont quite think it's applicable (I did not provide enough information first time around for you to understand I think)
#sarh I believe this Recordset that you are talking about is what I need, however I cannot figure out how to use it, any hints?
#Matt I am 90% sure I am using a bound form (Like I said, new to Access, been looking at everything people have suggested and learning as I go). There is of course an ID for the job (Just not shown, no need to be visible), but how would I access this to perform an operation on it? SQL I can do, integrating with Access/VBA I am new at
As I understand your situation, your form is data-bound bound (you can get record index), so - your form already located on this record. If you need to update some field of underlying dataset, you can write something like
Me!SomeField = ...
DoCmd.RunCommand acCmdSaveRecord
If your form has control bound to "SomeField", then the form will be updated automatically.
If this will not help, you can look to a couple of another directions:
1) Update records using SQL code. For example, you have ID of record that should be updated in the form data set, so you can write something like:
Call CurrentDB.Execute( _
"UPDATE SomeTable SET SomeField = SomeValue WHERE SomeTableID = " & Me!SomeTableID, dbSeeChanges)
2) You can look at the Bookmark property - both Recordset and Form has this property, it describes the record position. So you can write something like this (not the best example, but can help you to get an idea):
Dim Rs as Recordset
Set Rs = Me.RecordsetClone 'make a reference copy of the form recordset
Rs.Bookmark = Me.Bookmark 'locate this recordset to the form current record
Consider a simpler approach. I doubt you need to be concerned with the form's CurrentRecord property. And I don't see why you should need a command button for "Has been Completed" and another for "Has not been Completed".
Add a "Yes/No" data type field to the table which is used by your form's record source. Set it's default value property to 0, which represents False or No. Call it "completion_status". Create a new form using that record source. Then your form can have a check box control for completion_status.
Newly added records will have False/No as completion_status --- the check box will appear unchecked. The completion_status for other records in the forms can be toggled between Yes (checked) and No (unchecked) using the check box control.

Reading deleted record buffer

According to the Access (2003) documentation, when you delete a record, it's placed in a buffer, then the BeforeDeleteConfirm event is raised, then the AfterConfirmDelete ad Delete events. In my table if a record is deleted, I need to add one of its field values (a quantity value) to a field in another table.
I tried setting the quantity to a private variable in the Current event, but as soon as I delete click delete, it looks like the Current event fires again and resets my variable.
How can I access the value of a field in a deleted record?
The values in bound controls are still available at the form delete event. In this sample, txtid is a bound text box.
Private Sub Form_Delete(Cancel As Integer)
Dim strActivity As String
Dim strSql As String
strActivity = Me.txtid & " deleted."
strSql = "INSERT INTO tblAudit (activity) " & _
"VALUES (""" & strActivity & """);"
CurrentDb.Execute strSql, dbFailOnError
End Sub
This solution may require some redesigning, but it's what I use for a form of my own:
Add a boolean field to your record table called "Visible".
On your main form, set your form.filter="[Visible]=True"
On your main form, set AllowFilters=Yes.
On your main form, set your Form.Allow Deletions=False (we don't want them actually deleting the record)
Make a custom Delete command button that sets Visible=False.
If you feel the need later, you could add some maketable queries and delete queries to clean up your main table and move the deleted values off to another table.
This way, the user doesn't actually 'delete' the record. Rather, they just make that record invisible. And later, when the form is unloaded, you could add in some clean-up queries.
Generally, if I want to do something in the OnDelete events other than the default actions, I will tend to turn AllowDeletes OFF and create a DELETE RECORD command button that does what I want.
Thus, I don't depend on the built-in events. If I need to store a value, I do it, the pop up the confirm message, and then do what's needed, by whichever method I consider easiest.
I just find this a lot easier, particularly because user settings can cause things to behave differently. For example, the two Confirm events don't happen when the users has these settings (from the A2003 help file, but it's always been this way):
Note The AfterDelConfirm event does
not occur and the Delete Confirm
dialog box isn't displayed if you
clear the Record Changes check box
under Confirm on the Edit/Find tab of
the Options dialog box, available by
clicking Options on the Tools menu.
So I just avoid the whole thing by rolling my own deletion code and avoiding the default behaviors.
(and yes, if you've followed my posts over the years, you know that it's pretty unusual for me to advise against simply using default Access behaviors, but this is one case where I feel more control is appropriate in almost all cases)

Rebind Access combo box

I have an Access 2007 form that is searchable by a combobox. When I add a new record, I need to update the combobox to include the newly added item.
I assume that something needs to be done in AfterInsert event of the form but I can't figure out what.
How can I rebind the combobox after inserting so that the new item appears in the list?
The easiest way is to guarantee that the combobox is always up-to-date is to just requery the combobox once it gets the focus. Even if the recordset is then updated somewhere else, your combobox is always up-to-date. A simple TheCombobox.Requery in the OnFocus event should be enough.
There are two possible answers here that are efficient:
use the Form's AfterInsert event to Requery the combo box (as well as the OnDeleteConfirm event). This will be sufficient if the combo box does not display data that the user can update and that needs to be updated if the underlying record is updated.
if updates to the data need to be reflected in the combo box, then it would make sense to add a requery in the AfterUpdate events of the controls that are used to edit the data displayed in the combo box.
For example, if your combo box lists the names of the people in the table, you'll want to use method #2, and in the AfterUpdate event of Me!txtFirstName and Me!txtLastName, requery the combo box. Since you're doing the same operation in four places, you'll want to write a subroutine to do the requery. So, the sub would look something like this:
Private Sub RequerySearchCombo()
If Me.Dirty Then Me.Dirty = False
Me!MyCombo.Requery
End Sub
The reason to make sure you requery only when there is actually an update to the data displayed in the combo box is because if you're populating the combo box with the list of the whole table, the requery can take a very long time if you have 10s of 1,000s of records.
Another alternative that saves all the requeries would be to have a blank rowsource for the combo box, and populate it only after 1 or 2 characters have been typed, and filter the results that the combo displays based on the typed characters. For that, you'd use the combo box's OnChange event:
Private Sub MyCombo_Change()
Dim strSQL As String
If Len(Me!MyCombo.Text) = 2 Then
strSQL = "SELECT MyID, LastName & ', ' & FirstName FROM MyTable "
strSQL = strSQL & "WHERE LastName LIKE " & Chr(34) & Me!MyCombo.Text & Chr(34) & "*"
Me!MyCombo.Rowsource = strSQL
End If
End Sub
The code above assumes that you're searching for a person's name in a combo box that displays "LastName, FirstName".
There's another important caveat: if you're searching a form bound to a full table (or a SQL statement that returns all the records in the table) and using Bookmark navigation to locate the records, this method will not scale very well, as it requires pulling the entire index for the searched fields across the wire. In the case of my imaginary combo box above, you'd be using FindFirst to navigate to the record with the corresponding MyID value, so it's the index for MyID that would have to be pulled (though only as many index pages as necessary to satisfy the search would actually get pulled). This is not an issue for tables with a few thousand records, but beyond about 15-20K, it can be a network bottleneck.
In that case, instead of navigating via bookmark, you'd use your combo box to filter the result set down to the single record. This is, of course, extremely efficient, regardless of whether you're using a Jet back end or a server back end. It's highly desirable to start incorporating these kinds of efficiencies into your application as soon as possible. If you do so, it makes it much easier to upsize to a server back end, or makes it pretty painless if you should hit that tipping point with a mass of new data that makes the old method too inefficient to be user-friendly.
I assume your combobox is a control on a form, not a combobox control in a commandBar. This combobox has a property called rowsource, that can be either a value list (husband;wife;son;girl) or a SQL SELECT instruction (SELECT relationDescription FROM Table_relationType).
I assume also that your form recordset has something to do with your combobox recordset. What you'll have to do is, once your form recordset is properly updated (afterUpdate event I think), to reinitialise the rowsource property of the combobox control
if the recordsource is an SQL instruction:
myComboBoxControl.recordsource = _
"SELECT relationDescription FROM Table_relationType"
or if it is a value list
myComboBoxControl.recordsource = myComboBoxControl.recordsource & ";nephew"
But over all I find your request very strange. Do you have a reflexive (parent-child) relationship on your table?
I would normally use the NotInList event to add data to a combo with
Response = acDataErrAdded
To update the combo.
The Access 2007 Developers Reference has all the details, including sample code:
http://msdn.microsoft.com/en-us/library/bb214329.aspx
Requery the combo box in the form's after update event and the delete event. Your combo box will be up to date whenever the user makes changes to the recordset, whether it's a new record, and change, or a deletion.
Unless users must have everybody else's changes as soon as they're made, don't requery the combo box every time it gets the focus because not only will the user have to wait (which is noticable with large recordsets), it's unnecessary if the recordset hasn't changed. But if that's the case, the whole form needs to be requeried as soon as anybody else makes a change, not just the combo box. This would be a highly unusual scenario.
After update:
Private Sub Form_AfterUpdate()
On Error GoTo Proc_Err
Me.cboSearch.Requery
Exit Sub
Proc_Err:
MsgBox Err.Number & vbCrLf & vbCrLf & Err.Description
Err.Clear
End Sub
After delete:
Private Sub Form_Delete(Cancel As Integer)
On Error GoTo Proc_Err
Me.cboSearch.Requery
Exit Sub
Proc_Err:
MsgBox Err.Number & vbCrLf & vbCrLf & Err.Description
Err.Clear
End Sub