SQL/MS Access - Finding on/off peak days in data set - ms-access

I have a data set that looks like the following (but obviously much larger):
Date ------------------ Customer ---------- Output
1/01/2016 01:00 ---------- 4 ---------------- 5
1/01/2016 03:00 ---------- 5 ---------------- 20
1/02/2016 09:00 ---------- 4 ---------------- 10
1/04/2016 06:00 ---------- 5 ---------------- 5
1/06/2016 13:00 ---------- 4 ---------------- 10
1/11/2016 23:00 ---------- 5 ---------------- 25
2/10/2016 12:00 ---------- 5 ---------------- 15
2/10/2016 24:00 ---------- 4 ---------------- 25
2/14/2016 16:00 ---------- 4 ---------------- 10
2/16/2016 04:00 ---------- 5 ---------------- 5
2/14/2016 16:00 ---------- 5 ---------------- 10
2/16/2016 04:00 ---------- 4 ---------------- 5
2/18/2016 16:00 ---------- 5 ---------------- 10
2/19/2016 12:00 ---------- 4 ---------------- 5
I need a query that does the following:
1) is able to tell whether the date is on or off-peak, meaning if it is a holiday OR if it falls on the week or weekday
2) if it falls on a weekday, must be able to further break-down on/off peak by hours (1-7 and 24 are off-peak and 8-23 are on-peak)
3) be able to pivot all of that together by month and by average
Basically it would look like this:
Month ----- Cust ------- Year --------- Avg.On-peak ----------- Avg.Off-peak
1 ----------- 4 ------------- 2016 -------------- 10 -------------------------- 7.5
1 ----------- 5 ------------- 2016 -------------- 25 ------------------------ 12.5
2 ----------- 4 ------------- 2016 -------------- 15 -------------------------- 7.5
2 ----------- 5 ------------- 2016 -------------- 12.5 ---------------------- 12.5
I have a linked table to Access from Excel. The data is downloaded from an online database and is stored in excel (but if better to store in Access LMK)
I have tried a switch statement:
For reference [Hrly_Daily_Monthly 2] is a table that just dateparts the datetime_beginning_ept into month, hour, year, etc.
[Off-peak days] is a table of all holidays
Switch(
[Hrly_Daily_Monthly 2].Date=#1/1/2016#, "off-peak day",
[Hrly_Daily_Monthly 2].Date=#5/30/2016# , "off-peak day",
[Hrly_Daily_Monthly 2].Date=#7/4/2016# , "off-peak day",
[Hrly_Daily_Monthly 2].Date=#9/5/2016# , "off-peak day",
[Hrly_Daily_Monthly 2].Date=#11/24/2016# , "off-peak day",
[Hrly_Daily_Monthly 2].Day = 1, "off-peak day",
[Hrly_Daily_Monthly 2].Day = 7, "off-peak day",
TRUE, "on-peak day") AS [On/Off Day]
Get an error because of too many statements.
I also tried creating multiple queries, rather than doing in one, and using the Make Table, to then put all queries together. And it severely slowed the database.
Off-peak day query AKA [off-peak days 2] table
SELECT [Hrly_Daily_Monthly 2].datetime_beginning_ept, [Hrly_Daily_Monthly 2].output, [Hrly_Daily_Monthly 2].cust, [Hrly_Daily_Monthly 2].Date
FROM [Hrly_Daily_Monthly 2], [Off peak list]
WHERE ([Hrly_Daily_Monthly 2].datetime_beginning_ept=[Off-peak days] OR [Day] NOT BETWEEN 2 and 6);
On-peak day query (which is dependent on the off-peak query)
SELECT [Hrly_Daily_Monthly 2].Date, [Hrly_Daily_Monthly 2].cust, [Hrly_Daily_Monthly 2].output,
FROM [Hrly_Daily_Monthly 2]
WHERE NOT EXISTS (Select 1 FROM [Off-Peak Days 2] WHERE [Off-Peak Days 2].Date = [Hrly_Daily_Monthly 2].Date AND [Hrly_Daily_Monthly 2].Date = [Off-Peak Days 2].Date);
any help is much appreciated

