Determine how to calculate split weeks in SQL Server - sql-server-2005

Im not quite sure what the term is, I have been calling it "Split Weeks" but here is what I need to find out.
Given:
User will input #StartDate and #EndDate
col_week_end_date will always end on a Saturday, and is a DateTime column.
I want to cycle through either multiple or a single month(s) and sum col_payment_amt
Using the month of September 2010, a col_payment_amt with a col_week_end_date falls on 09/04/2010, which covers the week of Aug 29 - Sep 04.
The payment month is September, but only 3 workdays fall w/i this week (Wed, Thurs, Fri). So only 3/5ths of the payment is made for that week.
The same thing happens with the end of a month. In this case, the col_week_end_date falls on 10/02/2010. Only 4/5ths of the payment will be made for this week.
I have a particular way to sum the col_payment_amt when this happens at the beginning of a month, and also at the end.
What I can't figure out is how to tell when I am at the start of a month, and when i am at the end of a month so I can apply the appropriate function, when running the report for multiple months (Aug - Oct).
Currently if I just force them to run the report for a single month, no problems, and I have been told it is only a monthly report, but I know eventually I will be asked if it can be run for multiple months.
I know it is basically something like:
SELECT sum(CASE WHEN at-top-of-month-with-split-week THEN....
WHEN at-bottom-of-month-with-split-week THEN...
ELSE col_payment_amt END) as PayTotal
FROM...
WHERE....
GROUP BY...
I'm trying to figure out the at-top-of-month and at-bottom-of-month parts. The other parts I have.

This code works in IBM Informix Dynamic Server (tested on 11.50.FC6 for MacOS X 1.06.4, but would work on any supported version of IDS, and any platform). You will need to translate into MS SQL Server notation.
Version 2 - reference month identified by month number only
CREATE FUNCTION NumWeekDaysInRefMonth(eow DATE DEFAULT TODAY, ref_month INTEGER)
RETURNING INT AS numdays;
DEFINE monday DATE;
DEFINE friday DATE;
DEFINE mon_month INTEGER;
DEFINE fri_month INTEGER;
IF eow IS NULL OR ref_month IS NULL THEN RETURN NULL; END IF;
LET monday = eow - WEEKDAY(eow) + 1;
LET friday = monday + 4;
LET mon_month = MONTH(monday);
LET fri_month = MONTH(friday);
IF mon_month = ref_month AND fri_month = ref_month THEN
-- All in same month: 5 days count.
RETURN 5;
END IF;
IF mon_month != ref_month AND fri_month != ref_month THEN
-- None in the same month: 0 days count.
RETURN 0;
END IF;
-- Some of the days are in the same month, some are not.
IF mon_month = ref_month THEN
-- End of month
RETURN 5 - DAY(friday);
ELSE
-- Start of month
RETURN DAY(friday);
END IF;
END FUNCTION;
Test cases
SELECT NumWeekDaysInRefMonth('2010-09-04', 9) answer, 3 AS expected FROM dual;
SELECT NumWeekDaysInRefMonth('2010-09-04', 8) answer, 2 AS expected FROM dual;
SELECT NumWeekDaysInRefMonth('2010-10-02', 9) answer, 4 AS expected FROM dual;
SELECT NumWeekDaysInRefMonth('2010-10-02', 10) answer, 1 AS expected FROM dual;
SELECT NumWeekDaysInRefMonth('2010-10-02', 8) answer, 0 AS expected FROM dual;
SELECT NumWeekDaysInRefMonth('2010-10-09', 10) answer, 5 AS expected FROM dual;
This has much the same logic as the previous version (below); it takes just a month number to identify which billing month you are interested in, rather than a full date.
Version 1 - full reference date
CREATE FUNCTION NumWeekDaysInRefMonth(eow DATE DEFAULT TODAY, ref DATE DEFAULT TODAY)
RETURNING INT AS numdays;
DEFINE mon DATE;
DEFINE fri DATE;
DEFINE v_mon INTEGER;
DEFINE v_fri INTEGER;
DEFINE v_ref INTEGER;
IF eow IS NULL OR ref IS NULL THEN RETURN NULL; END IF;
LET mon = eow - WEEKDAY(eow) + 1;
LET fri = mon + 4;
LET v_mon = YEAR(mon) * 100 + MONTH(mon);
LET v_fri = YEAR(fri) * 100 + MONTH(fri);
LET v_ref = YEAR(ref) * 100 + MONTH(ref);
IF v_mon = v_ref AND v_fri = v_ref THEN
-- All in same month: 5 days count.
RETURN 5;
END IF;
IF v_mon != v_ref AND v_fri != v_ref THEN
-- None in the same month: 0 days count.
RETURN 0;
END IF;
-- Some of the days are in the same month, some are not.
IF v_mon = v_ref THEN
-- End of month
RETURN 5 - DAY(fri);
ELSE
-- Start of month
RETURN DAY(fri);
END IF;
-- Month-end wrapping
-- 26 27 28 29 30 31 1 2 3 4 5 6 Jan, Mar, May, Jul, Aug, Oct, Dec
-- 25 26 27 28 29 30 1 2 3 4 5 6 Apr, Jun, Sep, Nov
-- 24 25 26 27 28 29 1 2 3 4 5 6 Feb - leap year
-- 23 24 25 26 27 28 1 2 3 4 5 6 Feb
-- Mo Tu We Th Fr Sa Su Mo Tu We Th Fr
-- Su Mo Tu We Th Fr Sa Su Mo Tu We Tu
-- Sa Su Mo Tu We Th Fr Sa Su Mo Tu We
-- Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu
-- Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo
-- We Th Fr Sa Su Mo Tu We Th Fr Sa Su
-- Tu We Th Fr Sa Su Mo Tu We Th Fr Sa
END FUNCTION;
Test cases
These tests are equivalent to the previous set.
SELECT NumWeekDaysInRefMonth('2010-09-04', '2010-09-01') answer, 3 AS expected FROM dual;
SELECT NumWeekDaysInRefMonth('2010-09-04', '2010-08-01') answer, 2 AS expected FROM dual;
SELECT NumWeekDaysInRefMonth('2010-10-02', '2010-09-01') answer, 4 AS expected FROM dual;
SELECT NumWeekDaysInRefMonth('2010-10-02', '2010-10-01') answer, 1 AS expected FROM dual;
SELECT NumWeekDaysInRefMonth('2010-10-02', '2010-08-01') answer, 0 AS expected FROM dual;
SELECT NumWeekDaysInRefMonth('2010-10-09', '2010-10-01') answer, 5 AS expected FROM dual;
Explanation
The Informix DATE type counts in days, so adding 1 to a DATE gives the day after. The Informix WEEKDAY function returns 0 for Sunday, 1 for Monday, ..., 5 for Friday, 6 for Saturday. The DAY, MONTH and YEAR functions return the corresponding component of a DATE value.
The code allows any day of the week as the reference date for the payment (it does not have to be Saturday). Similarly, although the examples for version 1 use the first of the month as the reference day for the month, you can supply any date within the requisite month as the reference date; in version 2, that is simplified to passing in the requisite month number.
If anyone passes a NULL into the function, the answer is NULL.
Then we calculate the Monday of the week containing the 'end of week' date; if the week day is Sunday, it subtracts 0 and adds 1 to get Monday; if the week day is Sturday, it subtracts 6 and adds 1 to get Monday; etc. Friday is 4 days later.
In version 1, then we calculate a representation for the year and month.
If both Monday and Friday fall in the reference month, then the answer is 5 days; if neither falls in the reference month, the answer is 0 days. If the Friday is within the reference month, then the DAY() value of the Friday date is the number of days in the month. Otherwise, the Monday is within the reference month, and the number of days in the month is 5 - DAY(Friday).
Note that this calculation deals with completely unrelated months - as shown by the last but one test case; there are zero days of a payment made October that should be counted in August.

