Access reporting with queries? - vba

I have multiple queries that I'm trying to use to build a report. The way I'm doing is by using a temporary table. As I'm building it, I noticed that it takes quite sometime for the report to run because I'm using 2 criteria for each record.
So basically what I'm doing is populating the temp table with 2 values
CompanyID | Unit Price
Now what I do DlookUp the beginning inventory for each CompanyID, here's my code...
rs2.movefirst
with rs2
While Not .EOF
lngCompanyID = rs2("CompanyID")
lngUnitPrice = rs2("UnitPrice")
'GETTING BEGINNING INVENTORY VALUES
lngBegCount = Nz(DLookup("BegCount", "qryBegInv", "UnitPrice = " & lngUnitPrice & " AND CompanyID =" & CompanyID & ""), 0)
.Edit
rs2("BegInvCount") = lngBegCount
.Update
rs2.movenext
wend
End with
The problem I'm seeing is that the qryBEGINV that I'm looking up take about 10-15 seconds to load so since I have over 30 records, it takes a few minutes to run the report. Is there a way for me to be more efficient? Possibly DLOOKUP the values ONCE and then filtering instead of looking them up and filtering for every record?

Start with another temp table populated with the results of qryBegInv so you aren't recreating that results set on every loop iteration.

Related

How to return random records in a table based on query results?

I have a table named Values with thousands of records (sample is a lot less). The data itself is unique except that some records share the same ID.
So I need a query to return random records depending on the total count of TestQ1. For example, the query has a total of 9 records for ID 120 so there should be 3 random records each time this query is run because that's what the Test table indicates (this table changes the "Test" numbers weekly).
Pictured:
The "Values" table is the raw data.
The "TestQ1" query has a total count for that specific "ID"
and to the right, is the number of records that should be returned.
This is as far as I've been able to get:
SELECT TOP 5 Values.ID, Values.Test, Values.State, Rnd([Values]![ID]) AS [Random No], * FROM [Values] ORDER BY Rnd([Values]![ID]);
TOP N cannot be dynamic in a query object.
Can calculate a group sequence ID and use that in criteria to return a number of records for each ID group. That requires a unique identifier field which can be provided with an autonumber. Set it as random so the selection can be different as records are added to dataset.
Consider SQL:
SELECT Values.PKID, Values.ID, Values.Test, Values.State, DCount("*","Values","ID=" & [Values].[ID] & " AND PKID<" & [PKID])+1 AS GrpSeq
FROM TestQ2 INNER JOIN [Values] ON TestQ2.ID = Values.ID
WHERE (((DCount("*","Values","ID=" & [Values].[ID] & " AND PKID<" & [PKID])+1)<=[TestQ2].[Test]));
However, if you truly need to return randomized set of records with each query run, I expect VBA will be needed. Either to populate a field in Values table with a randomized sequence number for each ID group or to populate a temp table with randomized dataset. Consider VBA to populate field:
Sub RandomSeq()
Dim rs1 As DAO.Recordset, rs2 As DAO.Recordset, x As Integer
Set rs1 = CurrentDb.OpenRecordset("SELECT DISTINCT ID FROM [Values]")
Do While Not rs1.EOF
Set rs2 = CurrentDb.OpenRecordset("SELECT * FROM [Values] WHERE ID=" & rs1!ID & " ORDER BY Rnd([ID]);")
Do While Not rs2.EOF
rs2.Edit
x = x + 1
rs2!GrpSeq = x
rs2.Update
rs2.MoveNext
Loop
rs2.Close
x = 0
rs1.MoveNext
Loop
End Sub
Now run query:
SELECT Values.ID, Values.Test, Values.State, Values.GrpSeq
FROM TestQ2 INNER JOIN [Values] ON TestQ2.ID = Values.ID
WHERE (((Values.GrpSeq)<=[TestQ2].[Test]));
Be aware this will not be practical in a split database with multiple simultaneous users. Then a temp table (located in frontend) approach may be needed.

Duplicate records in all related Access tables with new PK/FK assigned at button click event

