Access VBA - Dlookup for checking duplicates (composite unique key) - ms-access

I have a join table, that consist FK from 2 tables. These two fields are set for composite unique key index, and I need to check for duplicates with DlookUp, If possible. Here is what I tried:
Nz(DLookup("PK_JoinTable", "JoinTable", "FK_FirstTable = " & Me.FK_FirstTable & "AND FK_SecondTable= " & Me.FK_SecondTable & _
" AND PK_JoinTable <> " & Nz(Me.PK_JoinTable, 0)), 0)
Is possible to use DLookUp for this case ?
EDIT: Here is my Table design...
JoinTable
PK_JoinTable
FK_FirstTable
FK_SecondTable
These foreign key table fields have unique index. I have a form where I enter data that joins both tables via Combobox. And I need to prevent duplicates entries. Both in Before_Update event, and Click event of "Save" button. This is my whole code for now (click_event of button):
Dim Duplicates As Long
Duplicates = Nz(DLookup("PK_JoinTable", "JoinTable", "FK_FirstTable = " & Me.FK_FirstTable & "AND FK_SecondTable= " & Me.FK_SecondTable & _
" AND PK_JoinTable <> " & Nz(Me.PK_JoinTable, 0)), 0)
If Duplicates > 0 Then
Cancel = True
MsgBox "Duplicate entry. This record will not be saved !, vbCritical
DoCmd.RunCommand acCmdUndo
Exit Sub
Else
' nothing
End If

After your explanations in comments, yes it is possible to do this with a Dlookup, but you had a small mistake in yours, you missed a space before AND FK_SecondTable :
Nz(DLookup("PK_JoinTable", "JoinTable", "FK_FirstTable = " & Me.FK_FirstTable & " AND FK_SecondTable= " & Me.FK_SecondTable & _
" AND PK_JoinTable <> " & Nz(Me.PK_JoinTable, 0)), 0)
You can also try this approach which achieve the same thing as your Dlookup, but is clearer IMO, offers more possibilities if your query goes more complex, and is easier to debug and tweak.
Dim RST As Recordset
Dim strSQL as string
strSQL = "SELECT DISTINCT(Attribute) FROM Table_Setting WHERE Attribute NOT IN (SELECT Attribute FROM Table_Setting WHERE BookType='" & strBookType & "')"
Set RST = CurrentDb.OpenRecordset(strSQL)
If Not RST.BOF Then
' Looks like we have duplicates
RST.Close
Set RS = Nothing
Cancel = True
MsgBox "Duplicate entry. This record will not be saved !", vbCritical
DoCmd.RunCommand acCmdUndo
Exit Sub
End If

To debug your DLookup call, construct the WHERE part in a separate string and Debug.Print it (Ctrl+G opens the Immediate window).
strSql = "FK_FirstTable = " & Me.FK_FirstTable & _
"AND FK_SecondTable= " & Me.FK_SecondTable & _
" AND PK_JoinTable <> " & Nz(Me.PK_JoinTable, 0)
Debug.Print strSql
Duplicates = Nz(DLookup("PK_JoinTable", "JoinTable", strSql), 0)
You'll notice that you are missing a space between Me.FK_FirstTable and AND.

You should just point these tables together in a query and perform a dlookup on the query if needed. If you specifically need the composite key you can concatenate the needed columns under an alias column.
All in all there's probably a much easier way to accomplish your overall goal but your short on detail... so I just answered with what
was provided.
With your updated question... the simplistic way to avoid duplicates is to create a unique index based off the three columns.

Debugging will be quite complicated when database is big with Dlookup, I suggest using where function is very important as well aa try to use foreign key as much as to remove complexities in ur database. If it is big then only.

Related

Look for duplicate records before adding a record

I clipped this from another post, since it is similar to what I need. Instead of using me.ID, etc, I need to reference a field in another table, specifically [Formdate] in a table named Brief Sheet.
I tried many variations, such as table "Brief Sheet".Formdate but no luck
What would be the proper syntax?
rst.FindFirst "[ID] <> " & Me.ID & _
" AND [TitleText] = '" & Me.TitleText & _
"' AND [UnitCode] = " & Me.UnitCode & _
" AND [AcademicYear] = " & Me.AcademicYear & _
" AND [Titleofchapterjournalarticle] = '" & Me.Titleofchapterjournalarticle & "'"
Use Dlookup. In the snippet you posted, the coder has already instantiated a recordset and is using recordset methods to find a record within it. What you said you want to do is actually a little simpler.
Below is a test procedure to illustrate getting your date value from your table.
You didn't clarify what criteria you would use to select the proper FormDate from the table "Brief Sheet", so I included "ID=3", which will select the FormDate record, whose ID=3. Adjust that as necessary.
Also, if your table name really is "Brief Sheet", and you have the ability to rename it, I highly recommend establishing some naming convention rules for your tables, first not having any spaces. Even Brief_Sheet would make your life easier down the road.
Public Sub Test1()
'dimension a variable to hold the date value
Dim datFormDate As Date
'fill the variable with the value you need to reference
datFormDate = DLookup("FormDate", "Brief Sheet", "ID = 3")
'Print the value to the "immediate" pane (just for testing)
Debug.Print datFormDate
'If you're running this code from within your form module, you can assign
'the value in your variable to a field in your table as such:
me.DateFieldtxtbx = datFormDate
End Sub

