Condition for Update vs insert - sql

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?

Related

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

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

Select a maximum value within a date range

Task:
Append/edit the currently working code below to return only one row per patient, the maximum value of d1_10.xtransfer (datatype int) with the restriction that this row's d1_10.dstartdate <= glob_End_Date.
Caveats:
There are similar questions on StackOverflow and its sister sites. None that I have found have successfully helped with a resolution to this issue.
This is a medical EHR database, I can share code, but any discussion of results has to be general and exclude patient information.
I am replacing the SQL query within a pre-existing Excel spreadsheet to do something different. Excel pulls information from our database with an ODBC connection. Our database is using Ingres SQL which accepts most, but not all, of your typical SQL code varieties. It's possible that a piece of code will generally work in other flavors of SQL but not with the combo of Ingres and Excel. I've got the spreadsheet working and returning results, now it's about making some fixes by writing SQL code that works in this software.
Thus far:
With the currently working code below (no maximum d1_10.xtransfer restrictions) we return all rows with d1_10.dstartdate in the user selected date range and with the user selected d1_10.xinstitute. We want just the latest one. That is, the patient's row with either the maximum d1_10.dstartdate within the date range, or the maximum d1_10.xtransfer (index that counts up as they are added) within the date range.
Currently working code:
"SELECT " & _
"d1.xpid ""XPID"", " & _
"d0_v1.name_family ""NAME_FAMILY"", " & _
"d0_v1.name_given1 ""NAME_GIVEN1"", " & _
"d0_v1.name_given2 ""NAME_GIVEN2"", " & _
"d1.sex ""SEX"", " & _
"d1.birthdate ""DOB"", " & _
"d0_v1.hsp_pid, " & _
"c58.brief_name, " & _
"c73.cname, " & _
"date_trunc('day',d1_10.dstartdate) ""DSTARTDATE"", " & _
"date_trunc('day',d1_17.ddeath) ""DDEATH"" " & _
"FROM d1 " & _
"JOIN d0_v1 ON d1.xpid = d0_v1.xpid " & _
"JOIN d1_2 ON d1.xpid = d1_2.xpid " & _
"JOIN c58 ON d1_2.xmodality = c58.xcmodality " & _
"JOIN d1_10 ON d1.xpid = d1_10.xpid " & _
"JOIN c73 ON d1_10.xinstitute = c73.xcsite " & _
"JOIN d1_17 ON d1.xpid = d1_17.xpid " & _
"WHERE " & _
"d1_10.xinstitute = " & institute_index & " AND " & _
"d1_10.dstartdate >= '" & glob_Start_Date & " 00:00:00' and " & _
"d1_10.dstartdate <= '" & glob_End_Date & " 23:59:59' "
The closest I have gotten with code that runs from the excel spreadsheet is with this additional line in the WHERE clause:
d1_10.xtransfer = (SELECT MAX(d1_10.xtransfer) FROM d1_10 GROUP BY xpid)
With this additional line we now return only one row from each patient that has a d1_10.xtransfer within the date range. But if they have a row where d1_10.xtransfer is more recent than the date range, then they don't show up in the results at all.
With this line the code is taking MAX(d1_10.xtransfer) for each xpid before it applies the date restriction. By my logic we want it to do so after instead, but I have been unable to come up with code that runs that gets it any closer than this.
Thanks in advance. I'll keep this question updated with additional info below this page break.
Additional Info:
Per PaulM:
Yes, xpid is a patient ID index number, unique to each patient.
Added/edited line in WHERE clause to: "d1_10.xtransfer = (SELECT MAX(xtransfer) FROM d1_10 d1_10_b WHERE d1_10.xpid = d1_10_b.xpid AND d1_10_b.dstartdate <= '" & glob_End_Date & " 23:59:59') "
Patient Bob has transfers on both the 14th and 17th of June that fit the rest of the criteria.
When inputting a date range with an end date of Jun 17+, the spreadsheet correctly returns a row for Bob with his Jun 17 transfer.
When inputting a date range with an end date of Jun 14,15 or 16, the spreadsheet incorrectly does not return a row for Bob.
It seems as though it still takes the maximum xtransfer before restricting by date.
Per PaulM's comment:
I ran the subselect for a specific patient as follows:
Input:
SELECT MAX(xtransfer) FROM d1_10 d1_10_b WHERE d1_10_b.xpid = '2258' AND d1_10_b.dstartdate <= '20-apr-2016 23:59:59'
It outputted a value of MAX(xtransfer) = '48233'. This is correct.
So, when run in Visual SQL as its own statement, setting d1_10_b.xpid equal to a specific patient, it correctly pulls the maximum xtransfer from the date range. (There was a more recent xtransfer outside of the date range, and it still correctly displayed the maximum xtransfer from within the date range.)
I then tried running this exact same subselect in the where close for the spreadsheet. That is, I manually selected the same date range (which is being passed through as a variable correctly and successfully) but I subbed out d1_10.xpid = d1_10_b.xpid for d1_10_b.xpid = '2258'. This did not work. The spreadsheet did not show a row for this patient, seemingly because it still applies the MAX() function before it restricts by the date range in the subquery. And yet, the subquery works when run by itself.
Much appreciation for any further suggestions.
You need to add the date restriction in the subselect as well as the main query. Also I suspect the group by is wrong. By adding a group by you're making the subselect a list of patients xtransfer values with the largest value for each xpid (identifies a patient?). However that means if the row you're interested in from the main query happens to have an xtransfer value that matches the largest one belonging to a different xpid you're getting a false match.
What you really need is to add a join on xpid from the subselect back up to the main query. To do that you'll need a different correlation name e.g.
d1_10.xtransfer = (SELECT MAX(xtransfer)
FROM d1_10 d1_10_b
WHERE d1_10.xpid = d1_10_b.xpid
AND d1_10_b.dstartdate > = ... {as above} )

Access reporting with queries?

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.

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.