Be aware that midnight is 0 hour in an Access date/time field.
Consider this query that associates data table with OffPeakDays and calculates date parts and off peak factor:
SELECT datetime_beginning_ept, Customer, Output, Dt, Hr, Wd, Mo, Yr, OffPeakDays.HolDate,
IIf([Dt]=[HolDate] Or [Wd]<3 Or [Hr]<8, False, True) AS OnPeak
FROM OffPeakDays
RIGHT JOIN
(SELECT datetime_beginning_ept, Customer, Output, DateValue([datetime_beginning_ept]) AS Dt,
Hour([datetime_beginning_ept]) AS Hr, WeekDay([datetime_beginning_ept],7) AS Wd,
Month([datetime_beginning_ept]) AS Mo, Year([datetime_beginning_ept]) AS Yr
FROM DataTable) AS [Hrly_Daily_Monthly 2]
ON OffPeakDays.HolDate = [Hrly_Daily_Monthly 2].Dt;
Now use that query in other queries to do aggregate calcs or use it as RecordSource for report and use report Sorting & Grouping features with aggregate calcs in textboxes. Report allows display of detail data as well as summary calcs.
One possible query based on the above:
SELECT qryDateCalcs.Mo, qryDateCalcs.Customer, qryDateCalcs.Yr,
Avg(IIf([OnPeak],[Output],Null)) AS AvgOnPeak,
Avg(IIf(Not [OnPeak],[Output],Null)) AS AvgOffPeak
FROM qryDateCalcs
GROUP BY qryDateCalcs.Mo, qryDateCalcs.Customer, qryDateCalcs.Yr;

Related

Calculate total scheduled against total actual in two separate tables

I have two tables in my schema. The first contains a list of recurring appointments - default_appointments. The second table is actual_appointments - these can be generated from the defaults or individually created so not linked to any default entry.
Example:
default_appointments
id
day_of_week
user_id
appointment_start_time
appointment_end_time
1
1
1
10:00:00
16:00:00
2
4
1
11:30:00
17:30:00
3
6
5
09:00:00
17:00:00
actual_appointments
id
default_appointment_id
user_id
appointment_start
appointment_end
1
1
1
2021-09-13 10:00:00
2021-09-13 16:00:00
2
NULL
1
2021-09-13 11:30:00
2021-09-13 13:30:00
3
6
5
2021-09-18 09:00:00
2021-09-18 17:00:00
I'm looking to calculate the total minutes that were scheduled in against the total that were actually created/generated. So ultimately I'd end up with a query result with this data:
user_id
appointment_date
total_planned_minutes
total_actual_minutes
1
2021-09-13
360
480
1
2021-09-16
360
0
5
2021-09-18
480
480
What would be the best approach here? Hopefully the above makes sense.
Edit
OK so the default_appointments table contains all appointments that are "standard" and are automatically generated. These are what appointments "should" happen every week. So e.g. ID 1, this appointment should occur between 10am and 4pm every Monday. ID 2 should occur between 11:30am an 5:30pm every Thursday.
The actual_appointments table contains a list of all of the appointments which did actually occur. Basically what happens is a default_appointment will automatically generate itself an instance in the actual_appointments table when initially set up. The corresponding default_appointment_id indicates that it links to a default and has not been changed - therefore the times on both will remain the same. The user is free to change these appointments that have been generated by a default, resulting in setting the default_appointment_id to NULL * - or -* can add new appointments unrelated to a default.
So, if on a Monday (day_of_week = 1) I should normally have a default appointment at 10am - 4pm, the total minutes I should have planned based on the defaults are 360 minutes, regardless of what's in the actual_appointments table, I should be planned for those 360 minutes every Monday without fail. If in the system I say - well actually, I didn't have an appointment from 10am - 4pm and instead change it to 10am - 2pm, actual_appointments table will then contain the actual time for the day, and the actual minutes appointed would be 240 minutes.
What I need is to group each of these by the date and user to understand how much time the user had planned for appointments in the default_appointments table vs how much they actually appointed.
Adjusted based on new detail in the question.
Note: I used day_of_week values compatible with default MySQL behavior, where Monday = 2.
The first CTE term (args) provides the search parameters, start date and number of days. The second CTE term (drange) calculates the dates in the range to allow generation of the scheduled appointments within that range.
allrows combines the scheduled and actual appointments via UNION to prepare for aggregation. There are other ways to set this up.
Finally, we aggregate the results per user_id and date.
The test case:
Working Test Case (Updated)
WITH RECURSIVE args (startdate, days) AS (
SELECT DATE('2021-09-13'), 7
)
, drange (adate, days) AS (
SELECT startdate, days-1 FROM args UNION ALL
SELECT adate + INTERVAL '1' DAY, days-1 FROM drange WHERE days > 0
)
, allrows AS (
SELECT da.user_id
, dr.adate
, ROUND(TIME_TO_SEC(TIMEDIFF(da.appointment_end_time, da.appointment_start_time))/60, 0) AS planned
, 0 AS actual
FROM drange AS dr
JOIN default_appointments AS da
ON da.day_of_week = dayofweek(adate)
UNION
SELECT user_id
, DATE(appointment_start) AS xdate
, 0 AS planned
, TIMESTAMPDIFF(MINUTE, appointment_start, appointment_end)
FROM drange AS dr
JOIN actual_appointments aa
ON DATE(appointment_start) = dr.adate
)
SELECT user_id, adate
, SUM(planned) AS planned
, SUM(actual) AS actual
FROM allrows
GROUP BY adate, user_id
;
Result:
+---------+------------+---------+--------+
| user_id | adate | planned | actual |
+---------+------------+---------+--------+
| 1 | 2021-09-13 | 360 | 480 |
| 1 | 2021-09-16 | 360 | 0 |
| 5 | 2021-09-18 | 480 | 480 |
+---------+------------+---------+--------+