SQL SUM statement in VBA function

My database has three tables, Holder, Product (which is an account) and Transaction. I have set up a form from the Holder table that has a subform from the Product table and the Product subform has the Transaction table as a subform. On the Holder portion of the form, I have put an Outstanding unbound text field that should display the total amount and tax fields of transactions from the transaction table that have not been paid (indicated by a checkbox on the Transaction table). I have set the control source of the unbound text box to =calcOutstanding() and written the following function for the form.
Public Function calcOutstanding()
Dim db As Database
Dim strSQL As String
Set db = CurrentDb
strSQL = "SELECT SUM(tblTransaction.TxAmount + tblTransaction.TxTax) As Outstanding" _
& "FROM tblTransaction" _
& "INNER JOIN tblProduct ON tblTransaction.fkProductID = tblProduct.ProductID" _
& "INNER JOIN tblHolder ON tblProduct.fkHolderID = tblHolder.HolderID" _
& "WHERE tblTransaction.TxPaid = False" _
& "AND tlbHolder.HolderID = Me.HolderID;"
DoCmd.RunSQL strSQL
calcOutstanding = Outstanding
End Function
The field now just shows #Error. What am I doing wrong?
There's a bunch wrong with your approach:
DoCmd.RunSQL is just for action queries (INSERT, UPDATE, DELETE).
You cannot return a value from DoCmd.RunSQL like you are attempting to and push it into a variable.
Your concatenation for the where clause is incorrect.
As HansUp mentioned, Access is very picky about parentheses in JOINs in its SQL.
Assuming the SQL is correct, this code lives on the parent form, and you dno't get multiple rows back in your query, maybe something like this would work:
Public Function calcOutstanding() As Currency
Dim db As DAO.Database
Dim rst As DAO.Recordset
Dim strSQL As String
Set db = CurrentDb
strSQL = "SELECT SUM(tblTransaction.TxAmount + tblTransaction.TxTax) As Outstanding " _
& "FROM (tblTransaction " _
& "INNER JOIN tblProduct ON tblTransaction.fkProductID = tblProduct.ProductID) " _
& "INNER JOIN tblHolder ON tblProduct.fkHolderID = tblHolder.HolderID " _
& "WHERE tblTransaction.TxPaid = False " _
& "AND tlbHolder.HolderID = " & Me.HolderID
Set rst = db.OpenRecordset(strSQL, dbForwardOnly)
calcOutstanding = rst![Outstanding]
Set rst = Nothing
set db = Nothing
End Function
Notice the concatenation in the WHERE clause to get the value from the form's data source (otherwise the SQL couldn't reconcile Me.HolderID within the scope of the SQL itself). Also, we push the returning dataset into a recordset and read from that. Something along these lines should work, I think. (Not in front of Access now, so sorry if any non-compiling statements.)
EDIT: Added the function return type as integer for specificity's sake.
EDIT 2: Added the function return type as currencyfor specificity's sake. Doh.
Right off the bat, I see a problem in the code you posted:
strSQL = "SELECT SUM(tblTransaction.TxAmount + tblTransaction.TxTax) As Outstanding" _
& "FROM tblTransaction" _
& "INNER JOIN tblProduct ON tblTransaction.fkProductID = tblProduct.ProductID" _
& "INNER JOIN tblHolder ON tblProduct.fkHolderID = tblHolder.HolderID" _
& "WHERE tblTransaction.TxPaid = False" _
& "AND tlbHolder.HolderID = Me.HolderID;"
There should be a space either at the end of each line Outstanding " _ or at the beginning of each line like " FROM tblTransaction otherwise your string will read OutstandingFROM tblTransaction when parsed, which will give you an error.
I doubt you need an external function to do this. MS Access allows you to reference fields from subform simply by Me.Subform!FieldName.Value
This means, you could simply access the subform fields that are related to your current record. Even perform IIF(condition, truevalue, falsevalue) on that field
read more about accessing forms and subforms here: http://access.mvps.org/access/forms/frm0031.htm
EDIT:
in your third subform (tbl_transaction), create a new unbound TextBox called (txt_outstanding) and assign this expression
=IIF([txPaid]=false, sum(txAMount +TxTax),0)
now you can access this field in your parent form something similar to this:
me.txt_someTextbox.value = nz(me.tbltransactionsubform!txt_outstanding.value,"")

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.

Order of records that DLookup uses to return first value (Access VBA)