Ok so here is what I ended up doing in a nutshell.
I set up a WHILE loop. I loop through the months for the date range, and insert the data into a TempTable along with a numeric Reference Month column (well, really an INT), and the month name (September, October etc...). From the TempTable I then select the data and have a CASE statement which determines which calculation to use. So it reads something like this...
-- Top of the month, Split Week
select sum(case when datepart(d,col_week_end_date) <= 7 AND RefMonth = month(col_ween_end_date)
then ...Top of Month calc...
-- Middle of the month, entire week is in Reference Month
when datepart(d,col_week_end_date) > 7 AND RefMonth = month(col_week_end_date)
then ...Just SUM the column as usual...
-- Bottom of month, Split Week
when RefMonth < month(col_week_end_date)
then ...Bottom of Month calc...
) end as MonthlySum,
col_RefMonth,
col_RefMonthName
from dbo.TempTable
group by col_RefMonth, col_RefMonthName
order by col_RefMonth
So far with the limited amount of data I have, it appears to be working with the split weeks in the months I have available. Seems the "Reference Month" was the key, and I needed to loop through and put the data in a TempTable. I was hoping there would be a way to get it in a single pass, or a least a quick dump into a #TempTable and select from there.
I'm not sure how efficient it will be, but it is only for monthly reporting and not really as an online interactive report.

Related

Find number of days in a given week according to the month for a given date. in SQL

