Auto Generate a Number that is not an auto number datatype.

OK... This is a new question that references to a previous post . Currently I have a form with a text field that has the  "default value" as =Nz(DMax("RepairNumber","dbo_WarrantyRepair1"))+1 which increments the "default value" set on the the RepairNumber field  of the table used as the record source for the form, and it works just fine, however now it will be used in as multi-user application and from what I read on some of the post here and elsewhere, that could be an issue. I was also reading some suggestion that recommend I use another table to generate the unique number and retrieve it from that table similar to what Dale Fye mentioned on that post. But I am not sure how to generate that table value and retrieve the unique value for each record.
skull52IT director Asked:
Who is Participating?

[Product update] Infrastructure Analysis Tool is now available with Business Accounts.Learn More

x
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

PatHartmanCommented:
To use a control table, you would use DAO to read the table, add one to the last used value and save it.  If you don't get an error, use the generated number.  If you get an error, try again.  So the part of the code that generates the ID must be inside a try again loop.  Depending on your environment, you may never actually run into contention but if you generate your own ID, then you MUST be prepared for it.  A slightly simpler alternative in that it doesn't require a loop, is to use a table that has two columns.  One is the autonumber, the second is the user ID.  You then insert a row in the table using your ID.  Then you run a query to retrieve the max autonumber value for your user ID.
0
skull52IT director Author Commented:
Ok... an example would be nice.  I cant use the autonumber datatype because the field that it would be inserted to is a Text datatype.
0
skull52IT director Author Commented:
I would like to use the function Dale Fye described but not sure how to call it or where to place it in my code.
0
The 7 Worst Nightmares of a Sysadmin

Fear not! To defend your business’ IT systems we’re going to shine a light on the seven most sinister terrors that haunt sysadmins. That way you can be sure there’s nothing in your stack waiting to go bump in the night.

Dale FyeOwner, Developing Solutions LLCCommented:
skull,

Generally, if you want to display the Repair Number on your form, while data is being generated, I would place it in the Form_Current event; something like:

Private Sub Form_Current

    if me.newRecord Then
        me.txt_RepairNumber = fnNextVal("YourTableName", "RepairNumber")
    end if

End Sub

Open in new window

You would have to replace "YourTableName" with the name of the table where your "RepairNumber" field is located.

You could modify the table I mentioned in that earlier response and rather than having fields for TableName  and FieldName, you could simply use a field like "VarName", and then, you would modify the code so that you only pass one value to the function "RepairNumber", and replace the search string (strCriteria) with something like:

    strCriteria = "[VarName] = '" & VarName & "'"

You would also have to modify the code following the AddNew to remove the references to TableName and FieldName and replace that with VarName.

Dale
0
PatHartmanCommented:
Ok... an example would be nice.  I cant use the autonumber datatype because the field that it would be inserted to is a Text datatype

Open in new window

You are not defining the field in YOUR table as an autonumber.  You are defining the field in the CONTROL table as autonumber.  You can take the generated number and paste it into the text field.

I guess I don't understand why you accepted the solution in the other post if you didn't understand how to implement it.
0
aikimarkCommented:
Do you need to generate a GUID?
0
skull52IT director Author Commented:
Dale I don't need to display the number on the form in fact I have that control hidden, I just need it to save the value in the table.
0
skull52IT director Author Commented:
Pat, that solution was a by product of the original post.
0
Dale FyeOwner, Developing Solutions LLCCommented:
skull,

The Current event should still work fine, but if you are concerned about your Repair numbers not being consecutive, then I would wait until just before the record is saved, maybe in the cmd_Save_Click event (that is the way I would do it).
0
PatHartmanCommented:
I would put the code in the BeforeUpdate event.  That way, it is as close to the save as possible and that minimizes your risk of assigning a duplicate.  Using the Current event would almost certainly result in duplicates more frequently because people get distracted, start a record and then take a phone call.  Meanwhile, someone else grabs the same number and inserts their record.  

I didn't look at the actual function but I assume It checks for duplicates and generates a new number.  If not, you'll need to handle that here.  The autonumber handles duplicates by assigning the number immediately.  That is one of the reasons people don't like the autonumber.  If you end up not saving a record, you end up with a gap that can't be filled.
0
Dale FyeOwner, Developing Solutions LLCCommented:
Pat,

