Looking to correct my After Update Event (MS Access) - ms-access

I'm currently using the below code to update certain fields so that the user doesn't have to enter them. For some reason, it only fires on records that are already saved and ignores the current record.
The funny thing is that it was working for a day or so (including the current record). Then Access started crashing on me and I was forced to re-write the code.
Private Sub RawMaterial_AfterUpdate()
sSQL = "UPDATE tmp_Formula INNER JOIN tbl_RawMaterial ON tmp_Formula.RawMaterial = tbl_RawMaterial.RawMaterial " _
& "SET tmp_Formula.MiscInfo = [tbl_RawMaterial].[MiscInfo], tmp_Formula.Potency = [tbl_RawMaterial].[Potency], " _
& "tmp_Formula.PUoM = [tbl_RawMaterial].[PUoM], tmp_Formula.CUoM = [tbl_RawMaterial].[ClaimUoM], " _
& "tmp_Formula.Cost = [tbl_RawMaterial].[Cost], tmp_Formula.CostUoM = [Tbl_RawMaterial].[CostUoM];"
CurrentDb.Execute sSQL, dbFailOnError
CurrentDb.TableDefs.Refresh
Me.Refresh
End Sub
Would anyone be able to help me re-write this so that it includes the current record? It is probably irrelevant but I have tried Me.CurrentRecord.SetFocus and Me.RawMaterial.SetFocus. Neither worked.

New record (or edits to existing record) must first be committed to table to be available for any SQL. A record is committed when:
1. close table/query/form
or
2. move to another record
or
3. run code to save - options:
a. If Me.Dirty Then Me.Dirty = False
b. DoCmd.RunCommand acCmdSaveRecord

Related

Replacing Multi-Value Fields With A Join Table

I'm moving on from multi-value fields due to my conversion to SQL Server for the back end. Unfortunately, I can't figure out how to replace it.
What I've done is created a many to many relationship between my "Opportunities" and "Purpose" tables utilizing a join table (one to many on each). This part was easy.
What I don't understand is how to then create a dropdown listbox (with check boxes to select the options) for the purpose.
I've found resources online pointing to the idea that I will need to use VBA, but have yet to find any actual examples. Is anyone familiar with how to do this?
Thanks in advance.
I had to do this for exactly the same reason; moving to a SQL Server back-end. My solution keeps the checkbox functionality but doesn't have them in a drop-down. You can put the check-boxes in a sub-form (that looks like a drop-down?) if you don't want a bunch of boxes on your main form.
For each checkbox you want to test for whether a record in the related table exists and add it if it doesn't when the box is checked, and the reverse when it's unchecked. The check-boxes must be unbound for this to work.
Here's the add/remove code. It goes in the "After Update" event:
Private Sub cb_1_AfterUpdate()
If Me.cb_1 = True Then
If DCount("Parent_ID", "Checkbox_records", "Parent_ID = " & Me.P_ID & "and Checked_box_num = 1") = 0 Then
strSQL = "INSERT INTO Checkbox_records (Parent_ID, Checked_box_num) VALUES (" & Me.P_ID & "," & "1)"
CurrentDb.Execute strSQL, dbFailOnError
End If
End If
If Me.cb_1 = False Then
If DCount("Parent_ID", "Checkbox_records", "Parent_ID = " & Me.P_ID & "and Checked_box_num = 1") > 0 Then
strSQL = "DELETE FROM Checkbox_records WHERE Parent_ID = " & Me.P_ID & " and Checked_box_num = 1"
CurrentDb.Execute strSQL, dbFailOnError
End If
End If
End Sub
The problem with unbound boxes is that they don't change when you switch records. So you have to set the boxes to reflect the data state when you change records, which you can do in the form's "On Current" event:
Private Sub Form_Current()
If DCount("Parent_ID", "Checkbox_records", "Parent_ID = " & Me.P_ID & "and Checked_box_num = 1") > 0 Then
Me.cb_1 = True
Else
Me.cb_1 = False
End If
End Sub
The thing I don't like about this solution is that you have to duplicate the code for each box, but it runs smoothly so it's livable.

