Change YesNo Value in field in Tbl1 based on value in control an active form bound to Tbl2

regnewby2465

Member
Local time
Today, 00:26
Joined
Sep 22, 2015
Messages
58
I have a table named TourT with a YesNo field named UnitAvailable.

Snap 2022-08-14 at 11.25.27.png


I have a form named CAD_Log_DispF with source table CADLogT.

Snap 2022-08-14 at 11.26.04.png


Snap 2022-08-14 at 11.31.34.png


I placed the Select Case code in the BeforeUpdate action of the CAD_Log_DispF form.

Snap 2022-08-14 at 11.26.50.png


As you see it simply shows the Unit available or not based on the value of the Action control. This works fine so far. But ultimately, what I need is for the UnitAvailable field in the TourT table to show Yes or No based on the latest action entered for that Unit in the [CAD_Log_DispF].[Action] control.

For those that have seen my other posts, you know that I am new to VBA so I would appreciate responses geared to my level. Thanks in advance.

Mark
 
Here is the problem. You can do it in several ways, but ideally you wouldn't do it at all. You would let a query based on TourT table JOINed with CAD_Log_DispF show True or False as required. Ideally this is all involved with a JOIN, but that can get complex fast for beginners. I'll take a simpler, more direct approach with the understanding that I am taking into account your request for simplicity.

In deciding implementation questions, always consider these sub-questions: WHERE, WHEN, WHAT, and HOW. Answering the sub-questions helps you focus exact implementation.

Your "WHERE" is related to a control on frmCAD-Log. You do something with the Action field on that form.

Your "WHAT" is that you want a field in a different form to update once you do that change.

Your "WHEN" needs clarification to identify the appropriate event. For example, did you want to change the value the moment that control changes? (Not recommended.) Or do you want to wait until the record is actually saved with the Action value having been updated? (More likely correct.) I'm going with the 2nd option.

If you want the update of frmCAD-Log to be the trigger, the AFTER_UPDATE event is the right place. I know you have gone around the mulberry bush a couple of times on this forum related to AFTER_UPDATE for a different question. There you were updating the record you had just updated on the same form. But here, the targeted record isn't part of the form. Note that what I am about to show you is valid IF AND ONLY IF that TourT table isn't open as a hidden part of a sub-form to the frmCAD_Log form.

Here is a snippet for idea purposes only to show HOW you might do this. You showed us a CASE ladder. If you put it in the right place, that might be exactly what you need. (Though I will have later comments about it.) Things you will need for this include the sources for your action value and the unit number to be updated.

Code:
<<put your SELECT CASE for action value here>>
strSQL = "UPDATE TourT Set UnitAvailable = " & bActionValue & " WHERE Unit = " & <<put unit number here>> & " ;"
CurrentDB.Execute strSQL, dbFailOnError

After that, you might need to have error trap handling to see if the SQL string failed. There are other ways but you said keep it simple.

Before I let go of this, you have an "abomination" in your case ladder. Cases 4 and 9 assign "" as the value for a Y/N field, and that is illegal. It can't happen. If your implication is that cases 4 and 9 never happen, then you don't place a CASE statement for them, or don't even have those cases in the program at all.

In the table diagrams for CADLogT and in TourT, that field is clearly set as Y/N in both places. It is a cardinal rule of DB design that you must always have REASONABLE values even for things that ain't supposed to happen. Like, if case 9 is "Officer carried away by the rapture" and you know for a fact that NONE of those sinners on the force will ever have that happen, you STILL need either TRUE or FALSE for case 9.
 
You're missing a table.

I have no idea what all those numeric fields named after numbers are for, but I do know they shouldn't be in TourT. Tables shouldn't accomodate data horizontally (with more columns\fields) they should accomodate data vertically (with more records\rows). All that data needs to come out of TourT and into a new table with a just 3-4 fields:

tblYourNameHere
ynh_ID, autonumber, primary key
ID_TourT, number, foreign key back to TourT.ID
ynh_Value, number, this will hold whatever is currently going into TourT for all those fields
ynh_Number, number, this will hold the current name of the field (e.g 1, 2, ...9) if needed.

Instead of 9 fields in TourT you would ad 9 records into tblYourNameHere. ynh_Number may or may not be necessary depending on if it is designating an order among all that data. If its just a way to differentiate field names, then it doesn't need to exist.

I'd get your table structure correct before doing all this form work you have been posting about.
 