I have an Access database with 5 tables:
Report_Start holds PK on [Report_ID]
CommSummary holds FK on [Report_ID]
MRPCSummary holds FK on [Report_ID]
SQESummary holds FK on [Report_ID]
MEPSummary holds FK on [Report_ID]
Report_ID links tables 2-4 to Table 1.
User enters new report info on a user form (Report Date, Vendor, buyer, planner, etc), which is stored in Report_Start table. At different times, different users use different forms to populate tables 2-4, and all data is linked by [Report_ID], which is the report date and vendor name concatenated together.
I added a button on the start-up form that needs to allow the user to duplicate existing data from all 5 tables and assign a new Report_ID and new Report_Date. This is needed because the users only have one or two fields to update each time we report out on the vendor status. They want to copy the old record to a new one and then make their one or two updates. (I already have forms and code that allows the existing records to be updated from each table.)
The new Report_ID and Report_Date will be generated by the user selecting from a dialog window:
a) Existing report date (Me.Cbo_OldDate)
b) Vendor (Me.Cbo_Vendor)
c) New report date (Me.Txt_NewDate)
After several days of research, I found a SQL statement that was close to what I wanted to accomplish, but I am getting syntax errors. And I am only trying to duplicate in the #1 table above to prove out my code before attacking the relational tables 2-5.
My code so far:
Start-up Form (user clicks on COPY RECORD button):
Private Sub Cmd_Copy_Click()
DoCmd.OpenForm "New_Data", acNormal, , , , acDialog
End Sub
New_Data form where user selects existing report info and new report date:
Private Sub Cmd_OK_Click()
Dim OldID As String
Dim NewID As String
Dim NewDate As Date
Dim strSQL As String
OldID = Me.Cbo_OldDate & Me.Cbo_Vendor
NewID = Me.Txt_NewDate & Me.Cbo_Vendor
NewDate = Me.Txt_NewDate
Me.Txt_Old_ID = OldID
Me.Txt_New_ID = NewID
strSQL = "SELECT r.Report_ID, r.Report_Date, r.Vendor, r.Buyer, r.MRPC, r.SQE, r.MEP WHERE (((r.Report_ID) = ('" & OldID & "') INTO Report_Start as r WHERE (((r.Report_ID) = '" & NewID & "' and ((r.Report_Date) = (#" & NewDate & "#)));"
CurrentDb.Execute strSQL, dbFailOnError
End Sub
The error I am getting is:
If someone can help me determine where the error is, and then give me some guidance on how to duplicate the information in the relational tables and assign the new FK equal to the new PK, I would GREATLY appreciate it! I do not want to have each user open their individual form to duplicate only their data.
Lastly, due to the relationships between the tables, I want one button to duplicate all, and then each user can look up their info by date and provide their updates.
You can perform and insert into with a select and where like this:
strSQL = "INSERT INTO Report_Start " & _
" SELECT " & newID & ", #" & newDate & "#, r.Vendor, r.Buyer, r.MRPC, r.SQE, r.MEP" & _
" FROM Report Start as r " & _
" WHERE r.Report_ID = " & oldID

Condition for Update vs insert

I have a table tblCosts which i display on an msaccess front end which enables users to add new entries as well as update existing ones. The table is structured as below.
ExpenseType Month Year Cost
Hardware June 2017 $500
Software July 2017 $300
Hardware Sept 2017 $150
I have an update and insert queries which work fine when run manually.
However I am having trouble differentiating the condition when to fire the query on the form. For example, if the record exists in the table, it should run the update query, if record does not exist, it should run the insert query.
For example if someone puts in
- Hardware Sept 2017 $120 it should update the 3rd entry from 150 to 120 but if someone puts in
- Furniture Sept 2017 $350 it should recognize that Furniture is not part of the DB and run the insert query. I have the update and insert queries but need help in identifying the condition when to run them.
The Update query I'm using is:
Update tblCosts
set tblCosts.Cost=[Forms]![frmCost]![txtCost]
where tblCosts.ExpenseType = [Forms]![frmCost]![txtExpType]
and tblCosts.Month = [Forms]![frmCost]![txtMonth]
and tblCosts.Year = [Forms]![frmCost]![txtYear]
The Insert query I'm using is:
Insert into tblCosts (ExpenseType , Month, Year, Cost)
Select [Forms]![frmCost]![txtExpType] as Exp1,
[Forms]![frmCost]![txtMonth] as Exp2,
[Forms]![frmCost]![txtYear] as Exp 3,
[Forms]![frmCost]![txtCost] as Exp 4
Need code (VBA or macro) behind a form that determines which action query to run. In VBA something like:
If DCount("*", "tablename", "ExpenseType='" & Me.cbxExpense & "' AND [Month]='" & Me.tbxMonth & "' AND [Year]=" & Me.tbxYear) = 0 Then
CurrentDb.Execute "INSERT INTO tablename (Expense, [Month], [Year], Cost) VALUES ('" & Me.cbxExpense & "', '" & Me.tbxMonth & "', " & Me.tbxYear & ", " & Me.tbxCost & ")"
Else
CurrentDb.Execute "UPDATE tablename SET Cost=" & Me.tbxCost & " WHERE Expense='" & Me.cbxExpense & "' AND [Month]='" & Me.tbxMonth & ", [Year]=" & Me.tbxYear
End If
Probably also want some validation code to make sure all four controls have data before executing queries.
The real trick is figuring out what event to put code into - the Cost AfterUpdate will work as long as the other fields have data entered first, otherwise the validation will fail and user will have to re-enter cost.
Could have code that doesn't make each control available until previous value is entered.
Month and Year are reserved words and should not use reserved words as names for anything.
Would be better to save month numbers instead of month names for sorting purposes.
Why updating a value which really should be a calculated aggregation of transaction records?

