Ms Access lock table while inserting data - ms-access

In a Access 2003 database, I have an "Inscriptions" (subscription) database with a primary key on 2 fields idPersonnel (employee) and idSession.
I have made a form so that user can select a session (in a listbox), then one or more employee (another listbox) and suscribe them to that session by using a button, which, on VBA side, first check that there is enough room on the session (defined by "MaxParticipants" field on "Sessions" table, linked to "Inscriptions" table on idSession), then insert data in "Inscriptions" table
This is working fine in a single-user environnement, but fails if 2 people want to join some employees on the same session at the same time, as I have a confirmation message between check and insertion. Therefore 2 users can select employees, get the confirmation message (at this point both are told there is enough room), resulting in having more people than expected joined to the session.
Fortuneatly, if both users try to insert the same employee(s) to that table, one will get a duplicate error, but insertion will be made if employees are different.
On another DB engine, such as SQL server, I would use a stored procedure that would lock the table, do the check and the insertion then unlock the table.
But it does not seem to be possible in MS Access.
What are the possibilities in MS Access to prevent a session from having more than maximum number of participants ? Any help is appreciated.

One way to accomplish your goal would be to do the INSERT in a transaction, count the participants for that session, and roll back the transaction if the new total exceeds the limit:
Option Compare Database
Option Explicit
Sub AddParticipant()
Dim cdb As DAO.Database, cws As DAO.Workspace, _
qdf As DAO.QueryDef, rst As DAO.Recordset
' test data
Const idPersonnelToAdd = 4
Const idSessionToAdd = 2
Set cdb = CurrentDb
Set cws = DBEngine.Workspaces(0)
cws.BeginTrans
Set qdf = cdb.CreateQueryDef("", _
"PARAMETERS prmIdPersonnel Long, prmIdSession Long; " & _
"INSERT INTO Inscriptions (idPersonnel, idSession) " & _
"VALUES (prmIdPersonnel, prmIdSession)")
qdf!prmIdPersonnel = idPersonnelToAdd
qdf!prmIdSession = idSessionToAdd
qdf.Execute dbFailOnError
Set qdf = Nothing
Set qdf = cdb.CreateQueryDef("", _
"PARAMETERS prmIdSession Long; " & _
"SELECT " & _
"Count(*) AS NumParticipants, " & _
"First(MaxParticipants) AS Limit " & _
"FROM Inscriptions INNER JOIN Sessions " & _
"ON Inscriptions.idSession = Sessions.idSession " & _
"WHERE Sessions.idSession = prmIdSession")
qdf!prmIdSession = idSessionToAdd
Set rst = qdf.OpenRecordset(dbOpenSnapshot)
If rst!NumParticipants <= rst!Limit Then
cws.CommitTrans
Debug.Print "INSERT committed"
Else
cws.Rollback
Debug.Print "INSERT rolled back"
End If
rst.Close
Set rst = Nothing
Set qdf = Nothing
Set cws = Nothing
Set cdb = Nothing
End Sub

Related

How to get bigdata from one table and insert into another in VBA?

