MS Access line chart Create Values for X-Axis - sql

I have a table in MS Access that is structured like this (example)
part number
time of testing
cleanliness class A
cleanliness class B
B
2021-06-12 15:22:22.00
20
30
A
2021-06-14 13:04:22.00
400
50
A
2021-06-14 13:28:28.00
200
60
A
2021-06-14 14:17:5.00
300
11
B
2021-06-17 09:25:7.00
18
5
B
2021-06-17 09:37:7.00
21
17
A
2021-06-25 11:53:18.00
150
70
C
2021-06-26 18:01:01.00
210
30
As you can see, the cleanliness of different parts is tested in no particular order.
My goal is to create a line chart of the cleanliness of each cleanliness class that contains the part numbers I choose of a list that contains all the part numbers while also choosing a timeframe.
time of testing
At first I have two textboxes in which you can put Start and End dates. With the following code I do a query which selects only the rows between these dates.
Private Sub Befehl11_Click()
'Dates
Dim Anfang As Variant
Dim Ende As Variant
Text5.SetFocus
Anfang = Text5.Text
Text7.SetFocus
Ende = Text7.Text
Dim dbs As DAO.Database
Dim qdf As QueryDef
Dim strSQL As String
Set dbs = CurrentDb
Set qdf = dbs.CreateQueryDef("DatumGefiltert")
Application.RefreshDatabaseWindow
'SELECT-Statement bauen Build Select Statement
strSQL = "SELECT * FROM dbo_Cleanliness WHERE Format(dbo_Cleanliness.Date_of_Analysis,'yyyy-MM-dd hh:mm:ss') >= Format("""
strSQL = strSQL & Anfang & " 00:00:00"""
strSQL = strSQL & ",""yyyy-MM-dd hh:mm:ss"") AND Format(dbo_Cleanliness.Date_of_Analysis,'yyyy-MM-dd hh:mm:ss') <= Format("""
strSQL = strSQL & Ende & " 23:59:00"" , ""yyyy-MM-dd hh:mm:ss"")"
Text9.SetFocus
Text9.Text = strSQL
qdf.SQL = strSQL
End Sub
List
The next step is to create a list where a can choose several part numbers.
First I created a query which only selects the column "part number" with SELECT DISTINCT
| part number |
| :--|
|A |
|B |
|C|
Then I created a listbox in a form with that query as source and enabled multiselect
Listbox
With a button the following code is run to build and execute a query
Private Sub Befehl4_Click()
Dim ctlSource As Control
Dim strItems As String
Dim intCurrentRow As Integer
'My listbox
Set ctlSource = Liste2
'The objects of the WHERE clause
For intCurrentRow = 0 To ctlSource.ListCount - 1
If ctlSource.Selected(intCurrentRow) Then
strItems = strItems & " " & "Nummer = " & ctlSource.Column(0, intCurrentRow) & " Or "
End If
Next intCurrentRow
'Get rid of the last OR
strItems = Left(strItems, Len(strItems) - 4)
'Build the Query
Dim dbs As DAO.Database
Dim qdf As DAO.QueryDef
Dim strSQL As String
Set dbs = CurrentDb
Set qdf = dbs.CreateQueryDef("myQuery2")
Application.RefreshDatabaseWindow
strSQL = "SELECT * FROM DatumGefiltert "
strSQL = strSQL & "WHERE "
strSQL = strSQL & strItems
'Order By part number(Nummer) and time of testing(Datum)
strSQL = strSQL & " ORDER BY Nummer, Datum"
'RUN Query
qdf.SQL = strSQL
'CLEAR the variables
' qdf.Close
' Set qdf = Nothing
' Set dbs = Nothing
End Sub
If I select A and B in the listbox I want to get.
part number
time of testing
cleanliness class A
cleanliness class B
A
2021-06-14 13:04:22.00
400
50
A
2021-06-14 13:28:28.00
200
60
A
2021-06-14 14:17:5.00
300
11
A
2021-06-25 11:53:18.00
150
70
B
2021-06-12 15:22:22.00
20
30
B
2021-06-17 09:25:7.00
18
5
B
2021-06-17 09:37:7.00
21
17
Now I want to create a line chart for each of the cleanliness classes that contains both the part numbers.
Desired Chart
My problem now is:
If I would use the date of analysis as values for the x-Axis the lines wouldn´t be conected
My idea would be to add a new column with the amount of times the part has been tested.
part number
time of testing
cleanliness class A
cleanliness class B
Test number
A
2021-06-14 13:04:22.00
400
50
1
A
2021-06-14 13:28:28.00
200
60
2
A
2021-06-14 14:17:5.00
300
11
3
A
2021-06-25 11:53:18.00
150
70
4
B
2021-06-12 15:22:22.00
20
30
1
B
2021-06-17 09:25:7.00
18
5
2
B
2021-06-17 09:37:7.00
21
17
3
These values in the column test number could be used as the X-Axis.
But unfortunately I don´t know how to do this?
Or is there maybe a simpler way to achieve my Linechart overall?
Thanks in advance! If something is unclear please ask for clarification.
Greetings arijon