Thank you for the reply. The TourT table is not hidden in any part of the subform. I amended the Select Case code as you suggested and put it in the subform's AfterUpdate event. In your suggestion, you put <<put unit number here>>. Is that where I would put the value of the combo box from which the operator selects the unit, if so that is ID_UnitT ? Does the code look right and is it in the right place? Thank you.


1660500520929.png
 
Last edited:
What you posted got clipped, but if that combo is where the unit is selected or defined, then I think you got the message.

Please note that plog's comment, even if you fixed it, would not change the approach I just described. His comment makes sense with respect to the concept of Normalization. If you are not familiar with that term, it is worthy of study and, long-term, would help manage your database anywhere from a little to a huge lot in terms of speed and efficiency. With regard to THIS question, it appears to be a separate problem.
 
1. Use CODE tags when posting code, not screenshots. People can't copy and paste and edit your code otherwise and we will be able to see the entirety of your code as well.

2. You're doing a lot of stuff for no effect. Let's just focus on Form_AfterUpdate(). You declare strYesNo, you set it a value and then...nothing. What's strYesNo purpose? You've done that a few times: UnitAvailable and everything in Form_BeforeUpdate() achieves nothing.

3. You're also doing the exact opposite--performing tasks with things that don't exist. bActionValue appears nowhere until you try to use its value. Where's it's value suppose to come from?
 
What you posted got clipped, but if that combo is where the unit is selected or defined, then I think you got the message.

Please note that plog's comment, even if you fixed it, would not change the approach I just described. His comment makes sense with respect to the concept of Normalization. If you are not familiar with that term, it is worthy of study and, long-term, would help manage your database anywhere from a little to a huge lot in terms of speed and efficiency. With regard to THIS question, it appears to be a separate problem.
Sorry, this is the whole code.

Code:
Private Sub Form_AfterUpdate()


    Dim strYesNo As Integer
    strYesNo = Me.Action.Value
    Select Case Action
        Case Is = 1: UnitAvailable = False
        Case Is = 2: UnitAvailable = False
        Case Is = 3: UnitAvailable = False
        Case Is = 5: UnitAvailable = True
        Case Is = 6: UnitAvailable = True
        Case Is = 7: UnitAvailable = True
        Case Is = 8: UnitAvailable = True
        Case Is = 10: UnitAvailable = True
              
    End Select
    
    strSQL = "UPDATE TourT Set UnitAvailable = " & bActionValue & " WHERE Unit = " & Me.ID_UnitT & " ;"
    CurrentDb.Execute strSQL, dbFailOnError


End Sub


Private Sub Form_BeforeUpdate(Cancel As Integer)


    Me.Employee = DLookup("EmployeeID", "LocalUserT")
    
    If IsNull([EntryDateTime]) Then
        [EntryDateTime] = Now()
    End If
    
    If IsNull([ID_Activity]) Then
        ID_Log = Nz(DMax("ID_Log", "CADLogT")) + 1
    End If
    
End Sub

The ID_UnitT is the name of the control on the combo box on the subform. It is a combo box where the operator selects the unit (which is actually the primary key of a record in the TourT table).
 
Let's take a couple of steps back. Many things become very easy if you "tablize" them as data and use that data to provide the answer.

Start with making a table to hold Actions if one does not exist. Then store the ActiveFlg in that table. Now you've solved two problems.
1. You don't have to modify all your code if you define a new Action.
2. In fact, you don't need ANY code at all:)

To get the "answer", you would use a left join to the Actions table in this subform's RecordSource and select the ActionFlg. Make sure to lock this control because you don't want to accidentally update it on this form since that would change the value in the Action table. You can also handle this in the query which is probably safer, instead of having
Select ..., ActionFlg, ....
Use
Select ..., ActionFlg & "" as ActionActive, ...

This makes the ActionFlg a calculated field named ActionActive and it will NOT be updateable in this query.

FYI, the Yes/No data type has TWO values - true and false. I see the latest code ignores two potential values but you really can't ignore them. You need to make them False. Because with a Yes/No data type, what is not true is false. You can't just ignore these values. If you really need a third state, you need to use an integer as the data type rather than Yes/No.
 
Thanks Pat,

First off, let me say that I took your advise, along with everyone else's and restructured all my tables to only have the Access generated ID. I eliminated the second field which I had been using to link tables so I should not have that procedure any longer to update the second field from the data in the ID field. It is taking me many many hours to do, but as you all suggested, it will help in the long run.