I have table with columns like key,English Phrase and that phrase with other 40 languages.See in following image :
I want to break the records of these table by it's language column like following image:
I did this using the following code:
Sub InsertIntoMasterPhrases()
Dim objRecordsetMaster As ADODB.Recordset
Set objRecordsetMaster = New ADODB.Recordset
Dim objRecordset As ADODB.Recordset
Set objRecordset = New ADODB.Recordset
objRecordsetMaster.ActiveConnection = CurrentProject.Connection
objRecordset.ActiveConnection = CurrentProject.Connection
objRecordsetMaster.Open ("SELECT [Master Table].* FROM [Master Table];")
While objRecordsetMaster.EOF = False
objRecordset.Open ("Select [SAP_LANGUAGE to LANG].[LANGUAGE NAME], [SAP_LANGUAGE to LANG].[LANGUAGE] " & _
"From [SAP_LANGUAGE to LANG]")
While objRecordset.EOF = False
key = objRecordsetMaster.Fields("Key").Value
englishPhrase = objRecordsetMaster.Fields("English Phrase").Value
language = objRecordset.Fields("LANGUAGE").Value
translation = objRecordsetMaster.Fields(languageName).Value
If (GetRecordsExist(CStr(key), CStr(englishPhrase), CStr(language)) = "") Then
Query = "INSERT INTO [Language Sample](Key,English,Translation,Language)VALUES ('" & key & "','" & englishPhrase & "','" & translation & "','" & language & "');"
CurrentDb.Execute Query
End If
objRecordset.MoveNext
Wend
objRecordset.Close
objRecordsetMaster.MoveNext
Wend
objRecordsetMaster.Close
End Sub
//Checking records already exist in table
Function GetRecordsExist(key As String, english As String, language As String) As String
Dim db As Database
Dim Lrs As DAO.Recordset
Dim LGST As String
Set db = CurrentDb()
Set Lrs = db.OpenRecordset("SELECT KEY FROM [Language Sample] where KEY='" & key & "' and English='" & english & "' and Language = '" & language & "'")
If Lrs.EOF = False Then
LGST = "Found"
Else
LGST = ""
End If
Lrs.Close
Set Lrs = Nothing
GetRecordsExist = LGST
End Function
In the Master table i have 15000 records and when its breaking 15000 records it becomes 15000 * 40 = 600000. above code inserting almost 10000 records per minutes and after few hour it' hangs up . But also it don't produce any error then i have to restart the access. Kindly help how can i do it in better way.
Alternative 1:
Use a large UNION query to append many records with one SQL statement, as described here:
How to simulate UNPIVOT in Access 2010?
You will probably want to split it into several chunks (e.g. 5 or 10 languages at a time), or Access might choke on the query.
Alternative 2:
Instead of running INSERT statements for each record, use a DAO recordset with .AddNew. This is faster by magnitudes, see this answer:
https://stackoverflow.com/a/33025620/3820271

How to avoid a "You must enter a value" error message in Access VBA