MYSQL How to perform custom month difference between two dates in MYSQL?

My requirement is to compute the total months and then broken months separately between 2 dates (ie first date from table and second date is current date). If broken months total count is > 15 then account it as one month experience and if its les than 15 don't account that as 1 month experience.
Assume I have a date on table as 25/11/2018 and current date is 06/01/2019;
the full month in between is December, so 1 month experience; and broken months are November and January, so now I have to count the dates which is 6 days in Nov and 6 days in Jan, so 12 days and is <= (lte) 15 so total experience will be rounded to 1 month experience
I referred multiple questions related to calculating date difference in MYSQL from stackoverflow, but couldn't find any possible options. The inbuilt functions in MYSQL TIMESTAMPDIFF, TIMEDIFF, PERIOD_DIFF, DATE_DIFF are not giving my required result as their alogrithms are different from my calculation requirement.
Any clue on how to perform this calculation in MYSQL and arrive its result as part of the SQL statement will be helpful to me. Once this value is arrived, in the same SQL, that value will be validated to be within a given value range.
Including sample table structure & value:
table_name = "user"
id | name | join_date
---------------------
1| Sam | 25-11-2017
2| Moe | 03-04-2017
3| Tim | 04-07-2018
4| Sal | 30-01-2017
5| Joe | 13-08-2018
I wanted to find out the users from above table whose experience is calculated in months based on the aforementioned logic. If those months are between either of following ranges, then those users are fetched for further processing.
table_name: "allowed_exp_range"
starting_exp_months | end_exp_months
-------------------------------------
0 | 6
9 | 24
For ex: Sam's experience till date (10-12-2018) based on my calculation is 12+1 month = 13 months. Since 13 is between 9 & 24, Sam's record is one of the expected output.
I think this query will do what you want. It uses
(YEAR(CURDATE())*12+MONTH(CURDATE()))
- (YEAR(STR_TO_DATE(join_date, '%d-%m-%Y'))*12+MONTH(STR_TO_DATE(join_date, '%d-%m-%Y'))) -
- 1
to get the number of whole months of experience for the user,
DAY(LAST_DAY(STR_TO_DATE(join_date, '%d-%m-%Y')))
- DAY(STR_TO_DATE(join_date, '%d-%m-%Y'))
+ 1
to get the number of days in the first month, and
DAY(CURDATE())
to get the number of days in the current month. The two day counts are summed and if the total is > 15, 1 is added to the number of whole months e.g.
SELECT id
, name
, (YEAR(CURDATE())*12+MONTH(CURDATE())) - (YEAR(STR_TO_DATE(join_date, '%d-%m-%Y'))*12+MONTH(STR_TO_DATE(join_date, '%d-%m-%Y'))) - 1 -- whole months
+ CASE WHEN DAY(LAST_DAY(STR_TO_DATE(join_date, '%d-%m-%Y'))) - DAY(STR_TO_DATE(join_date, '%d-%m-%Y')) + 1 + DAY(CURDATE()) > 15 THEN 1 ELSE 0 END -- broken month
AS months
FROM user
We can use this expression as a JOIN condition between user and allowed_exp_range to find all users who have experience within a given range:
SELECT u.id
, u.name
, a.starting_exp_months
, a.end_exp_months
FROM user u
JOIN allowed_exp_range a
ON (YEAR(CURDATE())*12+MONTH(CURDATE())) - (YEAR(STR_TO_DATE(u.join_date, '%d-%m-%Y'))*12+MONTH(STR_TO_DATE(u.join_date, '%d-%m-%Y'))) - 1
+ CASE WHEN DAY(LAST_DAY(STR_TO_DATE(u.join_date, '%d-%m-%Y'))) - DAY(STR_TO_DATE(u.join_date, '%d-%m-%Y')) + 1 + DAY(CURDATE()) > 15 THEN 1 ELSE 0 END
BETWEEN a.starting_exp_months AND a.end_exp_months
Output (for your sample data, includes all users as they all fit into one of the experience ranges):
id name starting_exp_months end_exp_months
1 Sam 9 24
2 Moe 9 24
3 Tim 0 6
4 Sal 9 24
5 Joe 0 6
I've created a small demo on dbfiddle which demonstrates the steps in arriving at the result.