The technique I recommended will not create duplicates because it doesn't use the DMAX( ) function to return the value.  It stores the next value in a separate table and uses DAO to open the table, retrieve the current (next) value, and then increment and save the new value.
0
PatHartmanCommented:
Thanks Dale.  I prefer that method also.
0
skull52IT director Author Commented:
Ok... This is driving me crazy. I created a table (tbl_NextVal) i set the default Value to 900000 so that that would be the starting value and increment that value by one, created the public function in a module, and put the function call on the forms current event  as soon as I open the form I get error 3265 Item not found in this collection, it happens twice then opens the form after clicking OK, also the Repair number on the form is 0 and not 9000000 as expected. If i OK past the error it saves the record but with 0 as the repair number and never advances but stays as 0.

Function
Option Compare Database
Public Function fnNextVal(VarName As String, FieldName As String) As Long

    Dim strCriteria As String
    Dim db As DAO.Database
    Dim rs As DAO.Recordset

    On Error GoTo ProcError

    Set db = CurrentDb
    Set rs = db.OpenRecordset("tbl_NextVal", dbOpenDynaset, dbFailOnError)

  strCriteria = "[VarName] = '" & VarName & "'"
  
    rs.FindFirst strCriteria
    If rs.NoMatch Then
        rs.AddNew
        rs!VarName = VarName
        'rs!FieldName = FieldName
        rs!NextVal = 2
    Else
        rs.Edit
        rs!NextVal = rs!NextVal + 1
    End If
    fnNextVal = rs!NextVal - 1
    rs.Update

ProcExit:
    On Error Resume Next
    rs.Close
    Set rs = Nothing
    Set db = Nothing
    Exit Function

ProcError:
    MsgBox Err.Number & vbCrLf & Err.Description

Open in new window


Current Event
Private Sub Form_Current()
    If Me.NewRecord Then
        Me.RepairNumber = fnNextVal("dbo_WarrantyRepair1", "RepairNumber")
    End If
Me![lblCR].Visible = True
Me![lblMG].Visible = True
End Sub

Open in new window


All code for the form
Option Compare Database
Private Sub btnSave_Click()
'Save the record.
   On Error GoTo btnSave_Click_Error
    RunCommand acCmdSaveRecord
    'Load a new record after the save.
    DoCmd.GoToRecord , , acNewRec

btnSave_Click_Error:
' If Err.Number = 2501 Then Exit Sub
Select Case True
    Case Err.Number = 0 'no error, just let the sub end
    Case Err.Number = 2501 'BeforeUpdate was cancelled an a msgbox generated, let this sub end
    Case Else 'something we haven't encountered yet has occurred.  Msgbox it
        MsgBox "Error " & Err.Number & " (" & Err.Description & ") in procedure btnSave_Click of VBA Document Form_ISSC"
End Select

End Sub

Private Sub cboCSR_Exit(Cancel As Integer)
   On Error GoTo cboCSR_Exit_Error

' This Make the Label super imposed on the CSR Combo Box not Visible
If Len(cboCSR) <> 0 Or IsNull(cboCSR) = False Then
Me![lblCR].Visible = False
End If
   On Error GoTo 0
   Exit Sub

cboCSR_Exit_Error:

    MsgBox "Error " & Err.Number & " (" & Err.Description & ") in procedure cboCSR_Exit of VBA Document Form_ISSC Product Return Authorization"
End Sub

Private Sub cboMod_Exit(Cancel As Integer)
   On Error GoTo cboMod_Exit_Error
   
 ' This Make the Label super imposed on the Model Combo Box not Visible
If Len(cboMod) <> 0 Or IsNull(cboMod) = False Then
Me![lblMG].Visible = False
End If
   On Error GoTo 0
   Exit Sub

cboMod_Exit_Error:

    MsgBox "Error " & Err.Number & " (" & Err.Description & ") in procedure cboMod_Exit of VBA Document Form_ISSC Product Return Authorization"
End Sub
Private Sub Form_AfterUpdate()
Me![lblCR].Visible = True
Me![lblMG].Visible = True
End Sub