I'm having issues avoiding a "You must enter a value in the __ field" error message in Access 2016. I have three tables, Tasks, Users, and TaskAssignments, and a split form that looks like:
User Task Assigned?
User1 Task1 True
User1 Task2 False
User1 Task3 True
User2 Task1 False
User2 Task2 False
User2 Task3 True
User3 Task1 True
User3 Task2 True
User3 Task3 True
Each task can have multiple users assigned to it, and each user is assigned to multiple tasks. I want my form to display every possible value, then use a checkbox, so that I can click and add a user to that task. The TaskAssignments table has a primary key and a unique constraint on both TaskID and UserID.
The recordsource for my form is a query:
select x.UserName, x.TaskName, ta.is_assigned
from (select * from Tasks, Users) x
left join TaskAssignments ta on (ta.TaskID = x.TaskID and ta.UserID = x.UserID)
I have an on click event that checks if a record exists in TaskAssignments and either updates or inserts into TaskAssignments. When I debug.print and manually run my queries, they both do what's expected. When I manually insert a record into my TaskAssignments table, my form behaves how I expect. When I need to insert a new record, however, I receive a message stating that I must enter a TaskID in TaskAssignments.
I've tried requerying the form, but I still receive the error message. Why can't it find the record that I just inserted?
Help please?!? Do I need to drastically rethink my approach here?
Here's the VBA:
Private Sub is_assigned_Click()
Dim CurrentUser, AssignmentQuery As String, SelectedUserID, SelectedTaskID As Integer
Dim ShouldInsert, IsAssigned As Boolean
CurrentUser = Environ$("Username")
SelectedUserID = Me.UserID
SelectedTaskID = Me.TaskID
IsAssigned = Me.is_assigned
Dim db As DAO.Database, rs As DAO.Recordset, strSQL As String
Set db = CurrentDb
strSQL = "select UserID, taskID from TaskAssignments where UserID=" & SelectedUserID & " and taskID =" & SelectedTaskID & ";"
Set rs = db.OpenRecordset(strSQL)
If rs.EOF = True Then
ShouldInsert = True
Else: ShouldInsert = False
End If
If ShouldInsert = True Then
AssignmentQuery = "insert into TaskAssignments (UserID, taskID, DateAssignmentUpdated, AssignmentUpdatedBy, is_assigned) values " _
& vbCrLf & "(" & SelectedUserID & "," & SelectedTaskID & ",#" & Now & "#,'" & CurrentUser & "'," & IsAssigned & ");"
ElseIf ShouldInsert = False Then
AssignmentQuery = "update TaskAssignments set UserID=" & SelectedUserID & ", DateAssignmentUpdated=#" & Now & "#, AssignmentUpdatedBy='" & CurrentUser & "',is_assigned=" & IsAssigned _
& vbCrLf & " where taskID = " & SelectedTaskID & " And UserID = " & SelectedUserID & ";"
End If
MsgBox AssignmentQuery
db.Execute (AssignmentQuery)
Forms("Task Assignments").Requery
Set rs = Nothing
Set db = Nothing
End Sub
Edit - here are the queries produced:
Insert
insert into TaskAssignments
(UserID, TaskID, DateAssignmentUpdated, AssignmentUpdatedBy, is_assigned)
values (301,4,Now(),'mylogin',True);
Update
update TaskAssignments
set UserID=270, DateAssignmentUpdated=Now(), AssignmentUpdatedBy='mylogin', is_assigned=False
where TaskID = 1 And UserID = 270;
And a constraint on my TaskAssignments table. Both TaskID and UserID are set as required in my table design (which was my whole goal - I was hoping to avoid adding records to TaskAssignments until the user has actually been assigned to a task).
alter table TaskAssignments add constraint TaskAssignmentsConstraint unique (TaskID, UserID);
Beware of wrong datatypes, each Dim needs its own datatype!
Dim CurrentUser As String, AssignmentQuery As String
Dim SelectedUserID As Long, SelectedTaskID As Long ' don't use 16-bit Integer for ID columns
Dim ShouldInsert As Boolean, IsAssigned As Boolean
To avoid troubles with date/time formatting: the database engine knows Now(), so you can directly use this in the Insert SQL:
AssignmentQuery = "insert into TaskAssignments (UserID, taskID, DateAssignmentUpdated, AssignmentUpdatedBy, is_assigned) values " _
& vbCrLf & "(" & SelectedUserID & "," & SelectedTaskID & ", Now(), '" & CurrentUser & "'," & IsAssigned & ");"
If it still doesn't work, use Debug.Print AssignmentQuery instead of MsgBox and add the actual SQL to your question (Ctrl+G shows the output).
Edit
Re-reading the question and comment, I think the problem is:
You are editing a bound form, and are updating/inserting in the same table the form is based on. That's where the Write conflict on Update comes from, the other error is probably because the bound form is trying to insert a record when you click is_assigned, but can't.
So yes, you need to rethink your approach, at least partially.
One solution is to insert the recordsource into a temp table, and base your form on that. Then the rest of the code will probably work.
It may be over-complicating things, though.
I got this problem trying to update a field that use to be a primary field in my table. When I altered what was considered the primary field I assume access would automatically stop enforcing is not null, but for some reason it didn't.
I fixed it by deleting the field, saving the table recreating the field and saving the table and the problem went away. Of course this wouldn't be an ideal solution if you have data in that table you don't want to lose so you might want to try backing it up first, before you give the solution a try, then reinserting the values.

RecordSet and Ms Access 2007