Query event durations

Assume I have a notes table which contains (amongst other things) an entry which indicates when an employee starts/ends work on a project; there will only be one note that indicates the "start", but there could a number of different entries to indicate the "end" (we would ignore any extra "end" dates"). There will be times that have multiple people working on the project, as well as times when no-one is working on the project.
I need to query the table, to establish the number of days where the project had someone working on it:
projectID dateStart dateEnd
--------- ---------- ----------
20769720 2018-01-26
20769720 2018-01-29
20769720 2018-02-02
20769720 2018-03-20
20825496 2018-02-07
20825496 2018-02-12
20825496 2018-03-07
20825496 2018-03-15
The above table is what we have extracted as 'key' events depicting the start/end dates and can see that:
[Project 20769720] has someone working on 26-29th January (4 days);
[Project 20825496] has someone working from 7th Feb to 7th March (28 days) and from 15th March to present (19 days) = total 47 days
We considered hogging out the data to a temporary table and processing the data with a number of updates, but we can't create a temporary tables, cursors or stored procedures for this; it all has to be in a query, to return:
projectID days
--------- ----
20769720 4
20825496 47
Get the min dateStart and max dateEnd, and find the difference. Keep in mind that is a project starts and stops (ie. has gaps in work), this will not work properly.
SELECT
projectID,
DATEDIFF(min(dateStart),max(dateEnd)) as `days`
FROM projectLog a
GROUP BY projectID

MYSQL has 2 tables. Same dates total. Compare and subtract dates

​tablo1 tablo2
-------------------------- ------------------------------
fiyat1 tarih1 fiyat2 tarih2
---------- ------------ ----------- -----------
1200 03-2017 2100 03-2017
1050 03-2017 5200 03-2017
3250 04-2017 3200 04-2017
2501 04-2017
6100 05-2017
1100 05-2017
Collecting the same dates at price 1, collecting the same dates at price 2,
subtract 2 totals, group by date.
I want to print something like this:
-----------------------
05-2017 7200
04-2017 2511
03-2017 -5050
The question is true, but the result is wrong. I tried this.
SELECT tablo1.tarih1,
tablo1.fiyat1,
SUM(tablo1.fiyat1),
tablo2.tarih2,
tablo2.fiyat2,
SUM(tablo1.fiyat1),
(SUM(tablo1.fiyat1) - SUM(tablo2.fiyat2)) AS sonuc
FROM tablo1 INNER JOIN
tablo2 ON tablo1.tarih1 = tablo2.tarih2
GROUP BY tablo1.tarih1
With the table structure being as it is, the query that can be written to get the desired result is:
SELECT t1.tarih1, (COALESCE(t1.fiyat1, 0) - COALESCE(t2.fiyat2, 0)) AS sonuc
FROM
(SELECT tarih1, SUM(fiyat1) AS fiyat1
FROM tablo1
GROUP BY tarih1
) AS t1
LEFT JOIN
(SELECT tarih2, SUM(fiyat2) AS fiyat2
FROM tablo2
GROUP BY tarih2
) AS t2
ON t1.tarih1 = t2.tarih2
ORDER BY t1.tarih1 DESC;
However, I'd like to offer a couple of suggestions:
It's generally a good idea to store the date in MySQL date format: YYYY-MM-DD. It'll be much easier for you to run yearly reports, if there ever was a need.
As far as book-keeping is concerned, maybe you'll find the following Q&A to be of your interest: What is a common way to save 'debit' and 'credit' information?

SQL query for various time periods

I have a table that contains Following entries:
completed_time|| BOOK_CNT
*********************************************
2013-07-23 | 2
2013-07-22 | 1
2013-07-19 | 3
2013-07 16 |5
2013-07-12 |4
2013-07-11 |2
2013-07-02 |9
2013-06-30 |5
Now, I want to use above entries for data analysis.
Lets say DAYS_FROM, DAYS_TO and PERIOD are three variables.
I need to fire following sort of queries:
"Total book from DAYS_FROM to DAYS_TO in interval of PERIOD."
DAYS_FROM is a date in format YYYY-MM-DD
,DAYS_TO is a date in format YYYY-MM-DD
PERIOD is {1W,2W,1M,2M,1Y}
where W,M,Y represents WEEK,MONTH and YEAR.
Example: The queries DAYS_FROM=2013-07-23 , DAYS_TO=2013-07-03 and PERIOD=1W should return:
ith week - total
1 - 3
2- 8
3- 6
4- 14
Explanation:
1-3 means (The total book from 2013-07-21(sun) to 2013-07-23(tue) is 3 )
2-8 means (The total book from 2013-07-14(sun) to 2013-07-21(sun) is 8 )
3-16 means (The total book from 2013-07-07(sun) to 2013-07-14(sun) is 6 )
4-14 means (The total book from 2013-07-03(wed) to 2013-07-07(sun) is 14 )
Please refer the calendar image for better understanding.
How to fire such query?
What I tried?
SELECT DAY(completed_time), COUNT(total) AS Total
FROM my_tab
WHERE completed_time BETWEEN '2013-07-23' - INTERVAL 1 WEEK AND '2013-07-03'
GROUP BY DAY(completed_time);
The above queries subtracted 7 days from 2013-07-23 and thus considered 2013-07-16 to 2013-07-23 as first week, 2013-07-09 to 2013-07-16 as second week and so on.
A simple starting point would be something like below, of course you may want to adjust the ith value to suit your needs;
SET #period='1M';
SELECT CASE WHEN #period='1Y' THEN YEAR(completed_time)
WHEN #period='1M' THEN YEAR(completed_time)*100+MONTH(completed_time)
WHEN #period='2M' THEN FLOOR((YEAR(completed_time)*100+MONTH(completed_time))/2)*2
WHEN #period='1W' THEN YEARWEEK(completed_time)
WHEN #period='2W' THEN FLOOR(YEARWEEK(completed_time)/2)*2
END ith,
SUM(BOOK_CNT) Total
FROM my_tab
GROUP BY ith
ORDER BY ith DESC;
An SQLfiddle to test with.