I have Access 2010 and can use only classic MSGraph, not ModernChart.
Following assumes there is a unique ID field (if there isn't, can easily add autonumber type) otherwise use [time of testing] field for unique identifier.
Build and save Query1 (replace Data with your table or query name):
SELECT ID, [part number], [time of testing], [cleanliness class A] AS Cleanliness,
"A" AS Class,
DCount("*","Data","[part number]='" & [part number] & "' AND ID<=" & [ID]) AS [Test Number]
FROM Data
UNION
SELECT ID, [part number], [time of testing], [cleanliness class B], "B",
DCount("*","Data","[part number]='" & [part number] & "' AND ID<=" & [ID])
FROM Data;
A correlated subquery can be used instead of DCount() to calculate group sequence number, review Access query counter per group
Build a form or report with RecordSource:
SELECT "A" AS Class FROM Data UNION SELECT "B" FROM Data;
Create a textbox named tbxClass and bind it to Class field. Label caption Cleanliness Class:
Create chart with RowSource:
PARAMETERS [tbxClass] Text ( 255 );
TRANSFORM Sum(Cleanliness) AS Clean
SELECT [Test Number] FROM Query1
WHERE [Class]=[tbxClass]
GROUP BY [Test Number]
PIVOT [part number];
I wasn't able to set form for ContinuousView (error "can't view a form as continuous if it contains ... a bound chart ...") - never encountered that before but never used a CROSSTAB query with dynamic parameter as RowSource. If you want the two graphs viewed at same time, then instead of dynamic parameter in WHERE clause, build two graph objects and use static criteria for Class field WHERE Class="A".
However, dynamic parameterized CROSSTAB RowSource works just fine for a report.
Strongly advise not to use spaces nor punctuation/special characters in naming convention, better would be: TimeOfTesting or TestTime.

Related

Create new rows of data based on range of dates in another table

I have a table that contains a start date, an end date, and number of hours. I would like to create rows of data in a new table based on these values.
ID
StartDate
EndDate
Hours
1
3/2022
6/2022
40
2
4/2022
4/2023
144
What I would like do is create a table that evenly splits the hours over the date range.
ForeignKey
MonthYear
Hours
1
3/2022
10
1
4/2022
10
1
5/2022
10
1
6/2022
10
2
4/2022
12
2
5/2022
12
2
6/2022
12
2
7/2022
12
2
8/2022
12
2
9/2022
12
2
10/2022
12
2
11/2022
12
2
12/2022
12
2
1/2023
12
2
2/2023
12
2
3/2023
12
2
4/2023
12
I have seen examples on how to do this using CTEs but they are not available in MS Access. I am trying to find a way to do this without VBA if possible. If necessary I will resort to VBA.
Here is VBA to get you started. It uses table with date/time type fields holding a full date value:
Sub ExpandData()
Dim db As DAO.Database, rs As DAO.Recordset
Dim dteStartDate As Date, dteEndDate As Date, x As Integer, intMonths As Integer
Set db = CurrentDb
Set rs = db.OpenRecordset("SELECT ID, StartDate, EndDate, DateDiff('m',[StartDate],[EndDate])+1 AS Months, Hours FROM Data;")
db.Execute "DELETE FROM DataExpanded"
Do While Not rs.EOF
For x = 0 To rs!Months - 1
db.Execute "INSERT INTO DataExpanded(ForeignKey, MonthDayYear, Hours) " & _
"VALUES(" & rs!ID & "," & DateAdd("m", x, rs!startDate) & "," & rs!Hours / rs!Months & ")"
Next
rs.MoveNext
Loop
End Sub
You can do it with SQL using a Cartesian (multiplying) query (where Hours is your source table):
SELECT DISTINCT
Hours.ID,
DateAdd("m",10*Abs([Deca].[id] Mod 10)+Abs([Uno].[id] Mod 10),[StartDate]) AS MonthYear,
[Hours]/(1+DateDiff("m",[StartDate],[EndDate])) AS HoursPerMonth
FROM
MSysObjects AS Uno,
MSysObjects AS Deca,
Hours
WHERE
DateAdd("m",10*Abs([Deca].[id] Mod 10)+Abs([Uno].[id] Mod 10),[StartDate])<=[EndDate];
The output will be different from your expected output, as you for ID 2 think you have 12 months, but do have 13:

How to run a double loop in Access VBA

i'm learning Access by few days and i'm having some problems running into a double loop. I'm doing something wrong because i'm not expert with Access. It seems i'm stuck in an infinite loop (or some other error). My goal is to retrieve some data from the same table. Here's an example:
Table1
Field1 Field2
AAA 1
BBB 2
CCC 3
CCC 4
AAA 5
BBB 6
i want to retrieve all the records in field 2 where Field1= x (but not repeating the action if the same record is found)
Here's my code:
Dim strSQL as String, rs as DAO.Recordset, rs2 as Dao.Recordset, result as String
strSQL = "SELECT * FROM Table1"
strSQL2 = "SELECT DISTINCT Field1 FROM Table1"
Set rs = Currentdb.OpenRecordset(strSQL)
Set rs2 = Currentdb.OpenRecordset(strSQL2)
rs2.movefirst
While not rs2.EOF
rs.movefirst
result = ""
While not rs.EOF
If rs.Fields("Field1") = rs2.Fields("Field1") Then
result = result & rs.fields("Field2") & " "
rs.movenext
End if
Wend
rs2.movenext
Debug.print result
Wend
Set rs=Nothing
Set rs2 = Nothing
The result i want should be:
Result(first loop) = 1 5
Result(second loop) = 2 6
Result(third loop) = 3 4
EDIT:
i'm running loops because i need to generate emails with the data i found. I know how to generate emails but i'm only stacked with this loop. An example of emails should be like this:
number of emails = number of unique values in [table1].[field1] (so 3 in the before mentioned example)
for each email a list of all records in field2 that has the value-in-loop in field1.
Assuming Field2 actually contains unique number values, consider:
TRANSFORM Max(Table1.Field2) AS MaxOfField2
SELECT Table1.Field1
FROM Table1
GROUP BY Table1.Field1
PIVOT DCount("*","Table1","Field1='" & Field1 & "' AND Field2<" & Field2)+1;
However, if there are too many values for a CROSSTAB to handle or if you really need a single string of values from Field2 for each value in Field1, use VBA to concatenate data. One version http://allenbrowne.com/func-concat.html
Another option using the getstring method of an ado recordset.
a) Paste into a new query in sql view and save: select field1,field2list(field1) from table1
b) In a vba module, paste field2list function:
Function field2list(field1) As String
Dim rs As New adodb.Recordset
Dim ColumnSeparator
Dim RowSeparator
Dim sql
ColumnSeparator = ""
RowSeparator = ","
sql = "select field2 from table1 where field1='" & field1 & "'"
Set rs = CurrentProject.Connection.Execute(sql)
If rs.RecordCount > 0 Then
field2list = rs.GetString(, -1, ColumnSeparator, RowSeparator)
End If
End Function
c) Open query created in step a.