I have a query that has CustID with mutiple Business affiliated with the CustID. I can't use Dlookup because it only returns one variable. I want to show on a form that for this custID, here are all the businesses it's affiliated it. I want the Businesses to show up into a field (business) in another table on the form.
I started out by this
Public Sub OpenRecordset()
Dim db As Database
Dim rs As Recordset
Set db = CurrentDb
Set rs = db.OpenRecordset("Q:businesses")
Do While Not rs.EOF
T:Custinfo!business = NAME (I am lost in between on how to identify the custid and place the businesses into the table field as a Dlookup)
rs.movenext
Loop
rs.Close
Set rs = Nothing
db.Close
End Sub
I keep looking at other examples but can't seem to tie together where the dlookup replacement will take place and how will you have to put this on a form as a datasheet?
You don't need a DLookup. You could do one of two things:
1) Use a listbox and set the recordsource equal to your query (assuming Q:businesses has been appropriately defined to give the businesses as a result)
2) Still need your query to be appropriate, but you could create a string with all of the businesses in it:
Public Sub OpenRecordset()
Dim db As Database
Dim rs As Recordset
Dim StrBusinesses As String
Set db = CurrentDb
Set rs = db.OpenRecordset("qryBusinesses")
If rs.EOF and rs.BOF Then
MsgBox("No businesses exist for this Customer")
Exit Sub 'Or do whatever else you want if there are no matches
Else
rs.MoveFirst
End If
StrBusinesses = ""
Do While Not rs.EOF
StrBusinesses = StrBusinesses & rs!Business & ", "
rs.movenext
Loop
rs.Close
StrBusinesses = Left(StrBusinesses, Len(StrBusinesses) - 2)
Forms!MyForm.MyField = StrBusinesses 'Set the field equal to the string here
Set rs = Nothing
db.Close
End Sub
Of course this assumes that the query "Q:Business" is defined to get the appropriate info such as:
SELECT custID, business FROM tblBusinesses WHERE custID = X
where "X" is the custID you are looking for.
If you need to set the query dynamically, you will need to set a querydef.
EDIT to include querydef code***********************
Also changed the name of the query to "qryBusinesses" in the code above and below as I'm not sure whether you can make a query with a colon in it.
To set the querydef, put this at the beginning of the code:
Dim qdf As QueryDef
Set qdf = CurrentDb.QueryDefs("qryBusinesses")
qdf.SQL = "SELECT custID, business FROM tblBusinesses" _
& " WHERE custID = " & Forms!MyForm.CustID 'replace with actual form and field
This assumes that i) qryBusinesses exists already,
ii) custID is a number field
EDIT**************
If you define the query to look at the form itself, you would not need to set the sql, so if the query were defined (either in VBA or through the query wizard) as:
qdf.sql = "SELECT custID, business FROM tblBusinesses" _
& " WHERE custID = Forms!MyForm.CustID"
then you would not need to redefine the sql. However, it is a bit more dynamic to put the custID into the qdf itself as it is easier to debug any issues as you can see the exact sql that is being run in the original method.

DBEngine(0)(0).Execute DELETE to complete before APPEND