MSAccess VBA AfterUpdate SQL code is running for every record instead of the current record

I have an access database that I will be using to track orders and to track inventory levels. When I attempt to add the parts on my order form (sbfrmOrderDetails) to my inventory table (tblInventory) my VBA code does not execute as planned.
Please note that I have stripped down the code and the tables to just the relevant information/values. The code posted below does work, just not as intended. I explain in much more detail below.
Form Structure
I created my Order form (frmOrder) as a Single Form. This form is referred to in my later code to determine the order number using the txtOrderID control. When I link my subform, the linked master field is OrderID.
Within this form is my Order Details subform (sbfrmOrderDetails) as a continuous form. Every control is bound, and it is linked to the parent form. The linked child field is OrderID.
Photo 1: This photo may better illustrate my form:
Table Structure
The relevant tables I have are structured like so:
TableName: tblOrders
TableColumns: OrderID
TableName: tblOrderDetails
TableColumns: ID|InvID|Qty|OrderID|DeliveryStatus
TableName: tblInventory
TableColumns: ID|InvID|Qty|OrderID
Intended Action
The action I am trying to take occurs in the subform and is supposed to be isolated to the current record. When the user changes the ComboBox (Combo1 bound control to tblOrderDetails.DeliveryStatus), my VBA code will execute an 'INSERT INTO' SQL string that adds the InvID and the Qty from the current record into the inventory table (tblInventory).
VBA Code for Combo1 AfterUpdate Event (On sbfrmOrderDetails)
Private Sub Combo1_AfterUpdate()
Dim db As DAO.Database
Dim strSQL As String
Set db = CurrentDb
If Me.Combo1.Value = "Delivered" Then
strSQL = "INSERT INTO [tblInventory] ([InvID],[Qty])" _
& "SELECT " & Forms![frmOrder].Form![sbfrmOrderDetails].Form![txtInvID] & " AS InvID," & Forms![frmOrder].Form![sbfrmOrderDetails].Form![txtQty] & " AS Qty " _
& "FROM tblOrderDetails WHERE ((tblOrderDetails.OrderID)=(" & Forms![frmOrder]![txtOrderID] & "));"
Debug.Print strSQL
db.Execute strSQL, dbFailOnError
Else
'Other event
End If
End Sub
Intended Results
When Combo1 (bound control) is changed from null to “Delivered” on record ID #11 only, it is supposed to add a single new record.
Photo 2: Intended Results:
Actual Results
When Combo1 (bound control) is changed from null to “Delivered” on record ID #11 only, it is adding a new record for every record populated in the subform.
Please refer to Photo 2 above to compare the Intended Results to the Actual Results.
You can see that the quantity from records 12 and 13 are transferred over under the InvID from record 11.
Please refer to Photo 1 to view the sample data and also to Photo 2 above to see the Actual Result of the code.
I suspect that since this is a continuous form that has Parent/Child linking, the form is running the VBA code once for every record (instead of one time for the current record).
Can I alter my VBA code to only run this code once on the current record as is intended? I am hoping this is the best approach to complete this task.
The output of Debug.Print strSQL would have been helpful (see How to debug dynamic SQL in VBA ), but it would be something like this:
INSERT INTO tblInventory (InvID, Qty)
SELECT 14 AS InvID, 2 AS Qty
FROM tblOrderDetails
WHERE tblOrderDetails.OrderID = 5
You are inserting two constant values, so you may as well use the INSERT ... VALUES (...) syntax, which by definition only inserts one record:
INSERT INTO tblInventory (InvID, Qty)
VALUES (14, 2)
The reason your statement inserts multiple records is because of WHERE tblOrderDetails.OrderID = 5. Multiple records (all on the subform) satisfy this clause.
You would have to specify the OrderDetails ID instead, to get only one record:
INSERT INTO tblInventory (InvID, Qty)
SELECT 14 AS InvID, 2 AS Qty
FROM tblOrderDetails
WHERE tblOrderDetails.ID = <Forms![frmOrder]![sbfrmOrderDetails].Form![txtID]>
tblOrders.OrderID --> this table has not been referenced in the statement and vba should throw an error
strSQL = "INSERT INTO [tblInventory] ([InvID],[Qty])" _
& "SELECT " & Forms![frmOrder].Form![sbfrmOrderDetails].Form![txtInvID] & " AS InvID," & Forms![frmOrder].Form![sbfrmOrderDetails].Form![txtQty] & " AS Qty " _
& "FROM tblOrderDetails WHERE ((tblOrders.OrderID)=
(" & Forms![frmOrder]![txtOrderID] & "));"
Debug.Print strSQL