Private Sub Form_BeforeUpdate(Cancel As Integer)
' Validates that Control has Data for required feilds
If Len(Firstname) = 0 Or IsNull(Firstname) = True Then
  Call MsgBox("You must enter First Name", vbExclamation, "Add Firstname")
  Me.Company.SetFocus
  Me.Firstname.SetFocus
   Cancel = True
    Exit Sub
End If
If Len(Lastname) = 0 Or IsNull(Lastname) = True Then
  Call MsgBox("You must enter Last Name", vbExclamation, "Add Lastname")
  Me.Company.SetFocus
  Me.Lastname.SetFocus
   Cancel = True
      Exit Sub
End If
If Len(Address1) = 0 Or IsNull(Address1) = True Then
  Call MsgBox("You must enter Address1", vbExclamation, "Add Address1")
  Me.Company.SetFocus
  Me.Address1.SetFocus
   Cancel = True
      Exit Sub
End If
If Len(City) = 0 Or IsNull(City) = True Then
  Call MsgBox("You must enter City", vbExclamation, "Add City")
  Me.Company.SetFocus
  Me.City.SetFocus
   Cancel = True
     Exit Sub
End If
If Len(State) = 0 Or IsNull(State) = True Then
  Call MsgBox("You must select a State", vbExclamation, "Add State")
  Me.Company.SetFocus
  Me.cboState.SetFocus
   Cancel = True
      Exit Sub
End If
If Len(Zip) = 0 Or IsNull(Zip) = True Then
  Call MsgBox("You must enter a Zip", vbExclamation, "Add Zip")
  Me.Company.SetFocus
  Me.Zip.SetFocus
   Cancel = True
      Exit Sub
End If
If Len(Phone) = 0 Or IsNull(Phone) = True Then
  Call MsgBox("You must enter a Phone No", vbExclamation, "Add Phone")
  Me.Company.SetFocus
  Me.Phone.SetFocus
   Cancel = True
      Exit Sub
End If
If Len(cboCSR) = 0 Or IsNull(cboCSR) = True Then
    Call MsgBox("You must select a CSR", vbExclamation, "Add CSR")
    Me.Company.SetFocus
    Me.cboCSR.SetFocus
   Cancel = True
      Exit Sub
End If
If Len(cboMod) = 0 Or IsNull(cboMod) = True Then
    Call MsgBox("You must select a Model", vbExclamation, "Add Model")
    Me.Company.SetFocus
    Me.cboMod.SetFocus
   Cancel = True
      Exit Sub
End If
If Len(Serial) = 0 Or IsNull(Serial) = True Then
    Call MsgBox("You must select a Serial Number", vbExclamation, "Add Serial")
    Me.Company.SetFocus
    Me.Serial.SetFocus
    Cancel = True
       Exit Sub
End If
If Len(ProblemDesc) = 0 Or IsNull(ProblemDesc) = True Then
    Call MsgBox("You must select a Problem Description", vbExclamation, "Add ProblemDesc")
    Me.Company.SetFocus
    Me.ProblemDesc.SetFocus
    Cancel = True
       Exit Sub
End If
End Sub
Private Sub Form_Current()
    If Me.NewRecord Then
        Me.RepairNumber = fnNextVal("dbo_WarrantyRepair1", "RepairNumber")
    End If
Me![lblCR].Visible = True
Me![lblMG].Visible = True
End Sub
Private Sub Form_Error(DataErr As Integer, Response As Integer)
On Error GoTo Err_Form_Error

    If DataErr = 3022 Then
          Response = IncrementField(DataErr)
Exit_Form_Error:
    Exit Sub

Err_Form_Error:
    MsgBox Err.Description
    Resume Exit_Form_Error
End If
End Sub
Private Sub Form_Load()
DoCmd.GoToRecord , "", acNewRec
End Sub
Function IncrementField(DataErr)
    If DataErr = 3022 Then
       Me!RepairNumber = Nz(DMax("RepairNumber", "WarrantyRepair1")) + 1
       IncrementField = acDataErrContinue
    End If
End Function

Open in new window