I have two statements in VBA. Here is a fragment of code:
'delete records from tbl_PLAN_data
strErr = "1a"
strSQL = "DELETE tbl_PLAN_data.*"
strSQL = strSQL & " FROM tbl_PLAN_data;"
DBEngine(0)(0).Execute strSQL, dbFailOnError + dbSeeChanges
DoEvents
strErr = "2a"
'append records to tbl_PLAN_data
DBEngine(0)(0).Execute "qryAppPLAN", dbFailOnError + dbSeeChanges
DoEvents
SQL string of qryAppPLAN:
INSERT INTO tbl_PLAN_data ( ID, Qty, QtyPln, QtyAct, ProdT, TmStamp, TID, Wks, vol, v4, v4k, DataPln, Shift, DataPlnShift, GRD, Wk, Wkd, GRDLdKf, KFig )
SELECT tbl_OrderDetailsSub.OrderDetailsSubID, [Quantity]-Sum(Nz([Qtyact],0)*IIf([np]=0,1,-1)) AS Qty, tbl_OrderDetails.Quantity, Sum(Nz([Qtyact],0)*IIf([np]=0,1,-1)) AS QA, Sum(tbl_OrderDetailsSub.PT) AS SumOfPT, Now() AS TmStamp, tbl_OrderDetailsSub.TID, tbl_Tooling.WksID, tbl_OrderDetailsSub.Volume, tbl_OrderDetailsSub.Vol4, tbl_OrderDetailsSub.Vol4K, tbl_OrderDetailsSub.DataPln, tbl_OrderDetailsSub.Shift, [datapln] & "-" & [shift] AS datshift, IIf(InStr([oprdetails],"G")>0 Or InStr([oprdetails],"Hs")>0 And InStr([oprdetails],"GL")=0,1,0) AS GRD, DatePart("ww",[DataPln],0,2) AS w, DatePart("w",[DataPln],0,2) AS wd, tbl_OrderDetailsSub.TblLoadKf, Nz([Koef],1) AS Expr1
FROM ((tbl_Order INNER JOIN (tbl_OrderDetails INNER JOIN tbl_OrderDetailsSub ON tbl_OrderDetails.[OrderDetailsID] = tbl_OrderDetailsSub.[OrderDetailsID]) ON tbl_Order.[OrderID] = tbl_OrderDetails.[OrderID]) LEFT JOIN tbl_ProdAct ON tbl_OrderDetailsSub.[OrderDetailsSubID] = tbl_ProdAct.ODSubID) INNER JOIN tbl_Tooling ON tbl_OrderDetailsSub.TID = tbl_Tooling.TID
WHERE (((tbl_Order.ProdTypeID)<>"S" Or (tbl_Order.ProdTypeID) Is Null) AND ((tbl_Order.CancelledDate) Is Null) AND ((tbl_Order.RefusingReason) Is Null) AND ((Right(CStr(Nz([tbl_Order]![ProcessedDate],"12:00:00")),8))="12:00:00") AND ((tbl_Order.OrderType)<>"Pasiûlymas") AND ((tbl_Order.ShippedDate) Is Null))
GROUP BY tbl_OrderDetailsSub.OrderDetailsSubID, tbl_OrderDetails.Quantity, tbl_OrderDetailsSub.TID, tbl_Tooling.WksID, tbl_OrderDetailsSub.Volume, tbl_OrderDetailsSub.Vol4, tbl_OrderDetailsSub.Vol4K, tbl_OrderDetailsSub.DataPln, tbl_OrderDetailsSub.Shift, [datapln] & "-" & [shift], IIf(InStr([oprdetails],"G")>0 Or InStr([oprdetails],"Hs")>0 And InStr([oprdetails],"GL")=0,1,0), DatePart("ww",[DataPln],0,2), DatePart("w",[DataPln],0,2), tbl_OrderDetailsSub.TblLoadKf, Nz([Koef],1), tbl_OrderDetails.Quantity
HAVING ((([Quantity]-Sum(Nz([Qtyact],0)*IIf([np]=0,1,-1)))>0) AND ((tbl_OrderDetailsSub.DataPln)>Date()-30))
ORDER BY tbl_OrderDetailsSub.DataPln;
Sometimes at second execute statement I get error "Record is deleted". It seems logic when I append records after deletion. But how to force VBA to wait till delete statement completes?
I would suggest you create a Database Object and execute on that object rather than using the DBEngine(0)(0), as when you use this, you are not only performing an expensive operation of creating a new instance of the Database, you are also trying to query the table which you just modified (deleted), so you might have a small glitch.
Dim dbObj As DAO.Database
Set dbObj = CurrentDB()
'delete records from tbl_PLAN_data
strErr = "1a"
strSQL = "DELETE tbl_PLAN_data.* FROM tbl_PLAN_data;"
dbObj.Execute strSQL, dbFailOnError + dbSeeChanges
'Optional
'DoEvents
strErr = "2a"
'append records to tbl_PLAN_data
dbObj.Execute "qryAppPLAN", dbFailOnError + dbSeeChanges
By creating a Database Object, you are making sure the operations are made against the "active" object on which you also made a modification, so would return you the right information. DoEvents might not even be required. I have added it, if you feel it is still causing problems introduce it again and see if it makes any difference.
Good Luck !

