Optimising a VBA procedure that iterates over two different tables of data - sql

I have two sql tables pricetbl and paymenttbl. Due to the fact that prices for item change everyday, I need to update the paymenttbl with prices of the right time. If a transaction is made after the date-effective of a product's price, then the transaction price in the paymenttbl is the same as the latest product's price (as long as the date-effective is smaller than transaction date). I don't only want to limit myself here, I want to iterate over the whole table to make changes to the data so every single transaction has the proper price.
PriceTbl
pID| Price| Date Effective
---------------------------
1 | 10 | 01-12-2014
2 | 20 | 01-02-2015
3 | 20 | 02-12-2014
2 | 40 | 20-03-2015
1 | 50 | 02-03-2015
4 | 34 | 20-02-2015
1 | 40 | 25-05-2015
PaymentTbl
payID | pID | transDate | Price
--------------------------------
1 | 1 | 02-12-2014| 05
2 | 1 | 04-03-2015| 10
3 | 2 | 21-04-2015| 35
4 | 3 | 03-12-2014| 15
Expected table paymenttbl after running query
payID | pID | transDate | Price
--------------------------------
1 | 1 | 02-12-2014| 10
2 | 1 | 04-03-2015| 50
3 | 2 | 21-04-2015| 40
4 | 3 | 03-12-2014| 20
What will be the fastest and easiest way to retrieve and update data? My current way takes up to like 30 minutes to update the table since I iterate over the paymenttbl then I iterate over the pricetbl and compare it to each item in the paymenttbl.
My Code:
Function updateWorktoDateEff()
Dim rt As DAO.Recordset
Dim wt As DAO.Recordset
Dim db As DAO.Database
Dim sqlrt As String
Dim sqlwt As String
Set db = CurrentDb
sqlrt = "select * from pricetbl where (dailyrate IS NOT NULL) and (dateeffective is not null) order by id, dailyrate"
sqlwt = "select * from paymenttbl where (id > 0) order by id, date"
Set rt = db.OpenRecordset(sqlrt)
Set wt = db.OpenRecordset(sqlwt)
Do While Not wt.EOF
Do While Not ratetable.EOF
If rt!ID = wt!ID Then
If rt <= wt!Date Then
wt.Edit
wt!Rate = ratetable!dailyrate
wt.Update
End If
ElseIf ratetable!ID > worktable!ID Then
Exit Do
End If
rt.MoveNext
Loop
rt.MoveFirst
wt.MoveNext 'move to the next worktbl record
Loop
End Function
simply iterating/retrieving the data wouldn't do it. I will also need to be able to alter it.

The most efficient way to perform this update is not to use recordsets and go row-by-row, but to use SQL to do a set-based update. I tried writing a single update query to get there, but MS-Access got fussy about the query not being updateable, so I instead wrote two queries: one to write to a temporary table (tempNewPrices) and one to update PaymentTbl using that temporary table.
Here's the first query to create the temporary table that includes the new column NewPrice:
SELECT PaymentTbl.payID, PaymentTbl.pID, PaymentTbl.transDate, PaymentTbl.Price,
(
SELECT [Price]
FROM PriceTbl pr
WHERE pr.pID = PaymentTbl.pID
AND pr.[Date Effective] = (
SELECT Max([Date Effective])
FROM PriceTbl pr2
WHERE pr2.pID = pr.pID
AND pr2.[Date Effective]< PaymentTbl.[transDate]
)
) AS NewPrice INTO tempNewPrices
FROM PaymentTbl;
and here's the update query to change the Price value in PaymentTbl:
UPDATE tempNewPrices
INNER JOIN PaymentTbl ON tempNewPrices.payID = PaymentTbl.payID
SET PaymentTbl.Price = [NewPrice];
This should be much more efficient than looping through recordsets.

Related

SQL - Set column value to the SUM of all references