vba function to return record with most recent date. given another criteria

I need to set the default value for a textbox in an ms access 2010 form. The default value needs to be the most recent date in CommunicationTable where the ClientNumber is the same as the ClientNumber associated with the current record in the form. The code below references the correct ClientNumber, but I am not sure how to get the most recent date. I am concerned that DMax might not be the appropriate function for getting the most recent date. How should I change the following to get the most recent date?
=DMax("DateOfCommunication","[CommunicationTable]","[ClientNumber]= "
& [Forms]![Main]![NavigationSubform].[Form]![ClientNumber] & "")
I realize I should also post the larger function in which the above function is nested:
=DLookUp("[Level]","[CommunicationTable]","DateOfCommunication= "
& DMax("DateOfCommunication","[CommunicationTable]","[ClientNumber]= "
& [Forms]![Main]![NavigationSubform].[Form]![ClientNumber] & ""))
Also, the form itself is bound to CommunicationTable. This VBA function is in the DefaultValue dialog box, which I get into via the property sheet for the text box. So I am not sure that creating a sql query will work in this case.
EDIT:
I have uploaded a stripped down copy of the database which reproduces the problem at this file sharing site.
To locate the code:
1.) Open the CommunicationEntryForm and
2.) open the AfterUpdate() event procedure for the ClientNumber field.
Next, to reproduce the problem:
1.) close `CommunicationEntryForm`
2.) In the Main form(which should already be open), click the View tab to open
the most recent CommunicationForm for any Client you want. Note the Level
number for that Communication.
3.) Click on the Communication tab. This will leave the form and show a list
of CommunicationForms for that Client.
4.) Click the Create New Form button. This will open up CommunicationEntryForm.
The value for Level should be the same as the value you noted in step 1 above.
The problem is that this is blank.
Can someone show me how to fix this problem?
#CodeMed - I did download the database but found you have issues other than what you are describing- such as when you 'add' a new communication you simply overwrite an existing record. I managed to get the result you were looking for, but it just changes the 3 records around. Does your non-sample program actually have the ability to create and add new records? As it is, I just changed your existing code to this:
Private Sub cmdNewCommForm_Click()
Dim cNum As Long
Dim strSQL As String
Dim rs As Recordset
Dim db As Database
Set db = CurrentDb
cNum = Forms!Main!NavigationSubform.Form!ClientNumber
strSQL = "SELECT Top 1 Co.Level AS MaxOfLevel " & _
"FROM CommunicationTable co Where Co.ClientNumber = " & cNum
Set rs = db.OpenRecordset(strSQL)
Forms!Main!NavigationSubform.Form!NavigationSubform.SourceObject = "CommunicationEntryForm"
Forms!Main!NavigationSubform.Form!NavigationSubform!ClientNumber = cNum
Forms!Main!NavigationSubform.Form!NavigationSubform!DateOfCommunication = Date
If rs.RecordCount > 0 Then
Forms!Main!NavigationSubform.Form!NavigationSubform!Level = rs!MaxOfLevel
Else
Forms!Main!NavigationSubform.Form!NavigationSubform!Level = 0
End If
Set rs = Nothing
Set db = Nothing
End Sub
What I would do is first grab the date by doing something like:
Dim db as Database
Dim rec as Recordset
Set db = CurrentDB
Set rec = db.OpenRecordset("SELECT Top 1 DateOfCommunication FROM CommunicationTable WHERE ClientNumber= " & [Forms]![Main]![NavigationSubform].[Form]![ClientNumber] & " ORDER BY DateOfCommunication DESC")
This will get the most recent date. Then, in your above VBA, you can just stick rec(0) in where your calculation was:
Me.MyDateField = DLookUp("[Level]","[CommunicationTable]","DateOfCommunication= #" & rec(0) & "#")
Substitute "MyDateField" with whatever that name of your date field actually is.
I'm pretty sure you need the pound signs (or "hashtags" as the kids call them today...) in order for Access to do the calculation on a date value.