While Loop in VBA Access

I have delete and append functions that build Table1 based on inputs from the user. Therefore Table1 has a different number of records appending to it for every user.
My SQL code works to find the dates, but it only does it once, I need to loop the SQL code for the length of the table. I'm not great at coding, I tried a while statement, not sure if I can use variable Z in the criteria for that, but I want it to run until the due_date in the record with the smallest ID value has been filled.
Here's what I tried:
Private Sub Command7_Click()
Y = DMax("ID", "Table1", BuildCriteria("Due_date", dbDate, "Null"))
A = DMin("ID", "Table1", BuildCriteria("Due_date", dbDate, "Not Null"))
X = DMin("ID", "Table1")
Z = DLookup("Due_date", "Table1", BuildCriteria("ID", dbLong, CStr(X)))
B = DLookup("Duration", "Table1", BuildCriteria("ID", dbLong, CStr(Y)))
C = DLookup("Due_date", "Table1", BuildCriteria("ID", dbLong, CStr(A)))
E = DateAdd("d", -B, C)
Dim SQL As String
SQL = "UPDATE Table1 " & _
"SET " & BuildCriteria("Due_date", dbDate, CStr(E)) & " " & _
"WHERE " & BuildCriteria("ID", dbLong, CStr(Y))
While Z Is Null
DoCmd.RunSQL SQL
End While
End Sub
To illustrate:
Before Running SQL
After running SQL once
After clicking several times
The goal would be to click once and the whole table fills
Your variable Z contains the result returned by the DLookup function when evaluated as the fourth line of the definition of your sub Command7_Click; the value of this variable will not change unless the variable is redefined.
The intent of your code is somewhat obscured by the use of your BuildCriteria function, so it is difficult to advise the best way to write the code...
Edit: BuildCriteria is a new one for me - thanks to #Andre for pointing this out.
Since the content of your SQL statement is static, there should be no need for a loop, as nothing is changing within the loop - the SQL statement will update all records which meet your criteria and will do nothing for every subsequent iteration (unless, that is, the value to which you are updating the records also fulfils the selection criteria).
EDIT
Based on your additional explanations & screenshots, you could approach the task by iterating over a recordset sorted by your ID field and successively calculating the appropriate Due_date for each record - something like:
Private Sub Command7_Click()
Dim dbs As DAO.Database
Dim rst As DAO.Recordset
Dim dat As Date
Set dbs = CurrentDb
Set rst = dbs.OpenRecordset("select * from Table1 order by ID desc")
With rst
If Not .EOF Then
.MoveFirst
Do Until .EOF
If Not IsNull(!Due_date) Then
dat = !Due_date
Else
dat = DateAdd("d", -!Duration, dat)
.Edit
!Due_date = dat
.Update
End If
.MoveNext
Loop
End If
.Close
End With
Set rst = Nothing
Set dbs = Nothing
End Sub
Though based on your screenshots, it seems that you are trying to use Access like an Excel spreadsheet.
Consider actually no For loop, no BuildCriteria and even no VBA SQL. Save the update query as an MS Access action query object that is run on button click.
Specifically, you would need several domain functions --DLookUp, DSum, and DMax-- where you calculate a running sum of duration days (i.e., a correlated aggregate computation) and then DateAdd the result to the DueDate of the corresponding maximum ID with no missing DueDate.
SQL
UPDATE myTable d
SET d.DueDate = DateAdd("d", -1 * DSum("Duration", "DueDateDuration", "ID >= " & d.ID),
DLookUp("DueDate", "DueDateDuration", "ID = " &
DMax("ID", "DueDateDuration", "DueDate IS NOT NULL")
)
)
WHERE d.DueDate IS NULL;
VBA
Private Sub Command7_Click()
DoCmd.OpenQuery "mySavedUpdateQuery" ' WITH WARNINGS
CurrentDb.Execute "mySavedUpdateQuery" ' WITHOUT WARNINGS
End Sub
To demonstrate on sample data:
Before Update (mytable)
ID Item Duration DueDate
2674 Issue 1 2/18/2019
2675 Shipping 1 2/19/2019
2678 Completed 0 2/20/2019
2679 Issue 1
2680 Shipping 10
2681 Other 6
2682 Buy Off 6
2683 Punch List 3
2684 Completed 0 3/29/2019
After Update (mytable)
ID Item Duration DueDate
2674 Issue 1 2/18/2019
2675 Shipping 1 2/19/2019
2678 Completed 0 2/20/2019
2679 Issue 1 3/3/2019
2680 Shipping 10 3/4/2019
2681 Other 6 3/14/2019
2682 Buy Off 6 3/20/2019
2683 Punch List 3 3/26/2019
2684 Completed 0 3/29/2019

