Using DatePart() with date ranges crossing the datepart boundary - ms-access

I am currently trying to summarise some data tables into a report. Each record in the table consists of a date range, something like this:
StartDate EndDate
--------------------
13/04/13 15/04/13
17/04/13 24/04/13
28/04/13 03/05/13
05/05/13 10/05/13
Assuming the date ranges signify something like days of leave, I want to be able to calculate the total amount of days of leave per month. I came across the DatePart function which seems to work apart from one edge case: when the date range crosses a month boundary. Since the DatePart function returns the month for one given date, I am no longer able to use that to determine the amount of days of leave for that edge case record (in the example above it is record 3), since it applies to two separate months.
Ideally I want my final table to look like:
Month #OfDays
--------------------
4 11 (1st record - 2, 2nd record - 7, 3rd record - 2)
5 8 (3rd record - 3, 4th record - 5)
I've considered some messy options, such as populating a temporary table having each record signifying a different day and then doing a query on that, but I am not sure how this ties in with a report. Right now my report record source is the (incorrect) query, is it possible to have a record source as a VBA function that returns a recordsource?
Another thing I thought was to possibly to have an initial query that splits up any edge cases into two seperate records, where the date range only covers one month, and then use that for my final grouping query. Is that even possible?
I feel there may be a much simpler solution to this problem yet I can't see it.
If anyone has any ideas it would be much appreciated!

To accomplish your task using Access queries you will need to create a table named [Numbers] with a single Number (Long Integer) column named [n] containing the numbers 1, 2, 3, ... up to the highest year you expect to be working with. I created mine as follows
n
----
1
2
3
...
2499
2500
You'll also need to paste the following VBA function into an Access Module
Public Function IsValidDayOfYear(YearValue As Long, DayValue As Long) As Boolean
Dim IsLeapYear As Boolean
If (YearValue Mod 400) = 0 Then
IsLeapYear = True
ElseIf (YearValue Mod 100) = 0 Then
IsLeapYear = False
ElseIf (YearValue Mod 4) = 0 Then
IsLeapYear = True
Else
IsLeapYear = False
End If
IsValidDayOfYear = (DayValue <= IIf(IsLeapYear, 366, 365))
End Function
Let's assume that your source table is called [DateRanges]. We'll start by creating a query that generates every day of the year for each year represented in the source table. The trick here is that DateSerial() "rolls over" month boundaries, so
DateSerial(2013, 1, 32) = #2013-02-01#
and
DateSerial(2013, 1, 234) = #2013-08-22#
SELECT DateSerial(yr.n, 1, dy.n) AS [Date]
FROM Numbers yr, Numbers dy
WHERE
(
yr.n
BETWEEN (SELECT MIN(DatePart("yyyy", DateRanges.StartDate)) FROM DateRanges)
AND (SELECT MAX(DatePart("yyyy", DateRanges.EndDate)) FROM DateRanges)
)
AND (dy.n < 367) AND IsValidDayOfYear(yr.n, dy.n)
For your sample data, that query returns all days in 2013.
Let's save that query as [AllDays]. Now we can use it to extract the individual days for each date range (omitting StartDate so the final counts match yours in the question)
SELECT [Date] FROM AllDays
WHERE EXISTS
(
SELECT * FROM DateRanges
WHERE AllDays.[Date] BETWEEN DateAdd("d", 1, DateRanges.StartDate) AND DateRanges.EndDate
)
That returns the individual days corresponding to each range, i.e.,
Date
----------
2013-04-14
2013-04-15
2013-04-18
2013-04-19
2013-04-20
2013-04-21
2013-04-22
2013-04-23
2013-04-24
2013-04-29
2013-04-30
2013-05-01
2013-05-02
2013-05-03
2013-05-06
2013-05-07
2013-05-08
2013-05-09
2013-05-10
We can save that query as [RangeDays] and then use it to calculate our counts by month...
SELECT DatePart("m", [Date]) AS [Month], COUNT(*) AS NumOfDays
FROM RangeDays
GROUP BY DatePart("m", [Date])
...returning
Month NumOfDays
----- ---------
4 11
5 8

Related

SQL group by days interval

