Access VBA Filtering out multiple Criteria one a table mark complete on when all is Done - vba

Have a table that has multiple Order Numbers and with those there is multiple Line Numbers. Basically There is a Order Number 123456 and there could be 5 Different Line Numbers with it. I want to be able to say when all those line numbers are complete then Mark the Order Number Complete. Thinking about using a recordset and a dcount. As you can see in the picture there are multiple Line Numbers with the same Order Number.

Saving calculated data, especially aggregate calc, is often not necessary and can even put data integrity at risk - saved calculated aggregate can get 'out of sync' with data. "Done" status can be calculated when needed. Domain aggregate function is one way. In a textbox or query:
IIf(DCount("*", "OrderDetails", "ord_no='" & [ord_no] & "' AND doc_completed = False")=0, "Done", "Not Done")
or
Nz(DLookup("doc_completed", "OrderDetails, "ord_no='" & [ord_no] "' AND doc_completed = False"), "True")
A correlated subquery should be able to return same value and possibly perform more efficiently.
SELECT *, IIf((SELECT Count(*) FROM OrderDetails WHERE ord_no=Orders.OrderNumber AND doc_completed = False)=0, True, False) AS IsDone FROM Orders;
Or build an aggregate query that counts records where doc_completed = False GROUP BY ord_no and join that query to Orders table.

Related

SQL - Loop through a list and add to a variable using sql select statements

I have data loaded in a table called Trades. Now I need to query this table, find elements that satisfy a particular condition and produce the trade value amount.
Here is the requirement
TradeAmt = 0
Loop for all Trades
{IF TradeId is 35
If type = 'I'
ADD (TradeAmt =TradeAmt + col_TradeAmt )
else
ADD (TradeAmt = TradeAmt + col_TradeAmtOverlap )
END-IF}
Return TradeAmt
Data:
Row1: tradeid=35, type=I, col_TradeAmt=10, col_TradeAmtOverlap=20
Row2: tradeid=35, type=S, col_TradeAmt=30, col_TradeAmtOverlap=40
Output: TradeAmt=50
How can i write this using SQL statements.
Well, in SQL you don't really loop over a sequence.
You write a statement that describes what you want to get from the set of data (e.g. the Trades table).
In your case, you want to accumulate all the elements in some way and provide that accumulation as a result, you can do that by using an aggregate function like SUM.
Something along these lines probably could work. Note that I'm nesting two queries here, the inner one to decide which column to treat as the "Amount" to accumulate depending on the Type of the trade and also to filter only the trade with Id 35, and the outer query performs the sum aggregate of all amounts:
SELECT SUM("Amount") FROM
(SELECT
CASE
WHEN Type = 'I' THEN col_TradeAmt
ELSE col_TradeAmtOverlap
END "Amount"
FROM Trades
WHERE TradeId = 35) "TradeAmt";

Calculation based on values in 2 different rows

I have a table in MS Access which has stock prices arranged like
Ticker1, 9:30:00, $49.01
Ticker1, 9:30:01, $49.08
Ticker2, 9:30:00, $102.02
Ticker2, 9:30:01, $102.15
and so on.
I need to do some calculation where I need to compare prices in 1 row, with the immediately previous price (and if the price movement is greater than X% in 1 second, I need to report the instance separately).
If I were doing this in Excel, it's a fairly simple formula. I have a few million rows of data, so that's not an option.
Any suggestions on how I could do it in MS Access?
I am open to any kind of solutions (with or without SQL or VBA).
Update:
I ended up trying to traverse my records by using ADODB.Recordset in nested loops. Code below. I though it was a good idea, and the logic worked for a small table (20k rows). But when I ran it on a larger table (3m rows), Access ballooned to 2GB limit without finishing the task (because of temporary tables, the size of the original table was more like ~300MB). Posting it here in case it helps someone with smaller data sets.
Do While Not rstTickers.EOF
myTicker = rstTickers!ticker
rstDates.MoveFirst
Do While Not rstDates.EOF
myDate = rstDates!Date_Only
strSql = "select * from Prices where ticker = """ & myTicker & """ and Date_Only = #" & myDate & "#" 'get all prices for a given ticker for a given date
rst.Open strSql, cn, adOpenKeyset, adLockOptimistic 'I needed to do this to open in editable mode
rst.MoveFirst
sPrice1 = rst!Open_Price
rst!Row_Num = i
rst.MoveNext
Do While Not rst.EOF
i = i + 1
rst!Row_Num = i
rst!Previous_Price = sPrice1
sPrice2 = rst!Open_Price
rst!Price_Move = Round(Abs((sPrice2 / sPrice1) - 1), 6)
sPrice1 = sPrice2
rst.MoveNext
Loop
i = i + 1
rst.Close
rstDates.MoveNext
Loop
rstTickers.MoveNext
Loop
If the data is always one second apart without any milliseconds, then you can join the table to itself on the Ticker ID and the time offsetting by one second.
Otherwise, if there is no sequence counter of some sort to join on, then you will need to create one. You can do this by doing a "ranking" query. There are multiple approaches to this. You can try each and see which one works the fastest in your situation.
One approach is to use a subquery that returns the number of rows are before the current row. Another approach is to join the table to itself on all the rows before it and do a group by and count. Both approaches produce the same results but depending on the nature of your data and how it's structured and what indexes you have, one approach will be faster than the other.
Once you have a "rank column", you do the procedure described in the first paragraph, but instead of joining on an offset of time, you join on an offset of rank.
I ended up moving my data to a SQL server (which had its own issues). I added a row number variable (row_num) like this
ALTER TABLE Prices ADD Row_Num INT NOT NULL IDENTITY (1,1)
It worked for me (I think) because my underlying data was in the order that I needed for it to be in. I've read enough comments that you shouldn't do it, because you don't know what order is the server storing the data in.
Anyway, after that it was a join on itself. Took me a while to figure out the syntax (I am new to SQL). Adding SQL here for reference (works on SQL server but not Access).
Update A Set Previous_Price = B.Open_Price
FROM Prices A INNER JOIN Prices B
ON A.Date_Only = B.Date_Only
WHERE ((A.Ticker=B.Ticker) AND (A.Row_Num=B.Row_Num+1));
BTW, I had to first add the column Date_Only like this (works on Access but not SQL server)
UPDATE Prices SET Prices.Date_Only = Format([Time_Date],"mm/dd/yyyy");
I think the solution for row numbers described by #Rabbit should work better (broadly speaking). I just haven't had the time to try it out. It took me a whole day to get this far.

Aggregate function / Group By - invalid column error

Using classic ASP and MS-SQL server 2008
What I am trying to do is select duplicate records, and count them so I can then update the first one and delete the rest.
My query selects the duplicates OK but as soon as I try and introduce a count it stops working with the message "Column 'calendar.id' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause."
I have tried to rewrite this and added the count to the second SELECT and adding the id to GROUP BY (and as many combinations of this sort of thing as I can think of but I still get errors. The answers I have looked at on stackoverflow for a solution seem to be a little complicated and not really applicable (or I'm not looking for the right thing)
Where am I going wrong with this?
(also is this the best method to look for and edit duplicates in a smallish table (200k rows?)
Dim strSQL_dup, rsSQL_dup, SQL_dupRecords, RecCount
strSQL_dup = "SELECT id, COUNT(id) AS RecCount FROM calendar WHERE start_date IN ( SELECT start_date FROM calendar WHERE pId = '" & pId & "' GROUP BY start_date HAVING (COUNT(start_date ) > 1) ) "
Set rsSQL_dup = conn.Execute(strSQL_dup)
While Not rsSQL_dup.EOF
If RecCount = 1 Then
'will eventually update the row
response.write(rsSQL_dup("id")) ' id of first'
Else
'will eventually delete the other rows
response.write(rsSQL_dup("id")) ' id of subsiquet rows
End If
rsSQL_dup.MoveNext
Wend
It seems you are missing GROUP BY in the main query:
SELECT id, COUNT(id) AS RecCount
FROM calendar
WHERE start_date IN (SELECT start_date FROM calendar
WHERE pId = '" & pId & "'
GROUP BY start_date
HAVING (COUNT(start_date ) > 1) )
GROUP BY clalendar.id
(The last line can be of course GROUP BY id I just wanted to highlight the link between the last and first lines)
The issue is that COUNT is an aggregate function, so whatever argument you pass to it needs to be in your GROUP BY clause.
If id itself is your key to determine uniqueness, you can change your query to
SELECT id, COUNT(id) AS RecCount FROM calendar group by id having count(id) > 1
If the date matters for filtering a subset of your data e.g. to search in a particular period only, you can include it in a WHERE clause. On the other hand, if date and id together determine uniqueness, you will need to include both fields in your GROUP BY clause.

MS Access SQL Query using Sum() and Count() gives incorrect results

I am having an issue with a query which returns results that are very far from reality (not only does it not make sense at all but I can also calculate the correct answer using filters).
I am building a KPI db for work and this query returns KPIs by employee by period. I have a very similar query from which this one is derived which returns KPIs by sector by period which gives the exact results I have calculated using a spreadsheet. I really have no idea what happens here. Basically, I want to sum a few measures that are in the maintenances table like temps_requete_min, temps_analyse_min, temps_maj_min and temps_rap_min and then create a subtotal AND present these measures as hours (measures are presented in minutes, thus the divide by 60).
SELECT
[anal].[prenom] & " " & [anal].[nom] AS Analyste,
maint.periode, maint.annee,
Round(Sum(maint.temps_requete_min)/60,2) AS REQ,
Round(Sum(maint.temps_analyse_min)/60,2) AS ANA,
Round(Sum(maint.temps_maj_min)/60,2) AS MAJ,
Round(Sum(maint.temps_rap_min)/60,2) AS RAP,
Round((Sum(maint.temps_requete_min)+Sum(maint.temps_analyse_min)+Sum(maint.temps_maj_min)+Sum(maint.temps_rap_min))/60,2) AS STOTAL,
Count(maint.periode) AS Nombre,
a.description
FROM
rapports AS rap,
analyste AS anal,
maintenances AS maint,
per_annuelle,
annees AS a
WHERE
(((rap.id_anal_maint)=anal.id_analyste) And
((maint.id_fichier)=rap.id_rapport) And
((maint.maint_effectuee)=True) And
((maint.annee)=per_annuelle.annee) And
((per_annuelle.annee)=a.annees))
GROUP BY
[anal].[prenom] & " " & [anal].[nom],
maint.periode,
maint.annee,
a.description,
anal.id_analyste
ORDER BY
maint.annee, maint.periode;
All measures are many orders of magnitude higher than what they should be. I suspect that my Count() is wrong, but I can't see what would be wrong with the sums :|
Edit: Finally I have come up with this query which shows the same measures I have calculated using Excel from the advice given in the comments and the answer provided. Many thanks to everyone. What I would like to know however, is why it makes a difference to use explicit joins rather than implicit joins (WHERE clause on PKs).
SELECT
maintenances.periode,
[analyste].[prenom] & " " & analyste.nom,
Round(Sum(maintenances.temps_requete_min)/60,2) AS REQ,
Round(Sum(maintenances.temps_analyse_min)/60,2) AS ANA,
Round(Sum(maintenances.temps_maj_min)/60,2) AS MAJ,
Round(Sum(maintenances.temps_rap_min)/60,2) AS RAP,
Round((Sum(maintenances.temps_requete_min)+Sum(maintenances.temps_analyse_min)+Sum(maintenances.temps_maj_min)+Sum(maintenances.temps_rap_min))/60,2) AS STOTAL,
Count(maintenances.periode) AS Nombre
FROM
(maintenances INNER JOIN rapports ON maintenances.id_fichier = rapports.id_rapport)
INNER JOIN analyste ON rapports.id_anal_maint = analyste.id_analyste
GROUP BY analyste.prenom, maintenances.periode
In this case, the problem is typically that your joins are bringing together multiple dimensions. You end up doing a cross product across two or more categories.
The fix is to do the summaries independently along each dimension. That means that the "from" clause contains subqueries with group bys, and these are then joined together. The group by would disappear from the outer query.
This would suggest having a subquery such as:
from (select maint.periode, maint.annee,
Round(Sum(maint.temps_requete_min)/60,2) AS REQ,
Round(Sum(maint.temps_analyse_min)/60,2) AS ANA,
Round(Sum(maint.temps_maj_min)/60,2) AS MAJ,
Round(Sum(maint.temps_rap_min)/60,2) AS RAP,
Round((Sum(maint.temps_requete_min)+Sum(maint.temps_analyse_min) +Sum(maint.temps_maj_min)+Sum(maint.temps_rap_min))/60,2) AS STOTAL,
Count(maint.periode) AS Nombre,
from maintenances maint
group by maint.periode, maint.annee
) m
I say "such as" because without a layout of the tables, it is difficult to see exactly where the problem is and what the exact solution is.

Why does the count function in SQL seem to be doing more than counting the column I ask it to?

I have an INSERT query that is pulling data from two tables and inserting that data into a third table. Everything appears to be working fine except that the COUNT part of the query isn't returning the results I would expect.
The first set of tables this query runs is MIUsInGrid1000 (number of rows = 1) and Results1000 (number of rows = 24). The number that is returned from the Count part of the query is 24 instead of being 1 like I would have expected.
The next set of tables is MIUsInGrid1000 (number of rows = 3) and Results1000 (number of rows = 30). The number that is returned from the Count part of the query is 90 instead of being 3 like I would have expected.
It appears that the product of the two counts is what is being returned to me and I can't figure out why that is. If I take out the references to the Results tables then the query works the way I would expect. I think that I am misunderstanding how at least some part of this works. Can someone explain why this is not working as I expected it to?
strQuery1 = "Insert Into MIUsInGridAvgs (NumberofMIUs, ProjRSSI, RealRSSI, CenterLat, CenterLong) " & _
"Select Count(MIUsInGrid" & i & ".MIUID), Avg(MIUsInGrid" & i & ".ProjRSSI), Avg(MIUsInGrid" & i & ".RealRSSI), Avg(Results" & i & ".Latitude), Avg(Results" & i & ".Longitude) " & _
"From MIUsInGrid" & i & ", Results" & i & " "
It seems logical to me that if you are joining two tables, one with 1 row and the other with 24 rows that there, is the possibility, of having a result set of 24 rows.
I notice you have not included a WHERE clause in your SQL (perhaps for brevity), but if you don't have that you are doing a CROSS JOIN (or cartesian join) between the tables and this will provide unexpected results.
The COUNT function will count all rows in the database, to determine how many "distinct" ID's there are, you can use the answer provided by Tomalak
This should solve your immediate problem
Count(DISTINCT MIUsInGrid" & i & ".MIUID)
The naked COUNT function counts the non-NULL values, not the distinct values, unless you tell it to switch behavior by using DISTINCT.
When two tables are joined like you do it (you build a cartesian product), then the number of resulting rows is the number of rows in the one times the number of rows in the other table.
This leads me to the suspicion that you are missing a join condition.
Apart from that i find it bewildering that you have a number of obviously identical tables that are indexed by name. This most certainly is a substantial design flaw in the database.
The way I usually figure these things out is to not use any aggregates first to see what my result sets will be. Then, I start adding in the aggregate functions.