What I have is a database that records customers and suppliers with their
addresses in 3 tables, tblCustomers, tblSuppliers and tblAddresses using Access.
Such as:
[tblCustomers] <== [tblAddresses] ==> [tblSuppliers]
For example, when I enter a customer's detail, after checked all the required fields
I'll insert a new record into [tblAddresses] then insert customer detail into [tblCustomers]
where [tblCustomers].AddressFK = [tblAddresses.AddressPK].
The problem is I get tblAddresses.AddressPK by selecting the latest record and this does not
handle multi-user. When 2 or more persons are creating new customers or suppliers at the same time
it links to the wrong address. e.g.
creating a new customer
creating a supplier
when (1) and (2) save the record at the same time, they both creating new address record in
tblAddresses and I don't know which belongs to which.
Initially, I thought since both customer and supplier share the same address fields
I could put their addresses in the same table but now it sounds like a bad idea.
Does anyone have any idea how to solve this?
I'm open to anything, at worse I'll redesign the database.
PS: I forgot to mentioned that I'm using Access built in VBA not .NET.
Update: Thanks for everyone's inputs, I've figured out a different way to manage this.
## identity sounds like a good idea but I have not used it (I've changed data entry
procedure instead) so I'm not sure if I should mark it as an answer or not. But thanks,
I learned something new. ^^
#LocTrang: In reality A customer might have more than one address (billing, delivery, alternative delivery). The relationship will look like
[Customer]-1:1----0:m[Address] > Customer-Address cannot exists without a corresponding customer details.
Same goes for suppliers. A supplier might have more than one address (billing, warehouse)
the relationship between supplier and address table will look the same as customer vs address.
Usually customer and supplier will have their own address table but if you would like to keep both addresses in one table you will design your address table like this
Address_ID > Primary1
Owner_ID > Primary2 (*)
Address_type >( can be primary key from address_type table)
Address_line1
Address_line2
City
... rest of he columns
this design will prevent data inconsistency in your database. (*) However: to achieve this, the owner_id must be unique throughout your database or at least between customer and supplier table. Since the Autonumber field in both customer and supplier table will start from 1 you cannot use it as owner_id as its not unique between those two tables. It is best to use "GUID/uuid" as unique_id in your tables or construct a custom unique field like supplier_ID starts with "S_" and customer_id starts with C_
Optionally you might create another table called Address_type as such:
Address_type_id > Primary key, Autonumber
Address_type
Description
and stick it to the address table.
now to answer your question:
if you are using Access forms to input your customer details you can create a subform for address and link the master and child form via LinkMasterField and LinkChildField. This way you create customer record first and then when you move to the address subform newly created customer_Id is pre-populated for you.
like her: (C_ID being unique customer/owner_id)
use the after insert event to auto update your customer unique id
Private Sub Form_AfterInsert()
Me.txt_c_id.value = "C_" & Me.txt_Customer_id.value
End Sub
if you are using VBA to enter customer details, try to encapsulate your data execution by transactions. this way you are always safe regardless how many other users are creating records:
vba code:
Private Sub btn_add_new_Click()
Dim MyDB As DAO.Database
Dim MyRs As DAO.Recordset
Set MyDB = CurrentDb
Dim Last_ID As Long
Dim SQL_GET As String
Dim SQL_SET As String
DBEngine.BeginTrans
On Error GoTo ERROR_TRANS:
SQL_SET = "INSERT INTO TBL_Customer(C_name,C_contact) VALUES('Second Customer','Second Contact')"
MyDB.Execute SQL_SET, dbFailOnError
SQL_GET = "SELECT MAX(Customer_id) AS LAST_ID FROM TBl_Customer"
Set MyRs = MyDB.OpenRecordset(SQL_GET)
Last_ID = Nz(MyRs("LAST_ID"), 0)
'Since access does not provide triggers we update the customer unique id manually
If Not Last_ID = 0 Then
SQL_SET = "UPDATE TBL_Customer SET C_ID = 'C_" & Last_ID & "' WHERE TBL_Customer.Customer_id = " & Last_ID
MyDB.Execute SQL_SET, dbFailOnError
End If
'Now add the address record via vba
SQL_SET = "INSERT INTO TBL_Address(Owner_ID, type, Address_line1, Address_line2, city) VALUES('C_" & Last_ID & "','Billing','01 Main Street','Flat 2','London');"
MyDB.Execute SQL_SET, dbFailOnError
DBEngine.CommitTrans
MsgBox "Customer inserted. New customer ID = C_" & Last_ID, vbInformation, "Success"
EXIT_ROUTINE:
On Error Resume Next
Set MyDB = Nothing
Set MyRs = Nothing
Exit Sub
ERROR_TRANS:
On Error Resume Next
DBEngine.Rollback
MsgBox "Sorry there was a problem while creating new customer record", vbExclamation, "Unable to insert"
Err.Clear
GoTo EXIT_ROUTINE
End Sub
I hope, you have understood and will make better use of this answer.
Related
How to get a Auto Number column value from a table in MS Access. I need the value to store it in another table.
For ex,
Table1: Table2:
Id value CId City
1 aaa 1 abc
2 bbb
Id in Table1 is auto numbered, where as CId in Table2 is not and note that not all values in table1 are present in table2. I'm creating a form to store value, city in respective tables. How do I get the auto number value so that I can store it as CId in table2.
So, if new value is entered it would take the Id 3, I require the Id 3 to store in Table2
Also, I can't go with Form Wizard option as, I dont want all the records in the table to be visible. I just require a blank form to save new data.
Please help me out in this. Thank you
I have used Unbound textbox Text1 to run below codes.
Private Sub cmdSQLRun_Click()
Dim MyAutoNumber As Long
CurrentDb.Execute "INSERT INTO Table1(MyValue) VALUES('" & Me.Text1 & "')"
'MyValue is value field of your example.
MyAutoNumber = DMax("ID", "Table1")
CurrentDb.Execute "INSERT INTO Table2(CID) VALUES(" & MyAutoNumber & ")"
'CID is table2 field name.
End Sub
I just want to know whether I could insert multiple values to a record by using an SQL statement from Visual Basic 6.0.
I have a database containing fields rollno, name, subopt1, subopt2. So instead of these 2 fields subopt1,subopt2 I want to insert it as a multiple values in a single field Subjects.
Is it possible to do it using an SQL statement from the Visual Basic 6 while inserting records. If so, how can I do it?
If you need only 2 fields as mentioned in the question then Étienne's answer would be enough. But if you need more flexibility and you may have unknow count of values then you can create another table e.g. RollSubject (rollsubjectId, rollno, subject) and insert your values there. You can comment if you need further information.
You would first need to create the new Subjects field in your table. You can do this with ALTER TABLE:
ALTER TABLE tablename
ADD Subjects VARCHAR(50);
Then, you can concatenate your two values into a single string using & in VB6:
Dim sSubjects As String
sSubjects = subopt1 & "," & subopt2
Finally, use the sSubjects variable in your SQL Statement.
When you read the data back from the database and want to separate subopt1 and subopt2, you can use the Split function:
Dim iCounter As Integer
Dim sSubjects As String
Dim sSubOpt(1 To 9) As String
sSubjects = "opt1,opt2,opt3,opt4,opt5,opt6,opt7,opt8,opt9"
Dim options As Variant
options = Split(sSubjects, ",")
For iCounter = 1 To 9
sSubOpt(iCounter) = options(iCounter - 1)
Next
I have an Invoice Form in MS Access 2007. This form have a subform named InvoiceLines having fields SerialNumber, ItemNumber, ItemName, ItemQty etc. I want to add Serial Numbers in this form. I have added =CurrentRecord property to showing current row number. But this property showing number “1” in all rows. Is there any easiest way to add serial numbers in MS Access form?
Ok, now knowing what you want here is my solution:
In the subform add this procedure:
Private Function SerialNumber(ByVal sourceForm As Form) As Variant
On Error GoTo Catch
SerialNumber = Null
With sourceForm.RecordsetClone
.Bookmark = sourceForm.Bookmark
SerialNumber = .AbsolutePosition + 1
End With
Done:
Exit Function
Catch:
If Err.Number <> 3021 Then MsgBox Err.Number & ":" & Err.Description, vbExclamation, "SerialNumber"
Resume Done
End Function
Then add a textbox control in this subform and set its Control Source to =SerialNumber([Form]).
That would fulfill all your needs.
Remark: If you delete a record in the subform you would have to refresh the subform to update the serial numbering.
You could solve this by changing the datasource of the form and add there a new column.
Assuming that your table is named Table1 and your primary key is ID, you could use a sub query to calculate it dynamically:
SELECT Table1.*, (Select Count(*) FROM Table1 as X WHERE X.ID < Table1.ID)+1 AS Serial FROM Table1
From your screenshot & description, it appears that you are describing an AutoNumber field.
Such a field cannot be edited by the user and will be automatically populated by MS Access with an integer unique within the dataset. This may be used as a primary key for the dataset and will be unaffected by sorting or deletion of records.
If your current SerialNumber field already contains data, you will not be able to change the datatype of this field to AutoNumber. As such, you will need to delete your existing SerialNumber field before adding a new field with datatype of AutoNumber.
I'm sure it's something simple, but, I keep getting an error when trying to run this. I've create a DB and want to import some data. I've parsed the import file, and assigned the relevant data to variables.
Next step is to insert into the DB, which I've managed, but, I'd like it to ignore the row if it finds a duplicate.
The bit of code is:-
strSQL = "INSERT INTO PubHol " & "(HolidayDate, HolidayName)" & " VALUES (""" & dtOutDT & """, """ & strOutDesc & """ )" & " WHERE NOT EXISTS (SELECT * FROM PubHol as ph WHERE ph.HolidayDate = CDate ('" & dtOutDT & "'))"
MsgBox strSQL
dbCurr.Execute strSQL, dbFailOnError
The MsgBox shows the following output:-
---------------------------
Microsoft Access
---------------------------
INSERT INTO PubHol (HolidayDate, HolidayName) VALUES ("01/01/2018", "New Year's Day" ) WHERE NOT EXISTS (SELECT * FROM PubHol as ph WHERE ph.HolidayDate = CDate ('01/01/2018'))
All of which seems to be a valid SQL statement to me, but I get a VB Error
Run-time error '3067' Query input must contain at least one table or
query.
Can anyone shed any light on where I'm going wrong?
Or any other tips on avoiding duplicates getting added to my DB?
Thanks
You can't use the INSERT INTO .. VALUES syntax together with a WHERE clause. (I would guess this is because WHERE is designed to limit the records being inserted, which only makes sense if you're trying to insert multiple records; VALUES in Access SQL inserts a single record.)
Ideally, you should apply a constraint and handle the resulting error, as in Parfait's answer.
If you can't do this, (because the constraint only applies to the import and not the regular business data, or for some other reason), then either make the decision whether to INSERT the records in VBA, not in SQL:
Dim recordCount As Integer
'get the number of records matching the criteria
If recordCount = 0 Then
'INSERT records here
End If
Or, if you still want to do this completely in SQL, I would suggest the following:
INSERT INTO PubHol (HolidayDate, HolidayName)
SELECT TOP 1 "01/01/2018", "New Year's Day"
FROM dummy
WHERE NOT EXISTS (
SELECT *
FROM PubHol as ph
WHERE ph.HolidayDate = CDate ('01/01/2018')
)
dummy is any table in your database which has at least one record.
NB. You might want to consider using SQL parameters; you could avoid having to manually construct string and date literals, and also to avoid the date conversion within the query.
Also, it seems as though you are importing a single record at a time. If you can create a linked table to your import source, it might be better instead to create an INSERT query which would massage the data from the linked table into the right shape for the destination table.
In Access SQL dialect, you cannot run WHERE without a data source in query. And looking specifically, you may do well with a constraint of unique values on HolidayDate and HolidayName that disallows duplicates:
dbCurr.Execute "ALTER TABLE [PubHol] ADD CONSTRAINT uniq_holiday" _
& " UNIQUE (HolidayDate, HolidayName)"
Then run a regular parameterized append query where any attempt to add duplicate values will return as an exception and be rolled back.
SQL (save as a query object, resuable for other holidays)
PARAMETERS [HolidayDateParam] Datetime, [HolidayNameParam] TEXT(255);
INSERT INTO PubHol (HolidayDate, HolidayName)
VALUES ([HolidayDateParam], [HolidayNameParam])
VBA
Dim qdef As QueryDef
...
Set qdef = dbCurr.QueryDefs("mysavedquery")
qdef![HolidayDateParam] = dtOutDT
qdef![HolidayNameParam] = strOutDesc
qdef.Execute dbFailOnError
Set qdef = Nothing
I'm having some trouble writing code for a button to add a selected record from one listbox(ListBoxForm2), to another listbox(ListBoxForm1). I've got a working button to remove a selected record from ListBoxForm1, which I'll include below. If anyone is able to help it would be greatly appreciated :-)
Form1 has a ListBox which reads records from Table1 (to which Table2 is inner joined) and displays them if the ID in Table1 exists in Table2. They can be selected and deleted using a button:
Private Sub RemoveMember_Callouts_Click()
Dim lngID As Long
Dim strSQL As String
If IsNull(ListBoxForm1) Then
Exit Sub
End If
'get selected record's ID
lngID = ListBoxForm1.Value
strSQL = "DELETE * FROM [Table2] WHERE Table2ID = " & lngID
CurrentDb.Execute strSQL
'refresh the list
ListBoxForm1.Requery
End Sub
However I'm struggling when it comes to programmatically adding a selected record in Table2(Form2) to Table1. Could I do similar code to my remove click event? Does anyone have any ideas?
Thanks
Before executing Delete statement. Write a statement to insert record into Table 1 from Table 2.
"INSERT INTO Table1(col1, col2) SELECT col1, col2 FROM Table2 WHERE Table2ID=" & lngID
I hope this will help