I have a sample table here with the following columns and sample records. I want to be able to sum my column cases using with a specific date range (the helper column).
I want to get my results this way:
Sum all cases WHERE date range is in between 2022-03-23 - 2022-04-01 and so on.
date range
Sum of Cases
2022-03-23-2022-04-01
5 (sample result only)
2022-03-24-2022-04-02
9 (sample result only)
The logic of the date range is always n - n9 days.
I 've tried this type of query but it does not work, it there a way for me to get this without have to use a query to create another column?
SELECT Date,
sum([QUERY 1]) as "Reports 7 days prev",
sum ([QUERY 2]) as "Reports 7 days after"
FROM REPORTS
GROUP BY Date
Data:
Date
BuyerID
Cases
Helper (Date Range)
4/1/2022
20001
2
2022-03-23-2022-04-01
4/1/2022
20001
1
2022-03-23-2022-04-01
4/2/2022
20002
3
2022-03-24-2022-04-02
4/5/2022
20003
5
2022-03-27-2022-04-05
4/7/2022
20004
6
2022-03-29-2022-04-07
4/7/2022
20005
9
2022-03-29-2022-04-07
Are you looking to get total cases for last X number of days? What does your initial data look like?
you can try something like:
Step 1: You aggregate all the cases for each date.
WITH CASES_AGG_BY_DATE AS
(
SELECT Date,
SUM(Cases) AS Total_Cases
FROM REPORTS
GROUP BY Date
),
Step 2: you aggregate the last 7 days rolling cases sum for each date
LAST_7_DAY_AGG AS
(
SELECT Date, SUM(Total_Cases) OVER(ORDER BY Date ASC ROWS BETWEEN 7 PRECEDING AND CURRENT ROW) AS sum_of_cases,
LAG(Date, 7) AS 7th_day
FROM CASES_AGG_BY_DATE
)
Step 3: create final output and concatenate date and 7th day before that
SELECT Date, CONCAT(Date, "-", 7th_day), sum_of_cases
FROM LAST_7_DAY_AGG;

Get value difference between two dates in MySQL

I have a following table structure:
page_id view_count date
1 30 2018-08-30
1 33 2018-08-31
1 1 2018-09-01
1 5 2018-09-02
...
View count is reset on 1st of every month, and it's current value is stored on a daily basis, so on 31st of August it was increased by 3 (because 33-30).
What I need to do is to retrieve the view count (difference) between two dates through SQL query. To retrieve view count between two dates in same month would be simple, by just subtracting bigger date with the lower date, but retrieving between two dates that are in different months is what's not sure to me how to achieve. If I wanted to retrieve data between 2018-08-13 and 2018-09-13 I would have to get difference between 2018-08-31 and 2018-08-13, and add it to the value of 2018-09-13.
Also, I would like to do it for all page_id at once, between the same dates if possible within a single query.
assuming that the counter is unique per page and that the page_id counter is inserted daily into the table, I think that such a solution would work
The dates are based on the example,
and should be replaced by the relevant parameters
SELECT
v1.view_count + eom.view_count - v2.view_count
FROM
view_counts v1
INNER JOIN view_counts v2 ON v2.page_id = v1.page_id AND v2.`date` = '2018-08-13'
INNER JOIN view_counts eom ON v2.page_id = v.page_id AND eom.`date` = LAST_DAY(DATE_ADD(v.`date`, INTERVAL -1 MONTH))
WHERE
`date` = '2018-09-13'

SQL Query - Find out how many times a row changes from 0 to another value

I am using MySQL 8 and need to create a stored procedure
I have a single table that has a DATE field and a value field which can be 0 or any other number. This value field represents the daily amount of rain for that day.
The table stores data between today and 10 years.
I need to find out how many periods of rain there will be in the next 10 years.
So, for example, if my table contains the following data:
Date - Value
2018-06-09 - 0
2018-06-10 - 50
2018-06-11 - 0
2018-06-12 - 15
2018-06-13 - 17
2018-06-14 - 0
2018-06-15 - 0
2018-06-16 - 12
2018-06-17 - 123
2018-06-18 - 17
Then the SP should return 3, because there were 3 periods of rain.
Any help in getting me closer to the answer will be appreciated!
You don't need to have a stored procedure for this.
A solution with MySQL's 8.0 LEAD function this supports dates with gaps.
The complete table needs to be scanned but i don't think that a huge problem with ~3560 records.
Query
SELECT
SUM(filter_match = 1) AS number
FROM (
SELECT
((t.value = 0) AND (LEAD(t.value) OVER (ORDER BY t.date ASC) != 0)) AS filter_match
FROM
t
) t
see demo https://www.db-fiddle.com/f/sev4NqgLsFPgtNgwzruwy/2
By the way, would you mind expanding your answer to understand how
LEAD and SUM work together?
LEAD(t.value) OVER (ORDER BY t.date ASC) simply means get the next value from the next record ordered by date.
this demo shows it nicely https://www.db-fiddle.com/f/sev4NqgLsFPgtNgwzruwy/6
SUM(filter_match = 1) is a conditional sum. in this case the alias filter_match needs to be true.
see what filter_match is demo https://www.db-fiddle.com/f/sev4NqgLsFPgtNgwzruwy/8
In MySQL aggregate functions can have a SQL expression something like 1 = 1 (which is always true or 1) or 1 = 0 (which is always false or 0).
The conditional sum only sums up when the condition is true.
see demo https://www.db-fiddle.com/f/sev4NqgLsFPgtNgwzruwy/7
Use MySQL join:
SELECT COUNT(*) Number_of_Periods
FROM yourTable A JOIN yourTable B
ON DATE(A.`DATE`)=DATE(B.`DATE` - INTERVAL 1 DAY)
AND A.`VALUE`=0 AND B.`VALUE`>0;
See Demo on DB Fiddle.

