Warning of duplicates in MS-Access - ms-access

I am working on a MS-Access database to keep track of last names and addresses and several other records. I am making a table and would like for it to be updated by the user by inputting info from a form. The question I have is can I have several key items that would warn if a duplicate is found. Mainly if a duplicate is found in the last name and the address fields. I need the form to accept the duplicate because how the info is being obtained duplicates are possible but need to be able to enter the duplicate and warn the user while entering the data that a previous entry has already been made that is similar.
Is this possible in Access, and easy to implement that many different users may be entering the data? I haven't used Access for some time, sorry if I'm not using the correct terms, as I'm still learning the basics of Access, as I mainly work in Excel. Excel is the current solution that is being used, but finding quickly that a better solution is needed.
Thanks

Names aren't good candidates for primary keys and it seems that you have set the name as the primary key of one of your tables. You should either use a better candidate key (social insurance number, for example) or create a surrogate (artificial, if you want) key with no meaning other than being a key for your table.
Without more detail about your table structure it's hard to be more specific.

Warn the user but allow her to proceed if she wishes?
If so, a key constraint would be too, ahem, constraining. Just check for the duplicates manually (by explicitly querying existing data from your code). You may wish to index the field(s) being searched.

Yes, you can validate through this code:
If Nz(DCount("FieldName", "table", "FieldName= '" & Me.FieldName.Value & "'"), 0) > 0 Then
If MsgBox("The FieldName already exist", vbOKCancel, "Duplicate Warning") = vbCancel Then Cancel = True
Me.Undo
Exit Sub
End If
End If

In general terms, Access (and relational databases generally) are about preventing duplicate data from being entered in the first place. If you merely want to 'warn', then it will take a bit of custom code. So, say the table is called Customers and it has fields such as CustomerID (AutoNumber, primary key) and LastName (Text). The form then has a text box called txtLastName that is bound to the LastName field. In this situation, you could handle the text box's BeforeUpdate event like this:
Option Compare Database
Option Explicit
Const DuplicateWarningTitle = "Duplicate Record"
Const DuplicateValueWarning = "A record already exists with " + _
"the value you have entered. Are you sure you want to continue?"
Function GetSQLLiteral(Value As Variant) As String
Select Case VarType(Value)
Case vbNull
GetSQLLiteral = "Null"
Case vbString
GetSQLLiteral = "'" + Replace(Value, "'", "''") + "'"
Case Else
GetSQLLiteral = CStr(Value)
End Select
End Function
Sub HandleKeyFieldBeforeUpdate(Control As Access.TextBox, Cancel As Integer)
Dim RS As DAO.Recordset
Set RS = CurrentDb.OpenRecordset( _
"SELECT 1 FROM Customers " + _
"WHERE " + Control.ControlSource + " = " + GetSQLLiteral(txtLastName.Value) + _
" AND CustomerID <> " & Me.CustomerID)
If Not RS.EOF Then
Select Case MsgBox(DuplicateValueWarning, vbExclamation + vbYesNo, DuplicateWarningTitle)
Case vbYes
' just proceed
Case vbNo
Cancel = True
Control.Undo ' if desired!
End Select
End If
End Sub
Private Sub txtLastName_BeforeUpdate(Cancel As Integer)
HandleKeyFieldBeforeUpdate txtLastName, Cancel
End Sub
By pushing the duplicate checking code into a generic helper routine, other key fields can be handled in the same way easily:
Private Sub txtAddress_BeforeUpdate(Cancel As Integer)
HandleKeyFieldBeforeUpdate txtAddress, Cancel
End Sub

Related

MS Access: Lookup excluding already-selected values; check unique key before record entry complete