Check if a column exists in a pivot query

I have a pivot query I need to loop through and add to another temporary table. The pivot query is a sum of the different statuses found. The statuses are Early, Late, and On-Time. Based on what the user selects, not all of the statuses are present. So when I run the following:
Set rs1 = CurrentDb.OpenRecordset("MyReceivingOnTimeDeliverySummary", dbOpenDynaset)
Set rs = CurrentDb.OpenRecordset("TRANSFORM Sum(recvqty) AS SumOfrecvqty " & _
"SELECT supname, Sum(recvqty) AS TotalReceivedQty " & _
"FROM MyReceivingOnTimeDeliveryDetail " & _
"GROUP BY supname " & _
"PIVOT Status", dbOpenDynaset)
If (rs.RecordCount <> 0) Then
rs.MoveFirst
Do While rs.EOF <> True
rs1.AddNew
rs1.Fields("[supname]").value = rs.Fields("[supname]").value
rs1.Fields("[TotalReceivedQty]").value = rs.Fields("[TotalReceivedQty]").value
rs1.Fields("[Early]").value = rs.Fields("[Early]").value
rs1.Fields("[Late]").value = rs.Fields("[Late]").value
rs1.Fields("[OnTime]").value = rs.Fields("[On-Time]").value
rs1.Update
rs.MoveNext
Loop
End If
If one of the statuses isn't in the results of the query then I will get an error where I am adding that value to the MyReceivingOnTimeDeliverySummary table.
How do I test to for each status and if they are not there then add as 0?
You should be avoiding recordsets for simple operations, like copying with small, uniform changes, in this case. But good news: this makes everything easier!
First, use the SQL statement you already have to create a query.
Dim db As Database
Set db= CurrentDb
db.CreateQueryDef "qry1", "sqltext"
Then, from that query, SELECT INTO (or INSERT INTO) your summary table.
db.Execute "SELECT * INTO MyReceivingOnTimeDeliverySummary FROM qry1"
Then you can add the fields if they aren't there.
On Error Resume Next: db.Execute "ALTER TABLE MyReceivingOnTimeDeliverySummary ADD COLUMN Early NUMBER": Err.Clear: On Error GoTo 0
On Error Resume Next: db.Execute "ALTER TABLE MyReceivingOnTimeDeliverySummary ADD COLUMN Late NUMBER": Err.Clear: On Error GoTo 0
On Error Resume Next: db.Execute "ALTER TABLE MyReceivingOnTimeDeliverySummary ADD COLUMN OnTime NUMBER": Err.Clear: On Error GoTo 0
Finally, fix the nulls to zero.
db.Execute "UPDATE [MyReceivingOnTimeDeliverySummary] SET [Early] = Nz([Early],0)"
db.Execute "UPDATE [MyReceivingOnTimeDeliverySummary] SET [Late] = Nz([Late],0)"
db.Execute "UPDATE [MyReceivingOnTimeDeliverySummary] SET [OnTime] = Nz([OnTime],0)"
Why do it this way? In my experience, SQL is a lot faster than recordsets.
Set the default value to zero for any of the MyReceivingOnTimeDeliverySummary fields which may not be present in the pivot query.
Then loop through the fields in the pivot query recordset and add those fields' values to the matching fields in the other recordset.
Dim fld As DAO.Field
If Not (rs.BOF And rs.EOF) Then
rs.MoveFirst
Do While Not rs.EOF
rs1.AddNew
For Each fld In rs.Fields
rs1.Fields(fld.Name).value = rs.Fields(fld.Name).value
Next
rs1.Update
rs.MoveNext
Loop
End If
Incidentally, you may also find the code operates faster if you substitute dbAppendOnly for dbOpenDynaset here:
OpenRecordset("MyReceivingOnTimeDeliverySummary", dbOpenDynaset)
I'm unsure how much of an impact that change will have. It doesn't change the logic of what you're trying to accomplish. And perhaps any speed impact would be insignificant. But it won't cost you much to find out. :-)