I want to have the column "CurrentCapacity" to be the SUM of all references specific column.
Lets say there are three rows in SecTable which all have FirstTableID = 1. Size values are 1, 1 and 3.
The row in FirstTable which have ID = 1 should now have a value of 5 in the CurrentCapacity column.
How can I make this and how to do automatically on insert, update and delete?
Thanks!
FirstTable
+----+-------------+-------------------------+
| ID | MaxCapacity | CurrentCapacity |
+----+-------------+-------------------------+
| 1 | 5 | 0 (desired result = 5) |
+----+-------------+-------------------------+
| 2 | 5 | 0 |
+----+-------------+-------------------------+
| 3 | 5 | 0 |
+----+-------------+-------------------------+
SecTable
+----+-------------------+------+
| ID | FirstTableID (FK) | Size |
+----+-------------------+------+
| 1 | 1 | 2 |
+----+-------------------+------+
| 2 | 1 | 3 |
+----+-------------------+------+
In general, a view is a better solution than trying to keep a calculated column up-to-date. For your example, you could use this:
CREATE VIEW capacity AS
SELECT f.ID, f.MaxCapacity, COALESCE(SUM(s.Size), 0) AS CurrentCapacity
FROM FirstTable f
LEFT JOIN SecTable s ON s.FirstTableID = f.ID
GROUP BY f.ID, f.MaxCapacity
Then you can simply
SELECT *
FROM capacity
to get the results you desire. For your sample data:
ID MaxCapacity CurrentCapacity
1 5 5
2 5 0
3 5 0
Demo on SQLFiddle
Got this question to work with this trigger:
CREATE TRIGGER UpdateCurrentCapacity
ON SecTable
AFTER INSERT, UPDATE, DELETE
AS
BEGIN
SET NOCOUNT ON
DECLARE #Iteration INT
SET #Iteration = 1
WHILE #Iteration <= 100
BEGIN
UPDATE FirstTable SET FirstTable.CurrentCapacity = (SELECT COALESCE(SUM(SecTable.Size),0) FROM SecTable WHERE FirstTableID = #Iteration) WHERE ID = #Iteration;
SET #Iteration = #Iteration + 1
END
END
GO
Personally, I would not use a trigger either or store CurrentCapacity as a value since it breaks Normalization rules for database design. You have a relation and can already get the results by creating a view or setting CurrentCapacity to a calculated column.
Your view can look like this:
SELECT Id, MaxCapacity, ISNULL(O.SumSize,0) AS CurrentCapacity
FROM dbo.FirstTable FT
OUTER APPLY
(
SELECT ST.FirstTableId, SUM(ST.Size) as SumSize FROM SecTable ST
WHERE ST.FirstTableId = FT.Id
GROUP BY ST.FirstTableId
) O
Sure, you could fire a proc every time a row is updated/inserted or deleted in the second table and recalculate the column, but you might as well calculate it on the fly. If it's not required to have the column accurate, you can have a job update the values every X hours. You could combine this with your view to have both a "live" and "cached" version of the capacity data.

SQL Insert when not exist Update if exist with mutliple rows in Target-Table

I have two tables, where table A has to be Updated or insert a row base on existing. I tried this by using JOINS EXCEPT and MERGE statement but I have one problem i can't solve. so here is an example :
Table A (Attribut-Table)
attr | attrValue | prodID
--------------------------
4 | 2 | 1
--------------------------
3 | 10 | 2
--------------------------
1 | 7 | 2
--------------------------
3 | 10 | 3
--------------------------
6 | 9 | 3
--------------------------
1 | 4 | 3
--------------------------
Table P(Product-Table)
prodID | stock |
------------------
1 | 1
------------------
2 | 0
------------------
3 | 1
------------------
4 | 1
------------------
Now what i would like to do the following in SQL:
All products, that has Stock > 0 should have an entry in Table A with attr = 6 and attrValue = 9
All products, that has Stock < 1 should have an entry in Table A with attr = 6 and attrValue = 8
i need a SQL Query to do that because my problem is that there are multiple entries for a prodID in Table A
That is what i am thinking of:
Fist check if any entry for the prodID(in Table B) exist in Table A, if not INSERT INTO Table A ( attr=6 and, attrValue = 8/9 (depends on Stock), prodID
If there is already an entry for the prodID in Table A with the attr = 6, then Update this row and set attrValue to 8/9 (depending on stock)
so I am looking for a translation of "my thoughts" to a sqlQuery
thanks for helping.
(using: SQL SERVER Express 2012 and HEIDI SQL for management)
Since your "attr 6" row is 100 % derivable from the state of the P table, it is a poor idea to store that row redundantly in A.
This is better :
(1) Define a first view ATTR6_FOR_P as SELECT prodID, 6 as attr, CASE (...) as attrValue from P. The CASE expression chooses the value 8 or 9 according to stock value in P.
(2) Define a second view A_EXT as A UNION ATTR6_FOR_P. (***)
Now changes in stock will always immediately be reflected in A_EXT without having to update explicitly.
(***) but beware of column ordering because SQL UNION does not match columns by name but by ordinal position instead.

Count number of table columns that are not null - MS Access SQL

I have a table which I have shown a simplified example of below:
ID | Item1 | Item2 | Item3 | Item4 | Item5
------------------------------------------
A | NULL | NULL | YES | YES | NULL
B | NULL | NULL | NULL | YES | NULL
C | NULL | NULL | NULL | NULL | NULL
I want to return the following data set:
ID | Count
------------
A | 2
B | 1
C | 0
I.e. I want a count of how many of the columns are NOT NULL for that ID
One potential solution would be
SELECT
ID,
SUM(
IIf(Item1 is NULL,0,1)
+
IIf(Item2 is NULL,0,1)
+
IIf(Item3 is NULL,0,1)
+
IIf(Item4 is NULL,0,1)
+
IIf(Item5 is NULL,0,1)
) 'Count'
FROM
tableName
GROUP BY
ID
However in practice the real table I am using has over a hundred columns and I would prefer to avoid having to write out the names of each column. Is there a simpler way to do this?
You can use VBA to loop through every record and field:
Function CountFields()
Set db = CurrentDb()
db.Execute ("delete * from ItemCounts")
Set RS = db.OpenRecordset("select * from [DataTable]")
RS.MoveFirst
Do While Not RS.EOF
Id = RS.Fields("ID").Value
Count = 0
For Each Item In RS.Fields
If (Item.Name <> "ID" And RS.Fields(Item.Name).Value <> "") Then Count = Count + 1
Next Item
db.Execute ("insert into ItemCounts (ID,[count]) select " & Id & "," & Count)
RS.MoveNext
Loop
MsgBox ("done")
End Function
This puts the counts in a table called ItemCounts, which needs to be set up before the VBA is executed. The fields in that table are ID and Count.
And, if you can reformat the source data, I agree with Minty - but I know that's not always feasible.
Your data is not normalised and therefore you are having to perform gymnastics in your code to work around the problem.
Your data should be stored vertically not horizontally;
ID | ItemNo | Value
---------------------
A | 2 | 1
A | 3 | 1
B | 4 | 1
This would make your query a simple total query, and allow for any number of items. You are also only storing data when you have some not for every case.
Edit: This will loop through the fields
Dim Rst As Recordset
Dim f As Field
Set Rst = CurrentDb.OpenRecordset(TableName)
For Each f In Rst.Fields
Debug.Print (f.name)
Next
Rst.Close
You can reduce it a little:
SELECT
ID,
ABS(SUM((Item1 is Not NULL)+(Item2 is Not NULL)+(Item3 is Not NULL)+(Item4 is Not NULL)+(Item5 is Not NULL))) As [Count]
FROM
tableName
GROUP BY
ID

In MS Access, how do I update a table record to its current value plus the count of records in a different table?

I have two tables.
**tblMonthlyData**
ReportMonth | TotalItems | TotalVariances
Jan | 5 | 0
Feb | 1 | 1
Mar | 2 | 0
Apr | 8 | 4
May | 4 | 0
Jun | 5 | 0
Jul | 3 | 0
Aug | 5 | 0
Sep | 9 | 3
Oct | 1 | 0
Nov | 7 | 0
Dec | 6 | 0
and
**tblDailyData**
ID | ItemNum | CountedQty | SystemQty | Variance
1 | Item1 | 4 | 4 | 0
2 | Item2 | 8 | 5 | -3
3 | Item3 | 1 | 2 | 1
4 | Item4 | 6 | 4 | -2
For the sake of clarity, we'll say the above tblDailyData is from a count done today, 01/27/2017. Variance is a calculated field based on the data in both quantity fields.
I'm trying to add the count of records in tblDailyData to TotalItems in tblMonthlyData based on the date of the count (i.e. counts are done daily and each counts data needs to be added to the appropriate month in tblMonthlyData). So for the above example I'd need to add 4 (number of records) to TotalItems in tblMonthlyData for the Jan record, resulting in the updated record being 9, and add 3 (number of variances) to TotalVariances, resulting in the updated record being 3.
So far, I've tried using a Make Table Query for both total items counted and total number of variances, then using an Update Query that looks like this:
UPDATE tblMonthlyData
SET TotalItems = TotalItems + tblTempTotalItems.CountOfItems,
TotalVariances = TotalVariances + tblTempTotalVariances.CountOfVariances
WHERE Format$([ReportMonth],"mmm")=Format$(Now(),"mmm");
I've also tried a similar method using select queries to count records and variances (without creating the temporary tables) and running the update query based on those. Both methods result in Access prompting for the CountOfItems and CountOfVariances parameters when the update query is ran instead of just taking the values from the specified temporary table or select query.
This seemed like it'd be such a simple operation (query the count of records and variances, add them to the appropriate monthly record in separate table), but it turns out I can't figure out how to make it work. Thanks for any help!
This does not seem to be a situation for a table, but rather for some views/queries, which will always be up to date.
Use a GROUP BY FORMAT([date_field],"mm/dd/yyyy") clause in your query for daily item count (if you want to add that to a montlhy count, we will do that in ANOTHER query.
SELECT FORMAT([date_field],"mm/dd/yyyy") AS Date, COUNT(ID) AS TotalItems
FROM tblDailyData
GROUP BY Date
Call this query dailyTotalItems.
SELECT FORMAT([date_field],"mm/dd/yyyy") AS Date, COUNT(ID) AS TotalItemsWithVariance, SUM(
FROM tblDailyData
WHERE NOT (Variance = 0)
GROUP BY Date
Call this query dailyTotalItemsWithVariance.
SELECT MONTH([date_field]) As MonthDate, SUM(TotalItems) As TotalMonthlyItems
FROM dailyTotalItems
GROUP BY MonthDate
Call this query monthlyTotalItems.
SELECT MONTH([date_field]) As MonthDate, SUM(TotalItemsWithVariance) As TotalMonthlyItemsWithVariance
FROM dailyTotalItemsWithVariance
GROUP BY MonthDate
Call this query monthlyTotalItemsWithVariance.
Then LEFT JOIN both on MonthDate.
SELECT * FROM monthlyTotalItems
LEFT JOIN monthlyTotalItemsWithVariance ON monthlyTotalItems.MonthDate = monthlyTotalItemsWithVariance.MonthDate
NOTE: TotalItems will always be >= TotalItemsWithVariance AND every date with a variance must have had a count. So get ALL dates in monthlyTotalItems and left join to match the monthlyTotalItemsWithVariance items (which must be included, as shown above)

Access - How to delete records if there is data with a newer date

I need to delete some records from access. My data looks like,
RDNUMB | RD SEQ | COUNTDATE | COUNT
-------+--------+-----------+--------
101200 | 10 | 3/25/12 | 120
101200 | 20 | 2/27/13 | 1400
101200 | 20 | 6/15/11 | 905
101200 | 20 | 10/1/07 | 1020
I need to figure out a way to look at look at the RDNUMB and RD SEQ and delete the entire record if there are records with a newer date. In this case I need to delete the records on dates 6/15/11 and 10/1/07.
RD SEQ is not unique to only this RDNUMB it is used over and over again.
Thanks for your thoughts and time
From a SQL Perspective this is what you want to do:
DELETE T
FROM YourTable AS T
JOIN ( SELECT RDNUMB, RDSEQ, COUNTDATE
FROM YourTable AS YT
WHERE YT.RDNUMB = T.RDNUMB AND YT.RDSEQ = T.RDSEQ
AND YT.CountDate <> (SELECT MAX(COUNTDATE) FROM YourTable
WHERE RDNUMB = YT.RDNUMB AND RDSEQ = YT.RDSEQ)
) AS R
ON R.RDNUMB = T.RDNUMB AND R.RDSEQ = T.RDSEQ AND R.COUNTDATE = T.COUNTDATE