Group records into non-contiguous hour-long bins - sql

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

Related

MS Access line chart Create Values for X-Axis

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.

how to get the last non empty column in ms access

so i have this huge database for my school project it goes like this
id
team
game1
score1
game2
score2
game3
score3
1
barca
vs real
2-1
vs bvb
5-2
vs atletic
0-3
2
real madrid
vs barca
1-2
vs betis
3-0
3
man city
vs man united
1-2
and i want to make a query that will give me only the last game of each team
in excel its easy but i cant do it in ms access
result that i need is
id
team
last game
1
barca
vs atletic
2
real madrid
vs betis
3
man city
vs man united
One thing that has been commented on is that your database design needs to be fixed - you don't go across (as you would in Excel), but down as extra rows in tables. There is a limit of 255 fields in an Access table that you need to be aware of.
However, if you decide to stick with this, a different way of approaching it would be to create a small VBA function that loops the fields backwards to get the answer that you need. Something like:
Function fLastMatch(lngTeamID As Long) As String
On Error GoTo E_Handle
Dim db As DAO.Database
Dim rs As DAO.Recordset
Dim strSQL As String
Dim lngLoop1 As Long
Set db = DBEngine(0)(0)
strSQL = "SELECT * FROM tblFootball WHERE id=" & lngTeamID
Set rs = db.OpenRecordset(strSQL)
If Not (rs.BOF And rs.EOF) Then
For lngLoop1 = rs.Fields.Count - 2 To 2 Step -2
If Not IsNull(rs.Fields(lngLoop1)) Then
fLastMatch = rs.Fields(lngLoop1)
Exit For
End If
Next lngLoop1
End If
fExit:
On Error Resume Next
rs.Close
Set rs = Nothing
Set db = Nothing
Exit Function
E_Handle:
MsgBox Err.Description & vbCrLf & vbCrLf & "fLastMatch", vbOKOnly + vbCritical, "Error: " & Err.Number
Resume fExit
End Function
This works because the last (nth) field in the recordset is position n-1 as recordsets are 0-indexed (the first field is in position 0, the second field is position 1.....). This means that the last match field is in position n-2. So we start at this position in the fields and check if there is data. If there is, we have found the last match played and can exit the loop. If there is no data, then we go back two fields, to the previous match, and then repeat the checks.
You can then use this function in a query to get the answers that you require:
SELECT id, fLastMatch(id) AS LastMatch
FROM tblFootball;
This approach means that you don't need to worry about how many matches are included and add the correct number of Nzs in if this changes over time (which is a bad idea in a database).
Regards,
Assuming the missing values are null, you can use nz():
select id, team,
nz(game3, nz(game2, game1)) as last_game
from t;
Note that this would be simpler in any other database. The standard SQL function coalesce() takes multiple arguments:
select id, team,
coalesce(game3, game2, game1) as last_game
from t;

Find minimum value of a series in a chart

I have a line chart in a sub-form that is loaded into a variety of reports, the chart has 7 series. I have been trying to alter the 'Y' axis minimum value on load, based on the minimum value of all the series but have been unable to find a way to access the series values. I am able to alter the minimum value by entering a value such as 'Me.LineChart.Axes(xlValue).MinimumScale = 30' but I need to change the value dynamically.
Any pointers on how to get to the values in the data series would be very much appreciated.
Open a recordset of an aggregate query that pulls Min and Max values for filtered dataset then reference fields of recordset.
Set rs = CurrentDb.OpenRecordset("SELECT LabNum, First(Blows) As B, Min(A) AS MinOfA, Min(W) AS MinOfW, Min(S) AS MinOfS, Min(F) AS MinOfF, Min(VMA) AS MinOfVMA, " & _
"Min(VTM) AS MinOfVTM, Min(VF) AS MinOfVF, Max(A) AS MaxOfA, Max(W) AS MaxOfW, Max(S) AS MaxOfS, Max(F) AS MaxOfF, " & _
"Max(VMA) AS MaxOfVMA, Max(VTM) AS MaxOfVTM, Max(VF) AS MaxOfVF FROM GraphBMD WHERE LabNum='" & strLabNum & "' GROUP BY LabNum;")
Or use DMin() domain aggregate function if need only one value.
intMinD = Nz(Int(DMin("D", "GraphProctor", "Source='Lab' AND LabNum='" & strLabNum & "'")), 0)

Error reading time cell in Excel using SQL (VBA)

I have an excel worksheet with 7 tables (each placed in a different sheet) and I built a routine to select through them, consolidating the data in a single sheet.
All tables have an HOUR column and I have two types of select: a full time range, where I select every row in each table, and a part-time range, where I select only the data between 6:00:00 and 23:00:00.
For i = 2 To Sheets.Count
...
strSQL = "SELECT [APPLICATIONNAME], [DAT], [TRANSACTION_NAME], SUM([TOT]), AVG([PERFORM]), SUM([ERRO]), AVG([TOTAL_TIME]), AVG([PERCENTUAL_AJUSTADO]), [HOUR], LEFT([HORA],2) " & _
" FROM [" & Sheets(ind).Name & "$] " & _
" WHERE (LEFT([HOUR],2) >= 6 AND LEFT([HOUR],2) <= 23)" & _
" GROUP BY [APPLICATIONNAME], [DAT], [HOUR], [TRANSACTION_NAME]" & _
" ORDER BY [DAT], [HOUR], [TRANSACTION_NAME]"
...
Next i
The problem is that it works fine only when I read the first three tables. From the fourth table on, it seems that Excel is converting the time cell to a numeric value and the data becomes incomplete.
I believe it's not a problem of cell formatting, because if I change the order of the sheets, the SELECT continues reading the first three tables perfectly.
I made it print the time read and the LEFT([hour],2). The results for the first three are, for example:
hour = 22:00:00
LEFT([hour],2) = 22
From the fourth table on, the results are (always):
hour = 8,3333333333333
LEFT([hour],2) = 8,
What may be happening? Is there another way to make it work for all sheets?
Thanks in advance.
Ricardo Freire

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