Consider the date "2022-07-02"
For the month July first week only have 3 days in it.
I need to find the number of days in the week for the given date.
In above date the week has 3 days where "2022-07-02" day reside.
Example 2 :
For month June in 2022 first week has 5 days in the week
Therefore if i declare a date as "2022-06-03" it should pass the number of days in the week as 5
I need a query to find the number of days for the specific week.
set datefirst 1 -- assumes Monday is set as start of week
declare #myDate date = getdate();
-- calculate the next last day of week
declare #nextSunday date = dateadd(day, 7 - datepart(weekday, #myDate), #myDate);
select case
-- advancing into the next month implies a partial week
-- datediff(month, #myDate, nextSunday) = 1 would be equivalent
when day(#nextSunday) < day(#myDate) then 7 - day(#nextSunday)
-- else see if still within first week
when day(#nextSunday) < 7 then day(#nextSunday)
else 7 end;
Within a query you might use it this way:
select case
when day(nextSunday) < day(dateColumn) then 7 - day(nextSunday)
when day(nextSunday) < 7 then day(nextSunday)
else 7 end
from
myData cross apply (
values (dateadd(day, 7 - datepart(weekday, dateColumn), dateColumn))
) v(nextSunday);
https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=ee5bfb52dabe31dd619cfd136689db59
If you don't want the shorthand form then just replace every instance of nextSunday in the final step with its full expression.
There's nothing in the logic that prevents this from working with another first day of week. I just chose a variable name that helped ellucidate this particular problem.

How to change day of date in SQL server and set last day of month if the day does not exist in the month

I tried using this
dateadd(month, 0, dateadd(day,(30-datepart(dd,'2015-02-28')),'2015-02-28'))
to get the required output and instead of getting '2015-02-28' i get '2015-03-02'. How is it possible to change day of date in SQL and set last day of month if the day does not exist in the month ?
====Update with sample data =============
Note: Goal is not to get the last day of the month
If i want to change the day to 30 and if it's a month which has only 28 days. it should be the end of the month date. If not date should be 30th.
Changing the day to 30th
If Feb it should be - '2015-02-28'
If march it should be - '2015-03-30'
If April it should be - '2015-04-30'
There exists a function for this if your sql server version is 2012 or higher:
SELECT EOMONTH('2015-02-15')
returns 2015-02-28
-- Replace Day portion of #OrigDate with #NewDay.
-- If the result would be beyond the end of the month, return the last day of the month
create function ReplaceDay ( #OrigDate date, #NewDay int )
returns date
as
begin
declare #NewDate date
-- Deal with extreme cases, in case someone passes #NewDay = 7777 or #NewDay = -7777
if #NewDay < 1
set #NewDay = 1
if #NewDay > 31
set #NewDay = 31
-- Subtract the DAY part of the original date from the new day.
-- Add that many days to the original date
-- Example: if the original date is 2018-02-08 and you want to replace 08 with 17, then add 17-08=9 days
set #NewDate = DateAdd ( d, #NewDay-Day(#OrigDate), #OrigDate )
-- Have we ended up in the next month?
-- If so subtract days so that we end up back in the original month.
-- The number of days to subtract is just the Day portion of the new date.
-- Example, if the result is 2018-03-02, then subtract 2 days
if Month(#NewDate)<>Month(#OrigDate)
set #NewDate = DateAdd ( d, -Day(#NewDate), #NewDate )
return #NewDate
end
go
select dbo.ReplaceDay ( '2017-02-08', 17 ) -- Returns 2017-02-17
select dbo.ReplaceDay ( '2017-02-08', 30 ) -- Returns 2017-02-28
for sql server 2012 or higher versions please check HoneyBadger's answer.
for older versions:
SELECT DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,#mydate)+1,0))

Set a hardcoded day and month for a given year in TSQL

I am working on a small query and would like a little assistance formatting a date
Situation
I am accepting a payment date and based on that date I set the expiration date to 31st March of either this year or the next. So if the payment is made in January, the exp date is 31st March of the current year, if it's made in August, it's the 31st March of the next year.
This is my query.
BEGIN
IF DATEPART(MONTH,#paydate) BETWEEN 1 and 3
UPDATE usrBio
SET ExpDate=DATEPART(YEAR,GETDATE())+'-31'+'-03'
WHERE IDnum =#IDnum
ELSE IF
DATEPART(MONTH,#paydate) BETWEEN 4 and 12
UPDATE usrBio
SET ExpDate=DATEPART(YEAR,GETDATE()+1)+'-31'+'-03'
WHERE IDnum =#IDnum
END
The problem I am having is that I am unsure of how to set the month and day as instead of being concatenating to the year string, it's subtracting from the year date and my result is 1905-06-07 00:00:00.000 where I'm expecting 2017-31-03.
If 2012+, consider DateFromParts()
Example 1
Declare #PayDate date = '2017-08-31'
Select DateFromParts(Year(#PayDate)+IIF(Month(#PayDate)>3,1,0),3,31)
Returns
2018-03-31
----------------------
Example 2
Declare #PayDate date = '2017-03-31'
Select DateFromParts(Year(#PayDate)+IIF(Month(#PayDate)>3,1,0),3,31)
Returns
2017-03-31
**
Edit - For 2008
**
Select DateAdd(YEAR,case when Month(#PayDate)>3 then 1 else 0 end,str(Year(#PayDate),4)+'-03-31')
The problem is you trying year as number instead of string
DEMO
SELECT CAST( DATEPART(YEAR,GETDATE()+1)
AS varchar(5)) +'-03'+'-31'
And you can optimize your logic like this
UPDATE usrBio
SET ExpDate = CAST( CASE WHEN DATEPART(MONTH,#paydate) BETWEEN 1 and 3
THEN DATEPART(YEAR,GETDATE())
WHEN DATEPART(MONTH,#paydate) BETWEEN 4 and 12
THEN DATEPART(YEAR,GETDATE()+1)
END AS varchar(4)
) +'-03'+'-31'

Need to create Financial Year entries in Oracle 11g DB

I need a financial year entries based on current or today's date AND time in Oracle11g DB.
Suppose if we consider today's date is 1ST April 2013, then i need the outputs as 01-APR-13 and 31-MAR-14.
Our requirement financial Year considered is from April (the current year) to March (following year).
Based on current datetime...the scenario of output depends on the current date-time which falls in the above said period or duration.
Another example: If we take today's datetime as 28th Dec 2012, then the output is needed as 01-APR-12 and 31-MAR-13.
Please help how to acheive the same in very short format using only SQL.
Consider the below table as
Create table venky (financialYearFrom DATE NOTNULL, financialYearTo DATE NOTNULL);
Something ugly:
SELECT to_date('1-APR-'||(to_char(sysdate,'yyyy')+
(case when to_char(sysdate,'mm')>3
then 0
else -1
end))) AS start_date ,
to_date('31-MAR-'||(to_char(sysdate,'yyyy')+
(case when to_char(sysdate,'mm')>3
then 1
else 0
end))) end_date
FROM dual;
If month > 3 then add a year to 31-MAR(end_of_period), else substract a year to 1-APR (start_of_period).
UPDATE: Something nicer:
select add_months(trunc(add_months(sysdate,-3),'yyyy'),3) as start_date ,
add_months(trunc(add_months(sysdate,-3),'yyyy'),15)-1 as end_date
from dual
Substracting 3 months will send you to the correct start year. Truncating to year and adding 3 months sends you to 1 APR. End_of period is 12 months ahead start minus one day.
create user define function for that here is example for SQL SERVER
find how to do it in oracle (both are same with minor diff in syntax)
CREATE FUNCTION [dbo].[getYear]
(
-- Add the parameters for the function here
#CurDate DATETIME
)
RETURNS VARCHAR(MAX)
AS
BEGIN
DECLARE #Y int;
SELECT #Y = CASE WHEN MONTH(#CurDate) <= 3 THEN YEAR(#CurDate) - 1 ELSE YEAR(#CurDate) END;
RETURN '01-APR-' + LTRIM(STR(#Y)) + ' and 31-MAR-' + LTRIM(STR(#Y+1));
END
and use it like this
Select dbo.GETYEAR(GETDATE());

Adding relative week number column to MySQl results

I have a table with 3 columns: user, value, and date. The main query returns the values for a specific user based on a date range:
SELECT date, value FROM values
WHERE user = '$user' AND
date BETWEEN $start AND $end
What I would like is for the results to also have a column indicating the week number relative to the date range. So if the date range is 1/1/2010 - 1/20/2010, then any results from the first Sun - Sat of that range are week 1, the next Sun - Sat are week 2, etc. If the date range starts on a Saturday, then only results from that one day would be week 1. If the date range starts on Thursday but the first result is on the following Monday, it would be week 2, and there are no week 1 results.
Is this something fairly simple to add to the query? The only ideas I can come up with would be based on the week number for the year or the week number based on the results themselves (where in that second example above, the first result always gets week 1).
Example:
$start = "05/27/2010";
$end = "06/13/2010";
//Query
Result:
week date user value
1 05/28/2010 joe 123
3 06/07/2010 joe 123
3 06/08/2010 joe 123
4 06/13/2010 joe 123
This can easily be done with this simple and obvious expression :)
SELECT ...,
FLOOR(
(
DATEDIFF(date, $start) +
WEEKDAY($start + INTERVAL 1 DAY)
) / 7
) + 1 AS week_number;
Some explanation: this expression just calculates difference between the given date and the start date in days, then converts this number to weeks via FLOOR("difference in days" / 7) + 1. That's simple, but since this works only when $start is Sunday, we should add an offset for other week days: WEEKDAY($start + INTERVAL 1 DAY) which equals to 0 for Sun, 1 for Mon, ..., 6 for Sat.