long time stalker but first time poster here so apologies for any social faux pas I make.
I am trying to use DLookup to search for a record in a table using VBA. The specific record I am after is the one closest in date to sdate (a user specified date) which also meets some other criteria. There are likely to be a couple of records with dates prior to sdate which meet the same criteria, and I am only interested in the one which is chronologically closest to sdate.
The following is a simplified example of the code I am using to try and achieve this. I use baseTestString as there are quite a few similar DLookup expressions so it saves typing and clarifies the code slightly (to me at least).
DoCmd.OpenTable ("Results")
DoCmd.SetOrderBy "[Survey_Date] Desc"
DoCmd.Close acTable, ("Results")
'set a new criteria for baseline testing using Dlookup
basetestString = "[Equipment_ID] = '" & equipID & "' AND [Baseline?] = True _
AND format([Survey_Date],""ddmmyyyy"") < format(" _
& sdate & ",""ddmmyyyy"")"
'set variables
[Forms]![results]![text_A] = Nz(DLookup("[Input]", "[results]", _
basetestString))
I believed (perhaps naively) that DLookup returns the first record it finds that matches the criteria specified. So, my code was designed to sort the table into chronological order, thinking that this would be the order that DLookup would cycle through the records.
However, each time I run the code, I am returned the lowest possibly date that matches the criteria rather than the one closest to sdate. After some playing around, I believe that DLookup uses the primary key as its basis for cycling through the records (the earlier dates are entered earlier, and hence given a primary key using autonumber which is lower than later dates).
This leads to me my questions...
1) I am correct in believing this is what is happening when I am returned the wrong record?
2) Is there a way to use DLookup in the way I am attempting? Can I choose which field is used to order the records for DLookup? Assigning the date field as the primary key is not possible as the dates might not always be unique.
3) Is there any other way I can achieve what I am trying to do here?
Thank you very much
It is always unsafe to rely on an order of records in a relational database. You could use DMax to get the date you need, but I think that in this case a recordset would be quicker.
Dim rs As DAO.Recordset
Dim db As Database
Dim ssql As String
Set db = CurrentDB
ssql=" SELECT input" _
& " FROM results" _
& " WHERE results.[baseline?] = true" _
& " AND results.equipment_id = '" & equipID & "'" _
& " AND results.survey_date = (SELECT" _
& " Max(results.survey_date) " _
& " FROM results" _
& " WHERE results.[baseline?] = true" _
& " AND results.equipment_id = '" & equipID & "'" _
& " AND results.survey_date <#" & Format(sdate,"yyyy/mm/dd") & "#)"
Set rs = db.OpenRecordset(ssql)
strInput = rs("Input")
Debug.Print strInput
You can also check the number of records returned, in case of an error.

How can I check for null values in Access?

I am new to Access. I have a table full of records. I want to write a function to check if any id is null or empty. If so, I want to update it with xxxxx.
The check for id must be run through all tables in a database.
Can anyone provide some sample code?
I'm not sure if you are going to be able to find all tables in the database with Access SQL. Instead, you might want to write up some VBA to loop through the tables and generate some SQL for each table. Something along the lines of:
update TABLE set FIELD = 'xxxxxx' where ID is null
Check out the Nz() function. It leaves fields unaltered unless they're null, when it replaces them by whatever you specify.
For reasonable numbers and sizes of tables, it can be quicker to just
open them
sort by each field in turn
inspect for null values and replace manually
It's good practice to find out where the nulls are coming from and stop them - give fields default values, use Nz() on inputs. And have your code handle any nulls that slip through the net.
I'm calling it the UpdateFieldWhereNull Function, and shown is a Subroutine which calls it (adapted from http://www.aislebyaisle.com/access/vba_backend_code.htm)
It updates all tables in the DbPath parameter (not tested, handle with care):
Function UpdateFieldWhereNull(DbPath As String, fieldName as String, newFieldValue as String) As Boolean
'This links to all the tables that reside in DbPath,
' whether or not they already reside in this database.
'This works when linking to an Access .mdb file, not to ODBC.
'This keeps the same table name on the front end as on the back end.
Dim rs As Recordset
On Error Resume Next
'get tables in back end database
Set rs = CurrentDb.OpenRecordset("SELECT Name " & _
"FROM MSysObjects IN '" & DbPath & "' " & _
"WHERE Type=1 AND Flags=0")
If Err <> 0 Then Exit Function
'update field in tables
While Not rs.EOF
If DbPath <> Nz(DLookup("Database", "MSysObjects", "Name='" & rs!Name & "' And Type=6")) Then
'UPDATE the field with new value if null
DoCmd.RunSQL "UPDATE " & acTable & " SET [" & fieldName & "] = '" & newFieldValue & "' WHERE [" & fieldName & "] IS NULL"
End If
rs.MoveNext
Wend
rs.Close
UpdateFieldWhereNull = True
End Function
Sub CallUpdateFieldWhereNull()
Dim Result As Boolean
'Sample call:
Result = UpdateFieldWhereNull("C:\Program Files\Microsoft Office\Office\Samples\Northwind.mdb", "ID", "xxxxxx")
Debug.Print Result
End Sub