"Not a valid bookmark" with DAO Recordset

I'm in the process of converting an Access Data Project (ADP) into a standard ACCDB format with ODBC linked tables. In the ADP, I had overridden the Refresh button to return the user to the current record by using the following code:
Public Sub RibbonCmd_RefreshScreen(ctl As IRibbonControl, ByRef cancelDefault)
On Error GoTo ErrHandler
cancelDefault = False
DoCmd.Echo False
Dim saveBookmark
With Screen.ActiveForm
saveBookmark = .Bookmark
.Requery
.Bookmark = saveBookmark
End With
'Success - cancel the default behavior
cancelDefault = True
ExitHandler:
DoCmd.Echo True
Exit Sub
ErrHandler:
cancelDefault = False
Resume ExitHandler
End Sub
My understanding is that this should work just fine with DAO, but I get error 3159, Not a valid bookmark. I've also tried replacing .Bookmark with .Recordset.Bookmark, but that gave me the same result. Is there something I'm doing wrong here?
Actually, a requery of a form or a requery of a recordset will re-set and invalidate book marks.
So such book marks are no longer valid after a requery.
So the best approach here will depend on either
a) I simply want to re-display any changed records (and not move off current record).
b) I simply want to re-display any changed records AND ALSO display new records (the new records is the critical part).
If you just need a refresh, then you can use the appropriately called command refresh.
Eg:
Me.Refresh
Or in your case
Screen.ActiveForm.Refresh
So the above is ONE line of code and is ALL you need. The current record pointer for the form does NOT change when you use this command. All and any record changed will re-display for you.
Note that since you can behind the form button use:
Me.Refresh
Then LITTLE need is required to call a general routine as you have written.
However, if you need the form to "load" or display any new records added, then you DO have to use requery. In this case as noted book marks in this case all become invalid.
So, for code to requery, then we use the PK value (and hopefully you used the default pk of ID that been the default for 20 years). The code would then become:
Dim lngID As Long
If IsNull(Me!ID) Then Exit Sub
lngID = Me!ID
Me.Requery
Me.Recordset.FindFirst "id = " & lngID
Now of course if the PK id is not the same for each form, then you most certainly could pass the NAME of the PK value to your "general" refresh routine. It would look like:
Public Sub MyRefresh(strPK As String)
Dim lngID As Long
If IsNull(Me(strPK)) Then Exit Sub
lngID = Me(strPK)
Me.Requery
Me.Recordset.FindFirst strPK & " = " & lngID
End Sub
The "hope" here is you actually really JUST need refresh, since as noted this is only one line of code, and better yet it does NOT move the record pointer.
I use VB6 and Visual Data Manager in development. I have had the same problem. Most probably it arose when 2 users tried to update the same record in the same time. So some fields in the table are corrupted.
Here are the steps I used to solve the problem:
1- Copy the structure of the table (lets call it table1)to another table (lets call it table2).
2- Find the correpted record(s) in table1.
3- Transfer the data from table1 to table2 except the corrupted record(s)
4- Reenter the excluded record(s) to table2 again.
5- Rename table1 table3
6- Rename table2 table1
That's all folk
abdobox#yahoo.com
I have used the forms Recordset.AbsolutePosition, and this works fine e.g. in the OnKeyDown exit of a field
Dim PrefilterPosition As Long
Private Sub ValnSubject_KeyDown(KeyCode As Integer, Shift As Integer)
' Not F2 - exit
If KeyCode <> vbKeyF2 Then Exit Sub
' Get the active control
Dim ActiveCtl As Control
Set ActiveCtl = Me.ActiveControl
ActiveControlName = ActiveCtl.Name
' Is the form's filter set?
If Me.Filter = "" Then
' NO: Apply the new filter
' Note the current position in the recordset
PrefilterPosition = Me.Recordset.AbsolutePosition
' Set the filter to the Active control's value
Me.Filter = "[" & ActiveCtl.ControlSource & "]='" & ActiveCtl.Value & "'"
Me.FilterOn = Me.Filter <> ""
Me.Requery
Else
' YES: Clear the filter
Me.Filter = ""
Me.FilterOn = Me.Filter <> ""
Me.Requery
' Align the recordset on the previously stored position
Me.Recordset.AbsolutePosition = PrefilterPosition
End If
' Restore the cursor to where it came from
Me.Controls(ActiveControlName).SetFocus
Ex_it:
End Sub
For context: this code was from an idea for an 'Instant Filter', where you position the cursor on a field in a tab form, press F2, and then a filter is applied so you see only records with the selected field's value. Press F2 again and the filter is removed and the cursor goes back into the place it was when you hit F2 the first time. Bookmarks do not work here, as Albert says above.