I have converted all of my Sharepoint linked tables to local tables for ease of working with them. Eventually, I will use a cloud service for remote operation of the Access db as you referenced before, i.e. Citrix, etc. Seems less complicated in the long run.

Now back to the issue at hand. I already have a table DispActionT, which has all the Actions in it. Would you recommend that field to be a Yes/No or use an integer such as 1 for true and 0 for false?

1660677715886.png


1660677693137.png
 
I would use Yes/No as long as you can only have 2 possible values (no Nulls). Also, I would rename your [ID] field to somehow prefix the table (e.g. DispAction_ID, da_ID, etc.). It can get painful when you have 20 [ID] fields in your database, so prefix them with something to make them easy to distinguish.
 
In the code from another thread, you had a Case statement that was setting the values of the Yes/No field to true or false but you were setting two of them to "" which was obviously causing a problem for a couple of reasons. To begin with "" is a ZeroLengthSTRING. If you believe its name, you will understand that you can't set a numeric field to a string value. You can set the Yes/No field to null but the end result is it is not treated as null. It is treated as False. So if you really need the third value, you cannot use the Yes/No data type, you have to use an integer. ALSO, if you are going to use a checkbox, toggling the checkbox goes from Yes to No to Yes. It never goes null once you have set a value to it. SO, if you want to make it null again (and I have cases where I need to), you MUST do it with VBA.

Me.chkStatus = Null
 
In regards to "you would use a left join to the Actions table in this subform's RecordSource and select the ActionFlg...
I'm confused with that one. How is doing a LEFT JOIN between the Actions table and the subform going to change the value in the parent form's record source (TourT), specifically the UnitAvailable field? Disregard the Yes/No control in the subform.... that was just to see if the Actions were working correctly with the CASE.
 
I would use Yes/No as long as you can only have 2 possible values (no Nulls). Also, I would rename your [ID] field to somehow prefix the table (e.g. DispAction_ID, da_ID, etc.). It can get painful when you have 20 [ID] fields in your database, so prefix them with something to make them easy to distinguish.
Thank you plog. I wanted to leave the primary key as it "came out of the box" so to speak. In the related field in a related table I us ID_(the name of the related table). That seems to make it easier for me... but just a preference I guess.
 
In the code from another thread, you had a Case statement that was setting the values of the Yes/No field to true or false but you were setting two of them to "" which was obviously causing a problem for a couple of reasons. To begin with "" is a ZeroLengthSTRING. If you believe its name, you will understand that you can't set a numeric field to a string value. You can set the Yes/No field to null but the end result is it is not treated as null. It is treated as False. So if you really need the third value, you cannot use the Yes/No data type, you have to use an integer. ALSO, if you are going to use a checkbox, toggling the checkbox goes from Yes to No to Yes. It never goes null once you have set a value to it. SO, if you want to make it null again (and I have cases where I need to), you MUST do it with VBA.

Me.chkStatus = Null
I chose to get away from the Yes/No and make the ActionFlg a 1 or 0 because a couple of the Actions I don't want any change in the unit's availability, ie... if the N, it simply means a Note Was Added. I can work with the 1 or 0 easier I think.
 
You missed Pat's point (and Doc who raised it initially).

It wasn't about Yes/No, 1/0. It was about your incomplete logic. You have 10 cases (1-10), you accounted for 8 of them. You need to define the other 2. Whether that be using whatever value (Yes/No/1/0/True/False/Black/White) or taking no action.

Right now, for those 2 cases you are taking an action with a Null value. If you want to take no action you need to reconfigure your code to do so.
 
I took the two "" cases out. The CASE ladder is now in the AFTER UPDATE action of the form, as I believe Pat recommended.

Code:
Private Sub Form_AfterUpdate()


    Dim strUnitAvail As Integer
    strUnitAvail = Me.ActionID.Value
    Select Case Action
        Case Is = 1: UnitAvailable = 0
        Case Is = 2: UnitAvailable = 0
        Case Is = 3: UnitAvailable = 0
        Case Is = 5: UnitAvailable = 1
        Case Is = 6: UnitAvailable = 1
        Case Is = 7: UnitAvailable = 1
        Case Is = 8: UnitAvailable = 1
        Case Is = 10: UnitAvailable = 1
             
    End Select
   
    strUnitAvail = "UPDATE TourT Set UnitAvailable = " & bActionValue & " WHERE ID = " & Me.TourID & " ;"
    CurrentDb.Execute strSQL, dbFailOnError


End Sub