0
Dale FyeOwner, Developing Solutions LLCCommented:
It looks like you may have created a hybrid of my two code examples, because the function declaration uses VarName and FieldName, but you are passing in the TableName and FieldName.  If you show me what your tbl_NextVal looks like (take a screen shot of the data view), I should be able to tweak the code properly.
0
skull52IT director Author Commented:
Thanks Dale, here you go Number table
0
Dale FyeOwner, Developing Solutions LLCCommented:
get rid of the default value.  If you want to use it this, way, then the table needs two fields:

varName (text)
NextVal (long)

Then enter a single record in the table with values:
"RepairNumber", 9000000

The code for your Current event should be:

Private Sub Form_Current()
    If Me.NewRecord Then
        Me.RepairNumber = fnNextVal("RepairNumber")
    End If
    Me![lblCR].Visible = True
    Me![lblMG].Visible = True
End Sub

Open in new window

and the code for the function should be:

Public Function fnNextVal(VarName As String) As Long

    Dim strCriteria As String
    Dim db As DAO.Database
    Dim rs As DAO.Recordset

    On Error GoTo ProcError

    Set db = CurrentDb
    Set rs = db.OpenRecordset("tbl_NextVal", dbOpenDynaset, dbFailOnError)

  strCriteria = "[VarName] = '" & VarName & "'"
  
    rs.FindFirst strCriteria
    If rs.NoMatch Then
        rs.AddNew
        rs!VarName = VarName
        rs!NextVal = 2
    Else
        rs.Edit
        rs!NextVal = rs!NextVal + 1
    End If
    fnNextVal = rs!NextVal - 1
    rs.Update

ProcExit:
    On Error Resume Next
    rs.Close
    Set rs = Nothing
    Set db = Nothing
    Exit Function

ProcError:
    MsgBox Err.Number & vbCrLf & Err.Description
    Resume ProcExit

End Function

Open in new window

I believe that should do it.
0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
skull52IT director Author Commented:
Dale, that seems to wok perfectly, thanks a lot. Now I hate to be a PITA but could you briefly explain how the function works, I like to understand  how something works and not just cut and paste. I kind of understand some of it and I bolded the areas that i don't quite understand what it does.

   Dim strCriteria As String
    Dim db As DAO.Database
    Dim rs As DAO.Recordset

    On Error GoTo ProcError

    Set db = CurrentDb
    Set rs = db.OpenRecordset("tbl_NextVal", dbOpenDynaset, dbFailOnError)

  strCriteria = "[VarName] = '" & VarName & "'"
 
    rs.FindFirst strCriteria
    If rs.NoMatch Then
        rs.AddNew
        rs!VarName = VarName
       rs!NextVal = 2
    Else
        rs.Edit
        rs!NextVal = rs!NextVal + 1
    End If
    fnNextVal = rs!NextVal - 1
    rs.Update

ProcExit:
    On Error Resume Next
    rs.Close
    Set rs = Nothing
    Set db = Nothing
    Exit Function

ProcError:
    MsgBox Err.Number & vbCrLf & Err.Description
    Resume ProcExit

End Function
0
skull52IT director Author Commented:
OK, now I am getting an issue when I try to close the form it runs the data validation code on the Form_BeforeUpdate event and telling me to enter a first name, I click OK and the following  error box pops, then it saves a blank record with just the Repair number  and of course it increments to the next number. Why would it be running the before update event on close?Error on form close
0
skull52IT director Author Commented:
Man... I have never had so much trouble with an Access App.
0
Dale FyeOwner, Developing Solutions LLCCommented:
1.  The code looks for a record that matches the VarName value in tbl_NextVal.  If it doesn't find that value, it creates a new record and stores the value (2), which is the value you want the function to return the next time the function is run.  If it finds "RepairNumber", then it increments the NextVal field (again the value you want to return the next time the function is run), and then returns !NextVal-1.

I do it this way to minimize the lines of code to execute the procedure.  You could set the new value to 1, set fnNextVal to that value, then increment !NextVal, but that would take an extra line of code.  I put the line:

fnNextVal = rs!NextVal - 1

outside of the If/Then/Else/End construct to also reduce the number of lines of code.  You could put that line inside the Then and Else sections of the IF/EndIf construct, but that would take two lines, when you only need one.

