Access SQL Query to Count Unique Occurrences of One Field Matching Multiple Parameters/Rows, Some Identical - sql

Struggling with ms-access's flavor of SQL queries still, though I've made some progress (thanks to y'all). I have an event log table like this:
Logs Table
logID (auto#)
modID (str)
relID (str)
DateTime (date)
TxType (short)
1
1234
22.3
10/1/22 0800
6
2
1234
22.3
10/1/22 0900
7
3
1234
22.3
10/1/22 1000
13
4
1234
22.3
10/1/22 1100
15
5
4321
22.3
10/1/22 0830
1
6
4321
22.3
10/1/22 0930
13
7
4321
22.3
10/1/22 1030
15
8
4321
22.3
10/1/22 1130
13
9
1234
23.1
11/1/22 0800
1
10
1234
23.1
11/1/22 0900
15
11
1234
23.1
11/1/22 1000
13
12
1234
23.1
11/1/22 1100
15
13
4321
23.1
11/1/22 0830
13
14
4321
23.1
11/1/22 0930
7
15
4321
23.1
11/1/22 1030
13
16
4321
23.1
11/1/22 1130
15
What I need to do is:
filter the table by relID, then
count the number of modID's that have a 15 txType as the last/most recent chronological event in their rows.
So ideally I'd filter e.g. by relID=23.1 and get these results (but not logID # 10 for example) and then count them:
logID (auto#)
modID (str)
relID (str)
DateTime (date)
TxType (short)
12
1234
23.1
11/1/22 1100
15
16
4321
23.1
11/1/22 1130
15
As part of another function I have been able to count any modID's having a single txType successfully using
SELECT COUNT(*)
FROM (
SELECT DISTINCT Logs.modID, Logs.relID
FROM Logs
WHERE ((Logs.relID='23.1') AND ((Logs.TxType=13)))
);
Another stackoverflow user (exception - thanks!) showed me how to get the last event type for a given modID, relID combination using
SELECT TOP 1 TxType
FROM Logs
WHERE (((Logs.modID=[EnterModID])) AND ((Logs.relID=[EnterRelID])))
ORDER BY DateTime DESC;
But I'm having trouble combining these two. I know I can combine COUNT and GROUP BY but Access treats GROUP BY very particularly, and I'm not sure how to use SELECT TOP to get the latest events for each modID rather than just the latest events in the table, period.

This should give you the logID from the row with the latest DateTime for each combination of modIDand your target relID:
PARAMETERS which_relID Text(255);
SELECT DISTINCT
(
SELECT TOP 1 logID
FROM Logs
WHERE modID=l.modID AND relID=l.relID
ORDER BY [DateTime] DESC
) AS latest_modID
FROM Logs AS l
WHERE l.relID=[which_relID]
Use it as a subquery which you INNER JOIN to your Logs table. Note the subquery evaluates rows regardless of TxType. So have the parent query select only rows whose TxType = 15
PARAMETERS which_relID Text(255);
SELECT l2.*
FROM
Logs AS l2
INNER JOIN
(
SELECT DISTINCT
(
SELECT TOP 1 logID
FROM Logs
WHERE modID=l.modID AND relID=l.relID
ORDER BY [DateTime] DESC
) AS latest_modID
FROM Logs AS l
WHERE l.relID=[which_relID]
) AS sub
ON l2.logID=sub.latest_modID
WHERE l2.TxType=15;
Note I moved the PARAMETERS clause into the parent query. But you can eliminate it altogether if you believe it's causing trouble.
DateTime is a reserved word. I enclosed it in square brackets to ensure Access understands we mean the name of an object.
Using your sample data, I get these 2 rows when I supply 23.1 for the query parameter:
logID
modID
relID
DateTime
TxType
12
1234
23.1
11/1/2022 11:00:00 AM
15
16
4321
23.1
11/1/2022 11:30:00 AM
15
I get a single row with 22.3 for the parameter:
logID
modID
relID
DateTime
TxType
4
1234
22.3
10/1/2022 11:00:00 AM
15

Related

Access Query: Match Two FKs, Select Record with Max (Latest) Time, Return 3d Field From Record

I have an Access table (Logs) like this:
pk
modID
relID
DateTime
TxType
1
1234
22.3
10/1/22 04:00
1
2
1234
23.1
10/10/22 06:00
1
3
1234
23.1
10/11/22 07:00
2
4
1234
23.1
10/12/22 08:00
3
5
4321
22.3
10/2/22 06:00
7
6
4321
23.1
10/10/22 06:00
1
7
4321
23.1
10/11/22 07:30
3
Trying to write a query as part of a function that searches this table:
for all records matching a given modID and relID (e.g. 1234 and 23.1),
picks the most recent one (the MAX of DateTime),
returns the TxType for that record.
However, a bit new to Access and its query structure is vexing me. I landed on this but because I have to include a Total/Aggregate function for TxType I had to either choose Group By (not what I want) or Last (closer, but returns junk results). The SQL for my query is currently:
SELECT Last(Logs.TxType) AS LastOfTxType, Max(Logs.DateTime) AS MaxOfDateTime
FROM Logs
GROUP BY Logs.dmID, Logs.relID
HAVING (((Logs.dmID)=[EnterdmID]) AND ((Logs.relID)=[EnterrelID]));
It returns the TxType field when I pass it the right parameters, but not the correct record - I would like to be rid of the Last() bit but if I remove it Access complains that I don't have it as part of an aggregate function.
Anyone that can point me in the right direction here?
Have you tried
SELECT TOP 1 TxtType
FROM Logs
WHERE (((Logs.dmID)=[EnterdmID]) AND ((Logs.relID)=[EnterrelID]))
ORDER BY DateTime DESC;
That will give you the latest single data row based on your DateTime field and other criteria.

Selecting records from slowly changing table with a set of dates

I have a slowly changing table,a new row is created each time any of the source fields are changed. Some metadata is added to show when that version was valid. This is a simplified example(dates are dd/mm/yyyy format) that doesn't show the fields which have changed.
Startdate
Enddate
Currentrecord
unique id
serial_number
15/12/2020
31/12/2020
0
1
2345
15/12/2020
8/3/2021
0
2
1234
19/9/2020
15/2/2021
0
3
2345
15/12/2020
8/3/2021
0
4
3456
9/3/2021
10/3/2021
0
5
3456
16/2/2021
10/3/2021
0
6
2345
9/3/2021
26/3/2021
0
7
1234
27/3/2021
2/5/2021
0
8
1234
11/3/2021
17/5/2021
0
9
3456
3/3/2021
27/4/2021
0
10
4567
20/1/2021
7/4/2021
0
11
5678
3/5/2021
30/6/2021
1
12
1234
25/5/2021
31/5/2021
0
13
2345
8/4/2021
22/5/2021
0
14
5678
1/6/2021
26/6/2021
0
15
2345
18/5/2021
3/6/2021
0
16
3456
27/6/2021
2/8/2021
0
17
2345
28/4/2021
28/6/2021
0
18
4567
23/5/2021
6/9/2021
0
19
5678
4/6/2021
28/6/2021
0
20
3456
29/6/2021
25/7/2021
0
21
3456
3/8/2021
31/12/9999
1
22
2345
26/7/2021
31/12/9999
1
23
3456
15/10/2021
31/12/9999
1
24
4567
7/9/2021
1/11/2021
0
25
5678
22/9/2021
10/11/2021
0
26
6789
2/11/2021
16/11/2021
0
27
5678
17/11/2021
21/11/2021
0
28
5678
15/7/2021
31/12/9999
1
29
7891
22/11/2021
31/12/9999
1
30
5678
26/11/2021
31/12/9999
1
31
6789
15/6/2021
31/12/9999
1
32
8912
There is only one record for each serial_number for any given point in time (i.e. the dates ranges will not overlap for identical serial_numbers) but there might be gaps between episodes for a some serial_numbers (representing something leaving and returning after a gap in service).
I want to supply an arbitrary list of datetimes, say midnight on 01/01/2021, 15/03/2021, 27/05/2021. 23/10/2021. I want to return a set of records, containing every record which was in effect on each of the dates, with each row labelled with the date it was selected by. So the above example should return this.
date
unique id
serial_number
1/1/2021
2
1234
1/1/2021
3
2345
1/1/2021
4
3456
15/3/2021
7
1234
15/3/2021
9
3456
15/3/2021
10
4567
15/3/2021
11
5678
27/5/2021
12
1234
27/5/2021
13
2345
27/5/2021
16
3456
27/5/2021
18
4567
27/5/2021
19
5678
23/10/2021
22
2345
23/10/2021
23
3456
23/10/2021
24
4567
23/10/2021
25
5678
23/10/2021
26
6789
23/10/2021
29
7891
23/10/2021
32
8912
I can see how to do this with a cursor, stepping through each date putting them into a variable and using something like
select #date, [unique id], serial_number
from example
where #date between start_date and end_date
to get the rows.
I can’t work out a pattern that would do it in a set based approach. My preferred SQL version is TSQL. Sorry as this is almost certainly a repeat, but I can't find a form of words that hits a worked example.
You can use a temporary table to accomplish this.
CREATE TABLE #RequestedDates([Date] DATE)
You insert your dates you want into a temporary table.
INSERT INTO #RequestedDates([Date])
VALUES ('2021-01-01'), ('2021-03-15') /*Other dates*/
And then you join with the temporary table and use the between clause to get the valid results.
SELECT rd.[Date]
, t.UniqueId
, t.SerialNumber
FROM MyTable t
INNER JOIN #RequestedDates rd on rd.[Date] BETWEEN t.StartDate AND t.EndDate
ORDER BY rd.[Date]
, t.UniqueId
, t.SerialNumber
You can join to VALUES with the dates you need.
Then join the datetimes on the range.
SELECT
datetimes.dt as [date]
, t.[unique id]
, t.serial_number
FROM example t
JOIN (VALUES
(cast('2021-01-01 00:00:00' as datetime)),
('2021-03-15 00:00:00'),
('2021-05-27 00:00:00'),
('2021-10-23 00:00:00')
) datetimes(dt)
ON datetimes.dt >= t.start_date
AND datetimes.dt <= t.end_date
ORDER BY datetimes.dt, t.[unique id], t.serial_number

SQL how to count but only count one instance if two columns match?

Wondering how to select from a table:
FIELDID personID purchaseID dateofPurchase
--------------------------------------------------
2 13 147 2014-03-21 00:00:00
3 15 165 2015-03-23 00:00:00
4 13 456 2018-03-24 00:00:00
5 1 133 2018-03-21 00:00:00
6 23 123 2013-03-22 00:00:00
7 25 456 2013-03-21 00:00:00
8 25 456 2013-03-23 00:00:00
9 22 456 2013-03-28 00:00:00
10 25 589 2013-03-21 00:00:00
11 82 147 1991-10-22 00:00:00
12 82 453 2003-03-22 00:00:00
I'd like to get a result table of two columns: weekday and the number of purchases of each weekday, but only count the distinct days of purchases if done by the same person on the same day - for example since personID 25 purchased two things on 2013-03-21, that should only count as one 'thursday' instead of 2.
Basically, if the personID and the dateofPurchase are the same for more than one row, only count it once is what I want.
Here is what I have currently: It does everything correctly except it will count the above scenario under the thursday twice, when I would only want to add one:
SELECT v.wkday as day, COUNT(*) as 'absences'
FROM dbo.AttendanceRecord pr CROSS APPLY
(VALUES (CASE WHEN DATEPART(WEEKDAY, date) IN (1, 7)
THEN 'Weekend'
ELSE DATENAME(WEEKDAY, date)
END)
) v(wkday)
GROUP BY v.wkday;
to clarify:
If an item is purchased for at least one puchaseID on a specific day they will be counted as purchased for that day, and do not need to be counted again for each new purchase ID on that day.
I think you want to count distinct persons, so that would be:
COUNT(DISTINCT personid) as absences
Note that single quotes are not appropriate around column aliases. If you need to escape them, use square braces.
EDIT:
If you want to count distinct person-days, then you can use:
COUNT(DISTINCT CONCAT(personid, ':', dateofpurchase) as absences

How to write the query to make report by month in sql

I have the receiving and sending data for whole year. so i want to built the monthly report base on that data with the rule is Fisrt in first out. It means is the first receiving will be sent out first ...
DECLARE #ReceivingTbl AS TABLE(Id INT,ProId int, RecQty INT,ReceivingDate DateTime)
INSERT INTO #ReceivingTbl
VALUES (1,1001,210,'2019-03-12'),
(2,1001,315,'2019-06-15'),
(3,2001,500,'2019-04-01'),
(4,2001,10,'2019-06-15'),
(5,1001,105,'2019-07-10')
DECLARE #SendTbl AS TABLE(Id INT,ProId int, SentQty INT,SendMonth int)
INSERT INTO #SendTbl
VALUES (1,1001,50,3),
(2,1001,100,4),
(3,1001,80,5),
(4,1001,80,6),
(5,2001,200,6)
SELECT * FROM #ReceivingTbl ORDER BY ProId,ReceivingDate
SELECT * FROM #SendTbl ORDER BY ProId,SendMonth
Id ProId RecQty ReceivingDate
1 1001 210 2019-03-12
2 1001 315 2019-06-15
5 1001 105 2019-07-10
3 2001 500 2019-04-01
4 2001 10 2019-06-15
Id ProId SentQty SendMonth
1 1001 50 3
2 1001 100 4
3 1001 80 5
4 1001 80 6
5 2001 200 6
--- And the below is what i want:
Id ProId RecQty ReceivingDate ... Mar Apr May Jun
1 1001 210 2019-03-12 ... 50 100 60 0
2 1001 315 2019-06-15 ... 0 0 20 80
5 1001 105 2019-07-10 ... 0 0 0 0
3 2001 500 2019-04-01 ... 0 0 0 200
4 2001 10 2019-06-15 ... 0 0 0 0
Thanks!
Your question is not clear to me.
If you want to purely use the FIFO approach, therefore ignore any data the table contains, you necessarely need to order by ID, which in your example you are providing, and looks like it is in order of insert.
The first line inserted should be also the first line appearing in the select (FIFO), in order to do so you have to use:
ORDER BY Id ASC
Which will place the lower value of the ID first (1, 2, 3, ...)
To me though, this doesn't make much sense, so pay attention to the meaning o the data you actually have and leverage dates like ReceivingDate, and order by that, maybe even filtering by month of the date, below an example for January data:
WHERE MONTH(ReceivingDate) = 1

SQL query self join

I am working on a query for a report in Oracle 10g.
I need to generate a short list of each course along with the number of times they were offered in the past year (including ones that weren't actually offered).
I created one query
SELECT coursenumber, count(datestart) AS Offered
FROM class
WHERE datestart BETWEEN (sysdate-365) AND sysdate
GROUP BY coursenumber;
Which produces
COURSENUMBER OFFERED
---- ----------
ST03 2
PD01 1
AY03 2
TB01 4
This query is all correct. However ideally I want it to list those along with COURSENUMBER HY and CS in the left column as well with 0 or null as the OFFERED value. I have a feeling this involves a join of sorts, but so far what I have tried doesn't produce the classes with nothing offered.
The table normally looks like
REFERENCE_NO DATESTART TIME TIME EID ROOMID COURSENUMBER
------------ --------- ---- ---- ---------- ---------- ----
256 03-MAR-11 0930 1100 2 2 PD01
257 03-MAY-11 0930 1100 12 7 PD01
258 18-MAY-11 1230 0100 12 7 PD01
259 24-OCT-11 1930 2015 6 2 CS01
260 17-JUN-11 1130 1300 6 4 CS01
261 25-MAY-11 1900 2000 13 6 HY01
262 25-MAY-11 1900 2000 13 6 HY01
263 04-APR-11 0930 1100 13 5 ST03
264 13-SEP-11 1930 2100 6 4 ST03
265 05-NOV-11 1930 2100 6 5 ST03
266 04-FEB-11 1430 1600 6 5 ST03
267 02-JAN-11 0630 0700 13 1 TB01
268 01-FEB-11 0630 0700 13 1 TB01
269 01-MAR-11 0630 0700 13 1 TB01
270 01-APR-11 0630 0700 13 1 TB01
271 01-MAY-11 0630 0700 13 1 TB01
272 14-MAR-11 0830 0915 4 3 AY03
273 19-APR-11 0930 1015 4 3 AY03
274 17-JUN-11 0830 0915 14 3 AY03
275 14-AUG-09 0930 1015 14 3 AY03
276 03-MAY-09 0830 0915 14 3 AY03
SELECT
coursenumber,
COUNT(CASE WHEN datestart BETWEEN (sysdate-365) AND sysdate THEN 1 END) AS Offered
FROM class
GROUP BY coursenumber;
So, as you can see, this particular problem doesn't need a join.
I think something like this should work for you, by just doing it as a subquery.
SELECT distinct c.coursenumber,
(SELECT COUNT(*)
FROM class
WHERE class.coursenumber = c.coursenumber
AND datestart BETWEEN (sysdate-365) AND sysdate
) AS Offered
FROM class c
I like jschoen's answer better for this particular case (when you want one and only one row and column out of the subquery for each row of the main query), but just to demonstrate another way to do it:
select t1.coursenumber, nvl(t2.cnt,0)
from class t1 left outer join (
select coursenumber, count(*) cnt
from class
where datestart between (sysdate-365) AND sysdate
group by coursenumber
) t2 on t1.coursenumber = t2.coursenumber