Pivot with changing dates

I have a script with result set something like this:
-- #Test (temptable)
Branch_Name C_Date Percentage
Branch 1 20140107 90
Branch 1 20140108 82
Branch 2 20140107 85
Branch 2 20140108 86
I would like to pivot this data, however the C_Date is populated to get 7 days back:
WHERE (1 = 1) AND
(tTable.C_Date > CONVERT(VARCHAR(10),GETDATE() -7, 112)) AND
(tEnter.C_Date < CONVERT(VARCHAR(10),GETDATE() -1, 112)).
I've tried
Select * from #Test
pivot (avg (Percentage) for C_date in ([20140107],[20140108])) as PivotTable
and it gives me the data I want (see below),
Branch_Name 20140107 20140108
Branch 1 90 82
Branch 2 85 86
but how do I get the pivot to look at dates populated by the GETDATE? I've tried putting the GETDATE command in each [] but that obviously didn't work.
** Note, my example shows 2 days, but my query is for 7 days back, not including the day it's being run.
Any help appreciated - thank you!
Instead of trying to work with the dates, try working with "the number of days between C_Date and today".
So early on (in a subquery or CTE) you do DATEDIFF(day,C_Date,GETDATE()) as NumDays.
You can then filter your where as WHERE NumDays between 1 and 7 and your pivot as:
pivot (avg (Percentage) for NumDays in ([1],[2],[3],[4],[5],[6],[7])) as PivotTable
Now, that handles most of what you need. The one thing we can't do (in plain SQL) is to convert those column names back into dates - because any particular SQL query has to produce a result set with a fixed "shape" - the number, names and types of the columns are fixed.
But hopefully that's enough to do in SQL, and if you do need to convert back into date headings, that can be done with whatever is consuming this result set.

TSQL SELECT based on a condition

I am trying to do a select from CTE based on a condition.
There is a variable I've declared for today's period (#PRD). It holds the value of what period we are currently in.
Now I would like to do a selection from a table that will restrict what information is returned based on whether we are in the first half of the year or not.
For instance, we are in period 2 so I want everything returned from my CTE which falls between PRD 1 and 5. IF we were in say period 6 (after 5), then yes I'd want everything returned from the table.
This is the pseudocode of what I'm trying to accomplish:
SELECT
CASE
WHEN #PRD <= 5
THEN (SELECT * FROM DISPLAY WHERE PERIOD IN (1,2,3,4,5))
ELSE (SELECT * FROM DISPLAY)
END
I'm getting an error:
Only one expression can be specified in the select list when the subquery is not introduced with EXISTS.
Please any thoughts on how I can do this?
Thanks x
EDITED/UPDATED:
More of the code involves a CTE and is really long. Bottom line is lets say I have this CTE
;WITH DISPLAY as (
select * from lots_of_things
)
SELECT * FROM DISPLAY
Having done a regular select on this CTE, it returns data that looks like this:
PERIOD (INT) DEPARTMENT GROUP BUDGET
1 ENERGY HE 500
2 ENERGY HE 780
3 ENERGY HE 1500
4 ENERGY HE 4500
5 ENERGY HE 400
6 ENERGY HE 3500
7 ENERGY HE 940
8 ENERGY HE 1200
I want it to show me just the top 5 rows if we the current period is 1,2,3,4,5. But to display ALL table rows if we are in any other period like 6,7,8,9 and onwards. The current period is held in the variable #PRD which is derived from doing a comparison of today's date with ranges held in a table. The value is accurate and also type INT
Hope this helps
SQL FIDDLE
This will work:
SELECT * FROM DISPLAY WHERE (#PRD > 5 OR PERIOD IN (1, 2, 3, 4, 5))
If this code confuses you, what's happening is that we check if #PRD > 5 and if that returns true, our expression is always true so we return all the rows.
If the variable is less or equal to 5 (like you checked in your example), the first check is false and then we check if the period is the list.
This might be a solution:
IF #PRD <= 5
SELECT * FROM DISPLAY WHERE PERIOD IN (1,2,3,4,5)
ELSE
SELECT * FROM DISPLAY
UPD
In this case you should use variable instead of CTE, if it's possible.
DECLARE #PRD INT;
SELECT #PRD = PERIOD FROM SOME_TABLE WHERE ...