Excel OLE DB connection refresh issues

I have an Excel workbook with two dynamic OLE DB queries. I'm having issues with refreshes.
To set things up I have a SQL table-valued function as the source. The data is of the nature of...
SGrp SG_Desc SKU SKU_Desc Server Billed
1 Item 1 111 whatever 15 12
1 Item 2 222 some more 10 9
2 Item 3 333 zzz 10 8
3 Item 4 555 abc 20 18
On the first sheet ("Overall") I have a data connection that summarizes the groups with the command text of dynamically modified with one button.
SELECT SGrp, SG_Desc, SUM(Served) AS Served, SUM(Billed) AS Billed FROM mySQLdb ('8/19/2018','8/25/2018') WHERE SGrp <> '' GROUP BY SGrp, SG_Desc ORDER BY SG_Desc
I then have a cell with a data validation list that selects the group and a button to execute the VBA to dynamically modify the other connection. There's also two cells with the report start & end date for filtering. When I push the "Detail" button it runs the code below.
Private Sub RunDetail_Click()
Dim StartDate As Date
Dim EndDate As Date
Dim SGrp As String
Range("A1").Value = Range("G8").Value2
StartDate = Sheets("Overall").Range("H1").Value
EndDate = Sheets("Overall").Range("H2").Value
SGrp = Sheets("Overall").Range("A1").Value
SGrp = LTrim(RTrim(SGrp))
With ActiveWorkbook.Connections("CJP_DeliveryRecap_Detail").OLEDBConnection
.CommandText = "SELECT SKU, SKU_Desc, Served, Billed FROM mySQLdb ('" & StartDate & "','" & EndDate & "') WHERE SG_Desc='" & SGrp & "'"
' .Refresh
' ActiveWorkbook.Connections("CJP_DeliveryRecap_Detail").Refresh
End With
'RefreshOLEDB
'ThisWorkbook.RefreshAll
ActiveWorkbook.Connections("CJP_DeliveryRecap_Detail").Refresh
'Application.CalculateUntilAsyncQueriesDone
Application.Wait (Now + TimeValue("0:00:03"))
Dim rc As Integer
Dim i As Integer
Worksheets("Detail").Activate
With Worksheets("Detail").Range("CJP_DeliveryRecap_Detail")
rc = .Rows.Count
End With
With Worksheets("Detail").Range("E1048576")
.Select
.End(xlUp).Select
End With
i = Selection.Row
Worksheets("Detail").Range("E5").Select
Worksheets("Detail").Range("E5:G" & i).ClearContents
Worksheets("Detail").Range("E5").Value = 1
Worksheets("Detail").Range("F5").Value = "=+CJP_DeliveryRecap_Detail[#Served]*E5"
Worksheets("Detail").Range("G5").Value = "=+CJP_DeliveryRecap_Detail[#Billed]*E5"
Sheets("Detail").Range("E5:G5").Copy Sheets("Detail").Range("E6:E" & rc + 4)
Sheets("Detail").Range("F1").Value = ("=SUM(F5:F" & rc + 4 & ")")
Sheets("Detail").Range("G1").Value = ("=SUM(G5:G" & rc + 4 & ")")
End Sub
So, what is it doing? The first "strange" thing is I occasionally get errors in the code that I have to continue on. A lot of times, but not all, it hits the Application.Wait line, then the Worksheets("Detail").Activate line, and sometimes the line where I set values or copy data.
There are some comments where I've been testing various refreshes etc. The issue is because while when the code completes it displays the data conn detail correctly but the calculations as to the size of the results is from the prior set. If I click the button a second time then it calculates them correctly. I would of course prefer not to have a arbitrary 3sec delay but simply run the rest of the code after the resultant records have been retrieved.
Where am I going wrong as I've been banging my head against the wall with this. Most of what I do is in Access but Excel was the proper tool in this case.
I just thought of, but not thrilled to, would it work to have a separate sub to handle the record size calcs and cell values clear\set and call that from the button click or would that still not be generated until after the original sub ends.
Thanks in advance,
You need to wait till query is complete before proceeding with calculations.
What you need is to add .BackgroundQuery = False to your connection before .refresh
With ActiveWorkbook.Connections("CJP_DeliveryRecap_Detail").OLEDBConnection
.BackgroundQuery = False
.CommandText = "SELECT SKU, SKU_Desc, Served, Billed FROM mySQLdb ......"
.Refresh
End With
This should help
If the stored procedure being called from Excel does not contain "SET NOCOUNT ON" it will not run properly from Excel. I had the same issue and SET NOCOUNT ON was not included in my stored proc; the second i added it, it worked!