When I run this, it errors on the
strUnitAvail = "UPDATE TourT Set UnitAvailable = " & bActionValue & " WHERE ID = " & Me.TourID & " ;"
CurrentDb.Execute strSQL, dbFailOnError


So now I am really confused.
 
My initial comment holds--you are using variables that you've neither declared nor set and the variables you are declaring and setting you aren't using.

strUnitAvail - you declare, you set twice (once illegally), you don't use
Action - you don't declare, you don't set, you do use
UnitAvailable - you don't declare, you do set, you don't use
bActionValue - you don't declare, you don't set, you do use
strSQL - you don't declare, you don't set, you do use
 
My initial comment holds--you are using variables that you've neither declared nor set and the variables you are declaring and setting you aren't using.

strUnitAvail - you declare, you set twice (once illegally), you don't use
Action - you don't declare, you don't set, you do use
UnitAvailable - you don't declare, you do set, you don't use
bActionValue - you don't declare, you don't set, you do use
strSQL - you don't declare, you don't set, you do use
Sorry, but at my knowledge level of VBA that is not any help. :(
 
Let's take a couple of steps back. Many things become very easy if you "tablize" them as data and use that data to provide the answer.

Start with making a table to hold Actions if one does not exist. Then store the ActiveFlg in that table. Now you've solved two problems.
1. You don't have to modify all your code if you define a new Action.
2. In fact, you don't need ANY code at all:)

To get the "answer", you would use a left join to the Actions table in this subform's RecordSource and select the ActionFlg. Make sure to lock this control because you don't want to accidentally update it on this form since that would change the value in the Action table. You can also handle this in the query which is probably safer, instead of having
Select ..., ActionFlg, ....
Use
Select ..., ActionFlg & "" as ActionActive, ...

This makes the ActionFlg a calculated field named ActionActive and it will NOT be updateable in this query.

FYI, the Yes/No data type has TWO values - true and false. I see the latest code ignores two potential values but you really can't ignore them. You need to make them False. Because with a Yes/No data type, what is not true is false. You can't just ignore these values. If you really need a third state, you need to use an integer as the data type rather than Yes/No.
I have a table called DispActionT, where I put an ActionFlg field as you suggested. The default value is nothing.

1660699900799.png


1660699928214.png


The in table CADLogT, I added

1660699991567.png


1660700093433.png


In the subform frmCAD-Log, the control UnitAvailable is accurate based on the CASE ladder I put in the forms BEFORE UPDATE event.

1660700146569.png


Code:
Private Sub Form_BeforeUpdate(Cancel As Integer)


    Me.EmployeeID = DLookup("EmployeeID", "LocalUserT")
   
    If IsNull([EntryDateTime]) Then
        [EntryDateTime] = Now()
    End If
   
   
    Dim strUnitAvail As Integer
    strUnitAvail = Me.ActionID.Value
    Select Case ActionID
        Case Is = 1: UnitAvailable = 0
        Case Is = 2: UnitAvailable = 0
        Case Is = 3: UnitAvailable = 0
        Case Is = 6: UnitAvailable = 1
        Case Is = 7: UnitAvailable = 1
        Case Is = 8: UnitAvailable = 1
        Case Is = 10: UnitAvailable = 1
             
    End Select

What I am ultimately wanting to achieve, as stated in my first post, is the UnitAvailable field on the table TourT is updated to either 1, or 0, or no change, to have the same value in the UnitAvailable control on subform frmCAD-Log, which is a subform of parent form CAD_CallDispSplitF (with a control source of TourT).

I hope this explained what I am needing a little better. If I understood you correctly, you said it could be accomplished by "tablizing" without the need for code, which in my case, would be great. But I am willing to try the code if necessary but I will need it explained in very very simple terms.
 
I am learning so any suggestions would be appreciated on a level that a beginner can understand. I am trying hard.
OK, lets talk about declarations and assignments and references. 'scuse me while I put on the "teacher hat."

1. When plog says "you don't declare" he is telling you that a particular variable has no visible declaration. A declaration is most often a DIM or PUBLIC or PRIVATE statement followed by a variable name followed by AS and finally followed by a valid data type. A couple of other keywords exist, but those are the "big three." That declaration allocates space for a variable of the given type.

If you do not use Option Explicit at the head of each module and DO reference a variable that has not been declared (see section 3 for "references"), that variable comes into existence anyway, as a data type of VARIANT - which is the only data type that can be NULL in value. When you have a declared variable, you have TWO pieces of information - its data type and a value that will be consistent with that data type. When you have an undeclared variable and no Option Explicit, you have a name but no data type. That data type is incredibly important.

If you declare something, there are three ways to make it have a value. The 2nd way is if you call a subroutine with an argument passed by reference, where the subroutine could internally assign a value (see 2nd section) and then pass back that value. The 3rd way is described later in the 2nd section. The 1st way to give it a value is to include an initialization value. Here is a simple example of declaration with an initial value:

PUBLIC PI AS DOUBLE = 3.141592653589793

That creates a public variable named PI as a scientific 64-bit number and initialized it with a 16-digit value. The reason I'm jumping on this point is that if you don't use Option Explicit and then perform an undeclared variable creation, you get a variable which is a variant that, depending on how it is used, will have a value of NULL, 0, "" (zero-length string), or NOTHING (an unassigned object). How do you know what type you will get? See section 3.

2. When plog says "you don't set" something, he is talking about value assignment. An assignment is simply a statement involving a variable, an equals sign, and any expression that can be evaluated to produce a value - a single value (not data type SINGLE, but single meaning what in math is called a SCALAR, or single-valued, expression). When the expression's evaluation is complete, the statement copies, or assigns, that value to the variable, allowing for the possibility that it might have to change the data type if the expression produces the wrong data type. This type conversion is why declarations matter.

If your expression computed a scientific number but you wanted a counting number (BYTE INTEGER, INTEGER, or LONG INTEGER), VBA will convert the number to the right data type if it can. (But SINGLE and DOUBLE can exceed the range allowed for any of those integers.) If you wanted a scientific number (SINGLE, DOUBLE) but got a counting number, VBA will convert the number to the right data type. (Conversions of any INTEGER to any SCIENTIFIC value always work but CAN lose precision sometimes.)

If you wanted a number but generated a text string, Access will know (by comparing data types) to warn you that something is wrong because text strings are not numbers and this will lead to a run-time error. This is why you ALWAYS want to declare variables with data types. The run-time system can tell if you screwed the pooch on an expression. What's worse is if you created an undeclared variable, that VARIANT will change SILENTLY to whatever data type is required for the expression in which it is used, but it will still have no value - and no warnings.

3. When plog says "you don't use" something, he is saying that you make no reference to it. This can be based on a declaration of a variable that appears in neither side of an assignment statement. Technically, an assignment IS a reference to the variable on the left-hand side of the equals sign. But a reference can also occur on the right-hand side of the equals sign. It can also occur in function or subroutine arguments. In each case, you take the value of the variable and feed it into whatever processing is called for.

An unreferenced variable is indicative of having wasted your time computing something you don't use. Unlike undeclared variables and unassigned variables, an unreferenced variable is unlikely to even get a minor error call-out. Access treats such things as "who cares" cases - because obviously if YOU didn't use it, YOU didn't care either. But the most common cases of these are spelling errors or a tricky memory where you called something by some name ... until you forgot and called it something else, thus failing to reference the REAL value you had set aside. So you get the wrong inputs into an expression.

4. Now, that furor over your 10-step CASE statement (actually, 8-step)... If you declare a variable but never assign it, its value is whatever is the default for that data type. Your cases 4 and 9 in your subroutine do not have assignments, so you are using whatever values are currently in the UnitAvailable variable, which is not declared and not passed in as a formal parameter to the subroutine. This leads to what is called a SIDE EFFECT - where an externally imposed value can linger. IF it happens that UnitAvailable comes from a prior declaration outside the subroutine, then if cases 4 or 9 ever pop up, you have leftovers that could lead to taking that UnitAvailable flag from a prior incarnation of that routine - since its value and placeholder were NOT in that routine. You DON'T KNOW in that case where their values originated. I might GUESS that the value you want comes in as FALSE by default - but it would be a GUESS and that is not usually a good programming technique. Guessing is for gamblers.

5. How do you stop this problem of ambiguous or uncertain values in variables? Assure that you ALWAYS use Option Explicit in EVERY module, usually as the very first line of that module. Then the compiler will gig you when you have an undeclared variable. And in the process of cleaning up those undeclared variables, you will discover things that need to have values assigned.

This is where you need to learn how to use one of the most powerful features of Access VBA - debugging. You discover what your code is doing by setting breakpoints and single-stepping through your code and examining values as you go. Tedious - but incredibly important. But that's the subject of a whole different discussion.
<<"teacher hat" comes off>>
 

Users who are viewing this thread

Back
Top Bottom