MS Access 03 Query using Yes/No Data Type

I have a table in Access..
AccessKey
AccessCardID
Distributed (yes/no)
On my form is a combo box with 10 Access Cards. I need help setting up a query where if guest A gets Access Card #1, the user will only be able to choose from Access Cards #2-#10, for guest B, c, and so on, until guest A returns Access Card #1.
so far the the query i have is
SELECT AccessCardID
FROM AccessKey
WHERE Distributed = False;
Here is my new code for After update
Private Sub AccessKeyNo_AfterUpdate()
If MsgBox("Do you want to assign Access Key" & Me.AccessKeyNo & "?", _
vbYesNo) = vbYes Then
Me.GuestAccessKeyID = Me.AccessKeyNo
Me.MyCheckBox = Not IsNull(Me.GuestAccessKeyID)
Me.AccessKeyNo.Requery
End If
End Sub
My new query
SELECT AccessKey.AccessKeyID
FROM AccessKey LEFT JOIN Guest ON AccessKey.AccessKeyID=Guest.GuestAccessKeyID
WHERE (((Guest.GuestAccessKeyID) Is Null));
And the On current for the form
Private Sub Form_Current()
Me.MyCheckBox = Not IsNull(Me.GuestAccessKeyID)
If IsNull(Me![GuestID]) Then
DoCmd.GoToControl "GuestFirstName"
End If
End Sub
Why don't you set Distributed to False in the above query so that you get the list of cards which have not been distributed yet.
If you do not like writing code, you can use a subform and set the record source to your table, AccessKey. Alternatively you can write a little code, say:
Private Sub NameOfComboHere_AfterUpdate()
Dim db As Database
Dim strSQL As String
Set db = CurrentDb
If MsgBox("Do you want to assign " & Me.NameOfComboHere & "?", _
vbYesNo) = vbYes Then
strSQL = "UPDATE AccessKey SET Distributed = True " _
& "WHERE AccessCardID = " & Me.NameOfComboHere
db.Execute strSQL, dbFailOnError
If db.RecordsAffected = 1 Then
MsgBox Me.NameOfComboHere & " has been assigned."
End If
End If
Me.NameOfComboHere.Requery
End Sub
There are a few notes. This assumes that the combobox has a bound column of AccessCardID and that AccessCardID is numeric. It also assumes that you have a reference to Microsoft DAO 3.x Object library. A subform may be the best bet.
EDIT based on Comments
Let us say you have a card id in the guest table, first, add a little code to the Current event for the form:
Me.MyCheckBox = Not IsNull(Me.GuestAccessCard)
This will set the checkbox to false if there is no card id and to true if there is an id.
You will need to change the query for the combobox to:
SELECT AccessCardID
FROM AccessKey
LEFT JOIN Guests
ON AccessKey.AccessCardID = Guest.GuestAccessCard
WHERE Guest.GuestAccessCard Is Null
Then the After Update event would run:
Private Sub NameOfComboHere_AfterUpdate()
If MsgBox("Do you want to assign " & Me.NameOfComboHere & "?", _
vbYesNo) = vbYes Then
Me.GuestAccessCard = Me.NameOfComboHere
Me.MyCheckBox = Not IsNull(Me.GuestAccessCard)
''The data will have to be saved for the
''combo to update with the new data
If Me.Dirty Then Me.Dirty = False
Me.NameOfComboHere.Requery
End If
End Sub
And do not forget to set the GuestAccessCard to Null when the checkbox is unticked and requery the combo.
I may be completely misunderstanding the situation here, but it seems to me that you don't want to have a Boolean field in the table with your cards to indicate if it's checked out -- all you need is a field in your Guests table where you enter the card number. Then you can tell which cards are available with a left join against the card numbers that are in use:
SELECT Cards.CardNumber
FROM Cards LEFT JOIN Guests ON Cards.CardNumber = Guests.CardNumber
WHERE Guests.CardNumber Is Null
This would mean that there's only ever one place where the information is stored (in the Guests table). You could also put a unique index on the CardNumber field in the Guests table (allow Nulls) so you could never assign the same card to two guests.
This may be the way you are doing things, or the way Remou has suggested, but I got too confused by all the convoluted back and forth!