2.  "Why would it be running the before update event on close? "

On close of what; the form?  It makes sense that the Form_BeforeUpdate event would fire when you close the form.  I generally include "Save" and "Close" buttons on my data entry forms, and prevent the user from closing the form if it is dirty by putting code in the Form_Unload event.
Private Sub Form_Unload(Cancel as Integer)

    Cancel = me.Dirty
    If Cancel then msgbox("Click Save or Cancel before attempting to leave this record", vbokonly)

End Sub

Open in new window

This way, the user is forced to click one of those buttons.  If they click Cancel, I use the Undo method to cancel any changes to the record.  If they click Save, I run my validation code.  Many developers prefer to use the Form_BeforeUpdate event to run their validation code, but either method works.
0
skull52IT director Author Commented:
Yes the form, I think  my issue is that my validation is on the before update event.  when the last record is entered and saved and the users attempts to close the form which has no data because the record was saved it runs the validation on the before_Update an then throws the error,  so I will move my validation to the save button, which I usually do , but I tried it there on the recommendation from another post.  Also is it possible that I am having issues because the form is dirty because the returnnumber and date created controls have data (See Fig 1). Also I am getting another weird issue  when I go into design view on the form it throws an error (See Fig 2) and saves what ever value is in the returnnumber  and the Date created to the  dbo_WarrantyRepair1 table.

Fig 1
Form
Fig 2
Error on design
0
Dale FyeOwner, Developing Solutions LLCCommented:
Yes, the fact that you are generating the RepairNumber in the Current event is probably the problem with trying to close the form, and the Before Update kicking off.  One of the reasons I add a Cancel button to my data entry forms is to handle situations like this.  In the Cancel button I use code similar to:

Private sub cmd_Cancel_Click

    if me.dirty then me.undo
    docmd.close acform, me.name

End Sub
0
skull52IT director Author Commented:
Dale, yes I plan to add a cancel button to my form to allow the user to cancel an entry and start anew, but not close the form. In this situation would you agree I should move the validation to the save button. should I also put your cancel code on the form close button to clear any data?
0
Dale FyeOwner, Developing Solutions LLCCommented:
If you have a Save button, that is where I normally put my validation code.  Additionally, if you don't really need to see the RepairNumber on screen, then I would wait until after all of the validation checks have been performed before I actually fill that value, instead of using the Current event.

But if you are going to do that, you will also need to put some code in the Form_Unload event, to prevent the form from unloading if the Form is dirty.
0
skull52IT director Author Commented:
Dale I did move my validation to the save button which did fix the errors, and I don't need to see the number on the from, in fact when I release the app it won't be visible. so I put the repairnumber code just after all the validation and just before the save command and that is working. Now for the
 <But if you are going to do that, you will also need to put some code in the Form_Unload event, to prevent the form from unloading if the Form is dirty.> I put the code you suggested but it threw a syntax error
Private Sub Form_Unload(Cancel as Integer)

    Cancel = me.Dirty
    If Cancel then msgbox("Click Save or Cancel before attempting to leave this record", vbokonly)

End Sub

Open in new window

0
Dale FyeOwner, Developing Solutions LLCCommented:
What was the syntax error?  If you attempt to compile the code, it will highlight errors?  

You might try:

IF Cancel = True then  msgbox "Click Save or Cancel before leaving this record", vbOkOnly

Without the ( ) around the msgbox arguments.
0
skull52IT director Author Commented:
yes it highlighted  If Cancel then msgbox("Click Save or Cancel before attempting to leave this record", vbokonly)
0
Dale FyeOwner, Developing Solutions LLCCommented:
did you try the change I recommended, dropping the ( )  after msgbox?
0
skull52IT director Author Commented:
Yes, and that fixed the error however when I click on the close button and the form is dirty the on unload event doesn't seem to fire as the form just close with no message and save the content of the control with the data.
0
skull52IT director Author Commented:
So... I put this on the close button
Private Sub btnClose_Click()
On Error GoTo btnClose_Click_Err
If Me.Dirty Then
 If MsgBox("Click Save or Cancel before leaving this record", vbOKOnly) Then GoTo btnClose_Click_Exit:
'Exit Sub
Else
    DoCmd.Quit acPrompt


btnClose_Click_Exit:
    Exit Sub

btnClose_Click_Err:
    MsgBox Error$
    Resume btnClose_Click_Exit
End If
    
End Sub

Open in new window

0
Dale FyeOwner, Developing Solutions LLCCommented:
So, you have 3 buttons (Close, Cancel, Save)?

You should put that code in the Form_Unload event to ensure that it occurs even when the user tries to close the form with the X or Close option from the Form controls (top left) button.  
Private Sub Form_Unload(Cancel as Integer)

dim strMsg as string

strMsg = "Click Cancel or Save before attempting to close this form!"
if me.dirty = False Then
    'do nothing - proceed
Else 
    msgbox strMsg, vbOkOnly
    Cancel = true
end if

End Sub

Open in new window

Then, in my cmd_Close event, I would simply do:

docmd.quit

Although I generally don't quit directly from a data entry form.  In most cases, closing a data entry form will take me back to some other form or switchboard.
0
skull52IT director Author Commented:
Yes I have 3 buttons, OK I will do that but it didn't fire the previous  code you suggested, also this is the only form in the application so that's why I  close it directly
0
skull52IT director Author Commented:
I put the code you suggested and it still doesn't fire, when I click the close button with the form dirty it just closes
0
skull52IT director Author Commented:
it is completely ignoring that code, i even tried it in the on close event and it didn't fire
0
skull52IT director Author Commented:
Ok... the unload event does not fire at all
0
Dale FyeOwner, Developing Solutions LLCCommented:
Are you sure that you have a reference in the form to the procedure in the code?  Open the form in design view, select the form, then look at the Events tab on the properties dialog.

Does it say [Event Procedure] along side the Unload event?
0
skull52IT director Author Commented:
Yep...event.jpg
Private Sub Form_Unload(Cancel As Integer)
Dim strMsg As String

strMsg = "Click Cancel or Save before attempting to close this form!"
If Me.Dirty = False Then
    'do nothing - proceed
Else
    MsgBox strMsg, vbOKOnly
    Cancel = True
End If

End Sub

Open in new window

0
Dale FyeOwner, Developing Solutions LLCCommented:
My bad.  The unload event fires after the record is saved, so that is not going to work.

OK, here is what I found looking at one of my applications.
1.  the default "Enabled" property for cmd_Cancel and cmd_Save is No, and cmd_Close is Yes
2.  use the Form_Dirty event to change that enabled status so the cmd_Close is disabled and cmd_Cancel and cmd_Save are enabled.
3.  in the cmd_Cancel_Click event, use me.undo to clear changes to the form, and then set cmd_Close.Enabled = true and cmd_Cancel.Enabled and cmd_Save.Enabled to False
4.  in cmd_Save_Click, after your validation checks are complete, set cmd_Save.Enabled and cmd_Cancel.Enabled = False and cmd_Close.Enabled = true.  Do this just before the me.dirty = true line so that the BeforeUpdate event will not be cancelled.
5.  Disable the close button on the form - format tab of the Properties dialog.  This way, the only way the user can close the form is with your close button.
6.  In each of the cases above, where you are changing the Enabled status of your buttons, you will first need to set the focus to some other control.  I usually enable a control, then set the focus to it, then disable the other control(s).
7.  Then put the following in the Form_BeforeUpdate event:

Private Sub Form_BeforeUpdate(Cancel as Integer)

    if me.cmd_Save.Enabled then
        msgbox "Click Save or Cancel!", vbOkOnly
        Cancel = true
    EndIf

End Sub

So, when the form is dirtied, your close button will be disabled and the cancel and save buttons will be enabled.  When you click Cancel, it will undo the changes on the form, enable cmd_Close and disable the other two buttons.  If you hit Save, it will do the validation checks, and if they pass, it will change the enabled status of the three buttons and then save the record.
0
skull52IT director Author Commented:
Dale, I will implement your suggestions above. I want to thank you for hanging in there with me you helped me immensely.
0
Dale FyeOwner, Developing Solutions LLCCommented:
Glad to help.  That's why we are here.
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Microsoft Access

From novice to tech pro — start learning today.