Group records into non-contiguous hour-long bins

I have event data of the form, where eventId is a long and time is Date/Time (shown below as just the time for simplicity, since all times will be on the same day):
eventId time
---------------
1 0712
2 0715
3 0801
4 0817
5 0916
6 1214
7 2255
I need to form groups containing an hour's worth of events, where the hour is measured from the first time in that group. Using the above data my groupings would be
Group 1 (0712 to 0811 inclusive): events 1, 2, 3
Group 2 (0817 to 0916 inclusive): events 4, 5
Group 3 (1214 to 1313 inclusive): event 6
Group 4 (2255 to 2354 inclusive): event 7
I've found plenty of examples of grouping data on predefined periods (e.g. every hour, day, 5 minutes) but nothing like what I'm trying to do. I suspect that it's not possible using straight SQL...it seems to be a chicken and egg problem where I need to put data into bins based on the data itself.
The cornerstone of this problem is coming up with the start time of each range but I can't come up with anything beyond the trivial case: the first start time.
The only way I can come up with is to do this programmatically (in VBA), where I SELECT the data into a temporary table and remove rows as I put them into bins. In other words, find the earliest time then grab that and all records within 1 hour of that, removing them from the table. Get the earliest time of the remaining records and repeat until the temp table is empty.
Ideally I'd like a SQL solution but I'm unable to come up with anything close on my own.
Some notes on a possible approach.
Dim rs As DAO.Recordset
Dim db As Database
Dim rsBins As DAO.Recordset
Dim qdf As QueryDef 'Demo
Set db = CurrentDb
'For demonstration, if the code is to be run frequently
'just empty the bins table
db.Execute "DROP TABLE Bins"
db.Execute "CREATE TABLE Bins (ID Counter, Bin1 Datetime, Bin2 Datetime)"
'Get min start times
db.Execute "INSERT INTO bins ( Bin1, Bin2 ) " _
& "SELECT Min([time]) AS Bin1, DateAdd('n',59,Min([time])) AS Bin2 " _
& "FROM events"
Set rsBins = db.OpenRecordset("Bins")
Do While True
Set rs = db.OpenRecordset("SELECT Min([time]) AS Bin1, " _
& "DateAdd('n',59,Min([time])) AS Bin2 FROM events " _
& "WHERE [time] > (SELECT Max(Bin2) FROM Bins)")
If IsNull(rs!Bin1) Then
Exit Do
Else
rsBins.AddNew
rsBins!Bin1 = rs!Bin1
rsBins!bin2 = rs!bin2
rsBins.Update
End If
Loop
''Demonstration of final query.
''This will only run once and then error becaue the query exists, but that is
''all you need after that, just open the now existing binned query
sSQL = "SELECT events.Time, bins.ID, bins.Bin1, bins.Bin2 " _
& "FROM events, bins " _
& "GROUP BY events.Time, bins.ID, bins.Bin1, bins.Bin2 " _
& "HAVING (((events.Time) Between [bin1] And [bin2]));"
Set qdf = db.CreateQueryDef("Binned", sSQL)
DoCmd.OpenQuery qdf.Name