Copying a record in VBA 2 (the new question)

I have this code that I am trying to copy a record with in VBA. Unfortunately I cannot get it to work. I do not knw why it won't do anything.
Dim OldRecord As DAO.Recordsets, NewRecord As DAO.Recordset
Dim strSQL As String
strSQL = "SELECT [Tote Log].* FROM [Tote Log] WHERE Number = " & _
Me.tbScannerRead.Value
Set OldRecord = CurrentDb.OpenRecordset(strSQL)
If OldRecord.Count = 1 Then
Set NewRecord = _
CurrentDb.OpenRecordset("SELECT [Tote Log].* FROM [Tote Log]")
With NewRecord
.AddNew
For i = 0 To .Fields.Count - 1
.Fields(i).Value = OldRecord.Fields(i).Value
Next i
.Fields("Number").Value = Me.tbScannerRead & "B2"
.Update
varBookMark = .Bookmark
End With
NewRecord = varBookMark
DoCmd.RunCommand acCmdSelectRecord
Me.tbMessageBox = "Added new record"
Me.tbMessageBox.Visible = True
GoodToteRead = False
Me.tbScannerRead.SetFocus
End If
I get nothing, I am trying to copying a record from the tote log and change the number from, lets say, L20444 to L20444B2 and have the same field information as the original. This is where I am so far but I get nothing. Ahy Help would be greatly, and I mean greatly, appreciated. Thanks
There are a few things that could be causing it. Here is one. Does your table have a primary key? It looks like you are trying to update the primary key to a value that already exists in the table before changing it. Is this happening on a form? If so Access can get upset at you for changing a recordset behind it's back. a me.undo() before making changes can help. Also if you are on a form you can acomplish the same thing this way. It's a bit hacky, but it is the easy way.
DoCmd.RunCommand acCmdSelectRecord
DoCmd.RunCommand acCmdCopy
DoCmd.GoToRecord , , acNewRec
DoCmd.RunCommand acCmdPaste
As an alternative, I would recommend something along these lines.
Dim sSql As String
Dim sUpdateSuffix as string
sUpdateSuffix="B2"
Const sTableName As String = "[Tote Log] "
sSql = "INSERT INTO "[Tote Log]([number],[whateverelse]) " & _
"SELECT [number]" & sUpdateSuffix & ",[whateverelse] FROM [Tote Log] WHERE Number = " & Me.tbScannerRead.Value
CurrentProject.Connection.Execute sSql
If you want to build the sql string dynamically use the same method as you already used to loop through the fields and build the query string.
me.requery will rebuild the form recordset.
Hope that helps you
Well it might actually be saving the database record but not redisplaying it; I'm having a hard time deciphering that part of the code, and I don't know what your form is bound to.
Anyway, you should open your recordsets like this:
Set rs = db.OpenRecordset(strSQL, dbOpenDynaset, dbSeeChanges)
especially if you are using SQL Server as the backend (which you should).
Once you have saved the record, you should probably just reload the record back into your form by doing a recordset.find(), rather than trying to bookmark it. Bookmarks only work on the same recordset they originated from. This provides round-trip verification that the data was actually saved into the database.