I'm currently struggling how to set up validation rules for forms in either Datasheet or Form view that trigger immediately upon going to either another field within the record or to another record entirely.
My form is designed to add records to one destination table where the primary key column needs to match a value of a specific field in any record of a source table. The rest of the fields in the form (and destination table) are for general user input (some DateTime fields, some text, some decimal).
I can get Access to display a standard error dialogue when a user attempts to free-enter a value not on the list immediately after selection of another field or record. The error displayed is
The text you entered isn't an item in the list.
Select an item from the list, or enter text that matches one of the listed items"
And if I reselect a lookup value already selected and go to the next record, I get
The changes you requested to the table were not successful because they would create duplicate values in the index, primary key, or relationship. Change the data in the field or fields that contain duplicate data, remove the index, or redefine the index to permit duplicate entries and try again.
However, I'd like that error (or similar) to appear immediately if going to another field within the same record. In other words, I want it to tell me that it's a duplicate before allowing the user to fill out the rest of the current record in the form or table.
I would like the selection list be restricted to values not previously present in the destination table. Obviously if editing an already-created entry, you should be able to keep the value you had previously (i.e. that value wouldn't be excluded from the dropdown list).
Alternatively there would be a selection dialogue that would appear if an otherwise valid value was duplicated.
Duplicate Value
You've already used that value. Would you like to change this record or the previously-entered record.
⪡ This One ⪢ < Previous >
If "Previous" is selected, it would jump up to the same field in the indicated record, providing a dropdown list for re-selection (and once done would jump back to the "current" record and autoselect the temporarily duplicated value.
I'll edit this post in a bit with my table design details, as well as source setups for the form.
"Solved" this in VBA.
Private Sub MyControl_AfterUpdate()
newval = Me.MyControl.Value
oldval = Me.MyControl.OldValue
If newval = oldval Then Exit Sub ' everything's okay
Dim rs As Object
Set rs = Me.Form.Recordset
whereclause = "MyControl = '" & newval & "'"
qry = "SELECT COUNT(*) as c FROM MyQuery WHERE " & whereclause
qrs = CurrentDb.OpenRecordset(qry)
If qrs.Count = 1 Then cnt = qrs(0).Value
If cnt >= 1 Then
selval = MsgBox("Would you like to keep your selection for this record?" & vbCrLf & "[yes = change previous record's MyField; no = change MyField for this record]", vbYesNo Or VbMsgBoxStyle.vbExclamation Or vbSystemModal Or vbDefaultButton2 Or vbMsgBoxSetForeground, "Duplicate MyField selection encountered")
If selval = vbYes Then
' set focus to the other entry, preserving selection here
thissel = Me.MyControl.ListIndex
Me.MyControl.Value = "temp" ' if a string is okay & so we can jump away from this record
thisloc = Me.Form.CurrentRecord ' record number
rs.Findfirst (whereclause)
thatloc = Me.Form.CurrentRecord
Debug.Print (thisloc & "now ; was" & thatloc)
Me.MyControl.Value = "invalid"
DoCmd.GoToRecord , , acGoTo, thisloc ' jump to the new row
Me.MyControl.Value = newval
DoCmd.GoToRecord , , acGoTo, thatloc ' jump to the one to change
If thissel <= 0 Then thissel = 1 ' may not be useful, given the error handling below
On Error Resume Next
Me.MyControl.ListIndex = thissel - 1
On Error GoTo 0
Me.MyControl.Dropdown
ElseIf selval = vbNo Then
Me.MyControl.Value = Me.MyControl.OldValue
Me.MyControl.Undo ' for some reason this doesn't clear the "dirty" bit, resulting in the edit pencil showing up for the row.
Me.MyControl.SetFocus
End If
Else
Debug.Print ("There were no matches! Das ist gut")
End If
End Sub
Residual Issue(s)
Selecting "Yes" from the dialogue and then hitting escape puts the invalid "invalid" string into the box, which ignores any requirement to restrict the final selection to one in the list (ie Limit to List = 1). Ideally this would "roll back" the two rows.
Maybe I should change this to an OnExit Event so it doesn't trigger before I even leave the cell? Not sure if the primary key violation would happen before this triggers though. OnChange triggers much too frequently (every time you down-arrow through the list).

Allow new record of duplicated cell value if a cell is null

Above is a table that shows what I want to prevent. It allows the entry of a record where the same machine, 1, is related to two contracts at the same time. How do I make a table where I can only insert a new record if all other records with the same idMachine have the cell contracEnded NOT NULL? Thanks for the help.
Assuming you are using a form to enter your data (which you should be) you could try putting the following VBA code in the BeforeUpdate event of the control you are using to enter the data. Here I'm assuming this control is a text box named txtIdMachine, which is bound to the idMachine field in your table (which I'm assuming is named tblMachines).
Private Sub txtIdMachine_BeforeUpdate(Cancel As Integer)
Dim numMachines As Integer, numEnded As Integer
numMachines = DCount("idMachine", "tblMachines", _
"idMachine = " & Me.txtIdMachine)
numEnded = DCount("*", "tblMachines", _
"contractEnded Is Not Null And idMachine = " & Me.txtIdMachine)
If (numMachines - numEnded > 0) Then
Cancel = True
MsgBox "Machine " & Me.txtIdMachine & " is already in use." & _
vbNewLine & "Select a new machine."
End If
End Sub
The DCount(A,B,C) function will return the number of matching entries from field A in a table or query B that matches the search string C (basically the WHERE clause from an SQL query). A,B,C are strings. The "*" tells it to search all columns.
In the end you just have to subtract the number of instances of the machine number the user is trying to enter Me.txtIdMachine from the number of non-Null end dates for that machine, and check to see if there are any idMachine without an end date. Since it's done BeforeUpdate, the current entry doesn't exist yet, so it doesn't count.

how to write a script to look for and correct invalid entries in a table

First off I'd like to make perfectly clear that my knowledge of Access and VBA is extremely limited at best. I have an employee database system that due to it's age has been prone to small data corruption issues and controls breaking due to differences between 2003/2007 and 2010. While I've managed to hash out the bulk of the problems, one that has me especially concered is the script we're using to manage access to the database. The system is split between two files, a frontend where users can access the database and a backend file that contains all of the tables.
The issue I have is in the frontend form that handles the logon for the users. The way the access system is set up is the user enters their SSN, then the script finds their SSN in the table and if it exists looks if an access checkbox is checked. If they have access, they're directed to the main menu, if not they get a denied message. What I've found though is for some reason or another, if an entry in the personnel table has an incomplete SSN, the script breaks and anyone can gain access to the database.
There's a query that runs in the frontend that looks at the master personnel table and pulls just the first two columns, SSAN and Access.
The form itself has a visible text box, "Text8", and a hidden Combo Box "Combo4". Combo4 uses the previously mentioned query for the row source (SELECT qryAccess.SSAN FROM qryAccess;), while Text8 is where the user enters their SSN.
Here's the code right now:
Option Compare Database
Private Sub Combo4_AfterUpdate()
' Find the record that matches the control.
Dim rs As Object
Set rs = Me.Recordset.Clone
rs.FindFirst "[SSAN] = '" & Me![Combo4] & "'"
If Not rs.EOF Then Me.Bookmark = rs.Bookmark
If Me![Access] = True Then
DoCmd.RunMacro "Access"
Else
DoCmd.OpenForm "frmDenied"
End If
End Sub
Private Sub Text8_AfterUpdate()
Me![Combo4] = Me![Text8]
' Find the record that matches the control.
Dim rs As Object
Set rs = Me.Recordset.Clone
rs.FindFirst "[SSAN] = '" & Me![Combo4] & "'"
If Not rs.EOF Then Me.Bookmark = rs.Bookmark
If Me![Access] = True Then
DoCmd.RunMacro "Access"
Else
DoCmd.OpenForm "frmDenied"
End If
End Sub
Like I said before, as long as every entry for the SSNs is a full 9-digits, this system works. However, if for some reason the entry is not the full 9 like I just found in my database (and no, I have no idea what caused that to happen, there is an input mask in place, 000-00-0000;;_), this system breaks. You could type in "abc" for the SSN and gain access to the database.
How can I write a small script that pre-checks the table for SSN entries that don't fit the 9-digit format that is set, and if it finds them, resets them to an unused number, such as 000000000, 000000001, etc?
Also, if you have any suggestions on how to streamline the existing code, I'd be more than happy to take them.
Add this function to you application
Public Function IsValidSSN(ByVal SSN As String) As Boolean
'Determines if SSN is a valid social security number
'requires SSN to be in either "#########" or "###-##-####" format
IsValidSSN = (SSN Like "###-##-####") Or _
SSN Like ("#########")
End Function
Also change your function to this:
Private Sub Combo4_AfterUpdate()
' Find the record that matches the control.
If IsValidSSN(Me![Combo4]) Then
Dim rs As Object
Set rs = Me.Recordset.Clone
rs.FindFirst "[SSAN] = '" & Me![Combo4] & "'"
If Not rs.EOF Then Me.Bookmark = rs.Bookmark
If Me![Access] = True Then
DoCmd.RunMacro "Access"
Else
DoCmd.OpenForm "frmDenied"
End If
Else
DoCmd.OpenForm "frmDenied"
End IF
End Sub
Private Sub Text8_AfterUpdate()
Me![Combo4] = Me![Text8]
If IsValidSSN(Me![Text8]) Then
' Find the record that matches the control.
Dim rs As Object
Set rs = Me.Recordset.Clone
rs.FindFirst "[SSAN] = '" & Me![Combo4] & "'"
If Not rs.EOF Then Me.Bookmark = rs.Bookmark
If Me![Access] = True Then
DoCmd.RunMacro "Access"
Else
DoCmd.OpenForm "frmDenied"
End If
Else
DoCmd.OpenForm "frmDenied"
End If
End Sub
EDIT
Also why are you using a combobox to enter a SSN? You can use input mask on text box. Also I would highly suggest that you convert your system to some other identification other than SSN because it is easily passable to get past this code to look at the table containing everyones SSN, by holding down shift when opening the application. As for streamlining your code just remove that combobox altogether. If they are typing it into a textbox there is no need to put it into a hidden combobox.
You have a text field, SSAN, and with that input mask the dashes are not included in the stored values. So valid values would be 9 digit strings.
If that is correct, you can use a query to identify any invalid stored values.
SELECT y.SSAN, Len(SSAN) AS LenghtOfSSAN
FROM YourTable AS y
WHERE Len(SSAN)<>9 OR y.SSAN ALike '%[!0-9]%';
That query will return rows where SSAN includes < or > 9 characters, and any values which include characters other than digits.
Note the ALike keyword tells the db engine to expect ANSI wild card characters.  If you prefer Access' * wild card instead, change it to Like '*[!0-9]*'
Once you fix the stored values, add a Validation rule for that SSAN field (Like "#########") to require all values consist of 9 digits.
Since it looks like this became more of a "How do I find the user" than "How do I fix the existing entries", let me throw my hat into the ring.
Unless I completely misunderstand this, the existing (and accepted answer) function is HORRIBLE. You can do this all much more efficiently and with less code. First of all, delete Combo4. No need for it. Then do this:
Private Sub Text8_AfterUpdate()
Dim X as Integer
X = DLookup("Access", "qryAccess", "SSAN = '" & Me!Text8 & "'")
If Nz(X) = True Then
DoCmd.RunMacro "Access"
Else
DoCmd.OpenForm "frmDenied"
End If
End Sub
That's all you need. If the user's SSN was stored incorrectly, he's gonna be denied. 7 digits, 8 digits, doesn't make a difference. Only exact matches get through. That is, assuming 0 = False and 1 = True, which should be the default anyway.

Table completely ignoring variable

Private Sub Form_Current()
Dim bytoffcut As Byte
Dim strCriteria
strCriteria = "[WOID] = " & Forms![frmAddStockBooking]![MouldWO]
bytoffcut = Nz(DMax("OffcutNo", "dbo_tblOffcuts", strCriteria), 0) + 1
MsgBox bytoffcut
Me.txtOffcut.Value = bytoffcut
Me.WOID.Value = Forms![frmAddStockBooking]![MouldWO]
Me.txtdate.Value = Now()
End Sub
Can anyone tell me why this is not working? The variable is behaving as expected where bytoffcut increments by one when i create a new record. But when I check the table the field bound to txtOffcut the field reads 1 instead of the incremented value.
EDIT: This code is being used in the On current property of the form. When I create a new record using a button on the form Dmax is used to find the highest offcut No value in a table and add one to it.
This appear to work in the form as the offcut no txtbox increments. But when i look at the table instead of having records with an increasing offcut no Instead all records read 1
Try sending your where clause in the DMax like this, assuming the WOID field in the table is an number type and not text or date.
"[WOID] = " & Forms![frmAddStockBooking]![MouldWO]
It would be better to evaluate your DMax() expression only once, especially if dbo_tblOffcuts is a large linked table without a usable index on [WOID].
If your DMax() expression can return a Null, use Nz() to transform the Null to zero. Then add one.
Dim bytoffcut As Byte
Dim strCriteria
strCriteria = "[WOID] = " & Forms![frmAddStockBooking]![MouldWO]
'Debug.Print strCriteria '
bytoffcut = Nz(DMax("OffcutNo", "dbo_tblOffcuts", _
strCriteria), 0) + 1
MsgBox bytoffcut
Me.txtOffcut.value = bytoffcut
This may not give you what you want when other users are editing dbo_tblOffcuts.
I have managed to solve the issue of multiple records being updated by creating a primary key for the table I am writing to.
I think that because Access could not uniquely identify the record it would edit all the records that met the criteria or something of the ilk. I am not entirely sure myself.

Adding logic to a sub form in Access?

I have a sub form in Access:
The CopyNo is a combobox that lets me select from the MovieCopies table. When I select one, I want the Title field to show the correct movie title associated to that copy's movie ID. I also want the format to show. When I select from DaysRented combobox, if I select 1 and the movie is New, I want it to display the price, if it is regular for 3 days display the correct price etc.
I'm just not sure how to give logic to the comboboxes.
If anyone could point me in the right direction of how to do this sort of thing in Access 2007 I'd really appreciate it.
Thanks
Something like this:
Private Sub cboCopyNo_AfterUpdate()
If Nz(Me.cboCopyNo, "") <> "" Then
Me.txtTitle = DLookup("Title", "MovieMaster", "MovieID = " & Me.cboCopyNo)
End If
End Sub
Private Sub cboDaysRented_AfterUpdate()
If Nz(Me.cboDaysRented, 0) > 0 Then
Dim strType as String
strType = DLookup("[Type]", "MovieMaster", "MovieID = " & Me.cboCopyNo)
If Me.cboDaysRented = 1 Then
Me.txtPrice = DLookup("Price1Day", "Price", "[Type] = '" & strType & "'")
Else
Me.txtPrice = DLookup("Price3Day", "Price", "[Type] = '" & strType & "'")
End If
End If
End Sub
Couple notes. Some of your field names are reserved words in certain databases, such as "Type". I highly recommend you try to use field names that are not reserved words in Access or SQL server.
DLookups are not necessarily the fastest way to lookup data but will likely be fast enough for what you're trying to do here. Sometimes I create my own DAO recordset and lookup the values I want rather than using DLookup. It's basically like writing your own DLookup function.
DLookup uses SQL language so your syntax in the third argument, the WHERE clause, needs to match SQL. If the field in your WHERE clause is text/string you'll need to use a single quote on either side of the value (as shown above around the strType variable). If it is a number field you will not need the quotes. If it's a date you'll need hash signs (#).