MS Access/Access-SQL: Switch Function to Calculate Distances based on Criteria

I am somewhat new to MS Access/SQL and StackOverflow, so bear with me. I have a problem that I can't seem to figure out. I had posted this before, but got no responses, and editing my original post did not help.
I have quantity and distance data for assets for each week. Not all weeks have Quantity or distances (some just have 1 or the other). Here is a sample of the data for one asset(put in CSV):
Asset,Week,Qty,Dist,Actual_Dist
2153,1,,125,
2153,2,,65,
2153,3,50.1,118,
2153,4,,123,
2153,5,96.6,91,
2153,6,,103,
2153,7,,120,
2153,8,,106,
2153,9,100.6,,
2153,13,96,,
2153,14,,102,
2153,15,,40,
2153,18,84.82,,
2153,21,97.8,,
2153,25,96.7,,
2153,28,31.27,63,
2153,29,77.5,,
What I want to be able to do, is take a SUM of the "DIST" field until the row has a corresponding "QTY" field value, and this would be calculated in the "Actual_Dist" field.
Looking at my example, Weeks 1 and 2 have no Qty values, however in Week 3, Qty is 50.1 and I would want the "Actual_Dist" calculated as the sum of "Dist" from Weeks 1-3 inclusive. So essentially, Row 3's "Actual_Dist" would be SUM(125+65+118).
Right now I see 3 cases:
Case 1: as above, if no Qty, but has a Dist, then sum the distances until the next Qty value.
Case 2: If Qty exists, but no Dist, then disregard
Case 3: If Qty has a value, and Dist has a value, and Qty has previous values before (ie., Week 28), then "Actual_Dist" = Dist
So I was thinking of doing a select switch to cover the two main cases (1 & 3):
Select Asset, Week, Qty, Dist, Switch (Qty like 'NULL' AND Dist <> 'NULL', SUM(Dist) AS Actual_Dist, Switch (Qty <> 'NULL AND Dist <> 'NULL', DIST) AS Actual_Dist
**Not sure if my Switch is done right, but I think you get the picture?
Now my issues comes in the sum function above. How do I get it sum properly and take the distance values before a qty value is present (Case 2)? I apologize if the formatting or presentation of sample data is poor. Just signed up. I will likely have to clarify some points, so let me know and I will clarify as necessary.
It is also important to note that this is just one asset, and there are many. For the sum function above, I need it to be able to sum the records above for ANY given number of records.
Hope someone can help.
Thanks
EDIT: #Cha had posted the following:
SELECT Data.Asset, Data.Week, Data.Qty, Data.Dist, Switch(Not IsNull([Qty]) And Not IsNull([Dist]),[Dist], Not IsNull([Qty]),Nz(DSum("Dist","Data","Asset=" & CStr([Asset]) & " And Week <= " & CStr([Week]) & " And Week > " & CStr(Nz(DMin("Week","Data","Asset=" & CStr([Asset]) & " And Week < " & CStr([Week]) & " And Not IsNULL(Qty)"),0))),0)) AS Actual_Dist FROM Data;
This code gave me errors due to data mismatch, so I changed all the data types to "Number" and modified the code as follows:
SELECT Data.Asset, Data.Week, Data.Qty, Data.Dist, Switch(Not IsNull([Qty]),Nz(DSum("Dist","[Data]","Asset=" & [Asset] & " And Week <= " & [Week] & " And Week > " & Nz(DMin("Week","[Data]","Asset=" & [Asset] & " And Week < " & [Week] & " And Not IsNULL([Qty])"),0)),0)) AS Actual_Dist
FROM Data;
The above code now satisfies Case 1, but only satisfies it for Row 3 and 5. This Case does not satisfy Rows 9 and 13, and it needs to apply there too. I believe the issue with those rows is that the "Dist" is NULL.
There is another issue, Case 1 and Case 3 overwrite eachother occasionally (when both Qty and Dist are not NULL. Is there a way to create 1 switch to run Case 1, and another (with the same code) to apply Case 3 but not Case 1?
Any help would be much appreciated!
Thanks
If you can use VBA, then try this:
Public Sub GetActual_Dist()
Const TABLE_NAME As String = "tblAssets"
Dim sTemp_Dist As Single
With CurrentDb.OpenRecordset("SELECT * FROM [tblAssets] WHERE NOT ([Qty] is not Null AND [Dist] is Null);")
If .EOF And .BOF Then
MsgBox "No data"
Exit Sub
End If
Do Until .EOF
If ![Qty] Is Null Then
sTemp_Dist = sTemp_Dist + ![Dist]
Else
.Edit
![Actual_Dist] = sTemp_Dist
.Update
sTemp_Dist = 0
End If
.MoveNext
Loop
End With
End Sub
Change the TABLE_NAME to the one in your database.