How to determine the number of days in a month for a given Date Range? - sql

I need to calculate using SQL Query, how many days within a given range fall into each calendar month.
I have given 2 dates, which define a date range; for example 2020-01-01 to 2020-08-03. I need to find how many days in that range fall in to each month i.e. how many fall into July, and how many into August.
In the example given, the expected result is 31 days in July and 3 days in August.

One approach uses a recusive query. Using date artithmetics, we can build the query so it performs one iteration per month rather than one per day, so this should be a rather efficient approach:
with cte as (
select
datefromparts(year(#dt_start), month(#dt_start), 1) month_start,
1 - day(#dt_start) + day(
case when #dt_end > eomonth(#dt_start)
then eomonth(#dt_start)
else #dt_end
end
) as no_days
union all
select
dateadd(month, 1, month_start),
case when #dt_end > dateadd(month, 2, month_start)
then day(eomonth(dateadd(month, 1, month_start)))
else day(#dt_end)
end
from cte
where dateadd(month, 1, month_start) <= #dt_end
)
select * from cte
Demo on DB Fiddle.
If we set the boundaries as follows:
declare #dt_start date = '2020-07-10';
declare #dt_end date = '2020-09-10';
Then the query returns:
month_start | no_days
:---------- | ------:
2020-07-01 | 22
2020-08-01 | 31
2020-09-01 | 10

You can refer this
;with dates(thedate) as (
select dateadd(yy,years.number,0)+days.number
from master..spt_values years
join master..spt_values days
on days.type='p' and days.number < datepart(dy,dateadd(yy,years.number+1,0)-1)
where years.type='p' and years.number between 100 and 150
-- note: 100-150 creates dates in the year range 2000-2050
-- adjust as required
)
select dateadd(m,datediff(m, 0, d.thedate),0) themonth, count(1)
from dates d
where d.thedate between '2020-01-01' and '2020-08-03'
group by datediff(m, 0, d.thedate)
order by themonth;
Please refer the link below where RichardTheKiwi user given a clear example for your scenario.
SQL Server query for total number of days for a month between date ranges

You can do all the work at the month level rather than the day level -- which should be a bit faster. Here is a method using a recursive CTE:
with cte as (
select #startdate as startdate, #enddate as enddate,
datefromparts(year(#startdate), month(#startdate), 1) as month
union all
select startdate, enddate, dateadd(month, 1, month)
from cte
where dateadd(month, 1, month) < #enddate
)
select month,
(case when month <= startdate and dateadd(month, 1, month) >= enddate
then day(enddate) - day(startdate) + 1
when month <= startdate
then day(eomonth(month)) - day(startdate) + 1
when dateadd(month, 1, month) < enddate
then day(eomonth(month))
when dateadd(month, 1, month) >= enddate
then day(enddate)
end)
from cte;
And the db<>fiddle.
The logic is simpler at the day level:
with cte as (
select #startdate as dte, #enddate as enddate
union all
select dateadd(day, 1, dte), enddate
from cte
where dte < enddate
)
select datefromparts(year(dte), month(dte), 1) as yyyymm, count(*)
from cte
group by datefromparts(year(dte), month(dte), 1)
order by yyyymm
option (maxrecursion 0)

Here is a solution with recursive CTE.
declare #startDate date = '2020-07-01'
declare #endDate date = '2020-08-03'
; WITH cte (n, year, month, daycnt)
AS (
SELECT
0
, DATEPART(year, #startDate)
, DATENAME(MONTH, #startDate)
, DATEPART(day, EOMONTH( #startDate ) ) - DATEPART(day, #startDate ) + 1
UNION ALL
SELECT
n + 1
, DATEPART(year, DATEADD(month, n + 1, #startDate) )
, DATENAME(MONTH, DATEADD(month, n + 1, #startDate) )
, IIF(
n = ( DATEPART(month, #endDate) - DATEPART(month, #startDate) ) + ( DATEPART(year, #endDate) - DATEPART(year, #startDate) ) * 12 - 1
, DATEPART(day, #endDate )
, DATEPART(day, EOMONTH( DATEADD(month, n + 1, #startDate) ) )
)
FROM
cte
WHERE
n <= ( DATEPART(month, #endDate) - DATEPART(month, #startDate) ) + ( DATEPART(year, #endDate) - DATEPART(year, #startDate) ) * 12 - 1
)
SELECT *
FROM cte
ORDER BY n
OPTION (maxrecursion 0)
This could be further simplified with a number function but that would also be essentially be a recursive CTE, though it would definitely look cleaner. But it requires defining a function on top of this SELECT statement.

Related

How to get date of a beginning of first work weekday even if the Monday is from last month?

My goal to is get get query that will return weekdays in a month. I can get the days of the month but I need to get dates starting from monday through Friday even if the Monday may be in the preceding month.
Example April 1st is a wednesday so I would need to bring back March 30th and 31st. And the last date returned would be by May 1st as that is the last friday that contains some April days..
If interested in a helper function, I have TVF which generates a calendar.
Example
Select * from [dbo].[tvf-Date-Calendar-Wide]('2020-04-01')
Returns
So, with a little tweak, we get can
Select WeekNr = RowNr
,B.*
From [dbo].[tvf-Date-Calendar-Wide]('2020-04-01') A
Cross Apply ( values (Mon)
,(Tue)
,(Wed)
,(Thu)
,(Fri)
) B(Date)
Which Returns
WeekNr Date
1 2020-03-30
1 2020-03-31
1 2020-04-01
1 2020-04-02
1 2020-04-03
2 2020-04-06
2 2020-04-07
2 2020-04-08
...
5 2020-04-29
5 2020-04-30
5 2020-05-01
The Function If Interested
CREATE FUNCTION [dbo].[tvf-Date-Calendar-Wide] (#Date1 Date)
Returns Table
Return (
Select RowNr,[Sun],[Mon],[Tue],[Wed],[Thu],[Fri],[Sat]
From (
Select D
,DOW=left(datename(WEEKDAY,d),3)
,RowNr = sum(Flg) over (order by D)
From (
Select D,Flg=case when datename(WEEKDAY,d)= 'Sunday' then 1 else 0 end
From (Select Top (42) D=DateAdd(DAY,-7+Row_Number() Over (Order By (Select Null)),#Date1) From master..spt_values n1 ) A
) A
) src
Pivot (max(d) for DOW in ([Sun],[Mon],[Tue],[Wed],[Thu],[Fri],[Sat]) )pvg
Where [Sun] is not null
and [Sat] is not null
)
-- Select * from [dbo].[tvf-Date-Calendar-Wide]('2020-04-01')
You first need to find the start of the week for the first day of the month, then the date for the end of the week that contains the last day of the month:
e.g.
SELECT WeekStart = DATEADD(DAY, -(DATEPART(WEEKDAY, '20200401')-1), '20200401'),
WeekEnd = DATEADD(DAY, 7-(DATEPART(WEEKDAY, '20200430')), '20200430');
Gives:
WeekStart WeekEnd
------------------------------
2020-03-29 2020-05-02
You wouldn't want to hard code the first and the last of the month, but these are fairly trivial things to get from a date:
DECLARE #Date DATE = '20200415';
SELECT MonthStart = DATEADD(MONTH, DATEDIFF(MONTH, 0, #Date), 0),
MonthEnd = EOMONTH(#Date);
Which returns
MonthStart MonthEnd
------------------------------
2020-04-01 2020-04-30
You can then just substitute this into the first query for week starts:
DECLARE #Date DATE = '20200401';
SELECT WeekStart = DATEADD(DAY, -(DATEPART(WEEKDAY, DATEADD(MONTH, DATEDIFF(MONTH, 0, #Date), 0))-1), DATEADD(MONTH, DATEDIFF(MONTH, 0, #Date), 0)),
WeekEnd = DATEADD(DAY, 7-(DATEPART(WEEKDAY, EOMONTH(#Date))), EOMONTH(#Date));
Which gives the same output as the first query with hard coded dates. This is very clunky though, so I would separate this out into a further step:
DECLARE #Date DATE = '20200401';
-- SET DATE TO THE FIRST OF THE MONTH IN CASE IT IS NOT ALREADY
SET #Date = DATEADD(MONTH, DATEDIFF(MONTH, 0, #Date), 0);
SELECT WeekStart = DATEADD(DAY, -(DATEPART(WEEKDAY, #Date)-1), #Date),
Weekend = DATEADD(DAY, 7-(DATEPART(WEEKDAY, EOMONTH(#Date))), EOMONTH(#Date));
Again, this gives the same output (2020-03-29 & 2020-05-02).
The next step is to fill in all the dates between that are weekdays. If you have a calendar table this is very simple
DECLARE #Date DATE = '20200415';
-- SET DATE TO THE FIRST OF THE MONTH IN CASE IT IS NOT ALREADY
SET #Date = DATEADD(MONTH, DATEDIFF(MONTH, 0, #Date), 0);
DECLARE #Start DATE = DATEADD(DAY, -(DATEPART(WEEKDAY, #Date)-1), #Date),
#End DATE = DATEADD(DAY, 7-(DATEPART(WEEKDAY, EOMONTH(#Date))), EOMONTH(#Date));
SELECT [Date], DayName = DATENAME(WEEKDAY, [Date])
FROM Calendar
WHERE Date >= #Start
AND Date <= #End
AND IsWeekday = 1
ORDER BY [Date];
If you don't have a calendar table, then I suggest you create one, but if you can't create one you can still generate this on the fly, by generating a set series numbers and adding these numbers to your start date:
DECLARE #Date DATE = '20200415';
-- SET DATE TO THE FIRST OF THE MONTH IN CASE IT IS NOT ALREADY
SET #Date = DATEADD(MONTH, DATEDIFF(MONTH, 0, #Date), 0);
DECLARE #Start DATE = DATEADD(DAY, -(DATEPART(WEEKDAY, #Date)-1), #Date),
#End DATE = DATEADD(DAY, 7-(DATEPART(WEEKDAY, EOMONTH(#Date))), EOMONTH(#Date));
-- GET NUMBERS FROM 0 - 50
WITH Dates (Date) AS
( SELECT TOP (DATEDIFF(DAY, #Start, #End))
DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY n1.n) - 1, #Start)
FROM (VALUES (1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) n1 (n)
CROSS JOIN (VALUES (1),(1),(1),(1),(1)) n2 (n)
)
SELECT [Date], DayName = DATENAME(WEEKDAY, [Date])
FROM Dates
WHERE ((DATEPART(WEEKDAY, [Date]) + ##DATEFIRST) % 7) NOT IN (0, 1);
Just generate all possible dates -- up to 6 days before the month begins. Take the valid weekdays after the first Monday:
with dates as (
select dateadd(day, -6, convert(date, '2020-04-01')) as dte
union all
select dateadd(day, 1, dte)
from dates
where dte < '2020-04-30'
)
select dte
from (select d.*,
min(case when datename(weekday, dte) = 'Monday' then dte end) over () as first_monday
from dates d
) d
where datename(weekday, dte) not in ('Saturday', 'Sunday') and
dte >= first_monday;
declare #dateVal datetime = GETDATE(); --assign your date here
declare #monthFirstDate datetime = cast(YEAR(#dateVal) as varchar(4)) + '-' + DATENAME(mm, #dateVal) + '-' + cast(01 as varchar(2))
declare #monthLastDate datetime = DAteADD(day, -1, DATEADD(month, 1, cast(YEAR(#dateVal) as varchar(4)) + '-' + DATENAME(mm, #dateVal) + '-' + cast(01 as varchar(2))))
declare #startDate datetime = DATEADD(DAY, 2 - DATEPART(WEEKDAY, #monthFirstDate), CAST(#monthFirstDate AS DATE))
declare #enddate datetime = DATEADD(DAY, 6 - DATEPART(WEEKDAY, #monthLastDate), CAST(#monthLastDate AS DATE))
Select #startDate StartDate, #enddate EndDate
****Result**
--------------------------------------------------------------
StartDate | EndDate
-----------------------------|--------------------------------
2020-03-02 00:00:00.000 | 2020-04-03 00:00:00.000
-----------------------------|---------------------------------**

Start and end of each month between two dates [duplicate]

This question already has answers here:
months between two dates in sql server with starting and end date of each of them in sql server
(6 answers)
Closed 4 years ago.
Given two #START_DATE and #END_DATE parameters, I'd like to write a query that generates pairs of (month_start_date, month_end_date) for every month that exists between those two days, including the ones those dates are in.
E.g. If #START_DATE = '2018-01-14' and #END_DATE = '2018-05-04' (yyyy-MM-dd), I'd like the output to be
month_start_date, month_end_date
2018-01-01, 2018-01-31
2018-02-01, 2018-02-28
2018-03-01, 2018-03-31
2018-04-01, 2018-04-30
2018-05-01, 2018-05-31
I tend to go for recursive CTEs for this purpose:
with dates as (
select datefromparts(year(#start_date), month(#start_date), 1) as dte
union all
select dateadd(month, 1, dte)
from dates
where dateadd(month, 1, dte) <= #end_date
)
select dte as start_date, eomonth(dte) as end_date
from dates;
This works as-is for up to 100 months. For more than that, you need to use set the max recursion option.
Following query returns the required result.
declare #StartDate date = '2018-01-14'
, #EndDate date = '2018-05-04';
;with Months as (
select top (datediff(month,#StartDate,#EndDate)+1)
[month_start_date] = dateadd(month
, datediff(month, 0, #StartDate) + row_number() over (order by number) -1
, 0)
, month_end_date = dateadd(day,-1,dateadd(month
, datediff(month, 0, #StartDate) + row_number() over (order by number)
,0))
from master.dbo.spt_values
order by [month_start_date]
)
select * from Months;
You need recursive cte :
with t as (
select dateadd(day, 1, eomonth(#START_DATE, -1)) as start_dt, #END_DATE as end_dt
union all
select dateadd(mm, 1, start_dt), end_dt
from t
where dateadd(mm, 1, start_dt) < #END_DATE
)
select start_dt as month_start_date, eomonth(start_dt) as month_end_date
from t
option (maxrecursion 0);

Calculate last days of months for given period in SQL Server

Is it possible to do in SQL: for example I have period where #s_date = '20130101' and #e_date = '20130601' and I want to select all last days of months in this period.
This is example of result:
20130131
20130228
20130331
20130430
20130531
Thanks.
The easiest option is to have a calendar table, with a last day of the month flag, so your query would simply be:
SELECT *
FROM dbo.Calendar
WHERE Date >= #StartDate
AND Date <= #EndDate
AND EndOfMonth = 1;
Assuming of course that you don't have a calendar table you can generate a list of dates on the fly:'
DECLARE #s_date DATE = '20130101',
#e_date DATE = '20130601';
SELECT Date = DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY Object_ID) - 1, #s_date)
FROM sys.all_objects;
Then once you have your dates you can limit them to where the date is the last day of the month (where adding one day makes it the first of the month):
DECLARE #s_date DATE = '20130101',
#e_date DATE = '20130601';
WITH Dates AS
( SELECT Date = DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY Object_ID) - 1, #s_date)
FROM sys.all_objects
)
SELECT *
FROM Dates
WHERE Date <= #e_Date
AND DATEPART(DAY, DATEADD(DAY, 1, Date)) = 1;
Example on SQL Fiddle
You can run the following query and then adjust it by using your table details:
declare #s_date as datetime= '20130101'
declare #e_date as datetime= '20131020'
SELECT DateAdd(m, number, '1990-01-31')
FROM master.dbo.spt_values
WHERE 'P' = type
AND DateAdd(m, number, #s_date) < #e_date
example for 20130101 :
select CONVERT(VARCHAR(8),
dateadd(day, -1, dateadd(month, 1,
convert(datetime, '20130101',112))), 112)
result :
20130131
Try this query
WITH sample
AS (SELECT Cast('2013-04-01' AS DATETIME) Date
UNION ALL
SELECT Dateadd(day, 1, date) dt
FROM sample
WHERE date < Cast('2013-05-05' AS DATETIME))
SELECT *
FROM sample
Fiddle
EOMONTH(#date) is the function you need.
Here is the help page https://learn.microsoft.com/en-us/sql/t-sql/functions/eomonth-transact-sql?view=sql-server-2017
This query gets the las 50 End Of Months.
The original query used as an example is from here.
https://dba.stackexchange.com/a/186829
WITH cte AS
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 1)) - 1 AS [Incrementor]
FROM [master].[sys].[columns] sc1
CROSS JOIN [master].[sys].[columns] sc2
)
SELECT top 50 EOMONTH(DATEADD(Month, -1 * cte.[Incrementor], GETDATE()))
FROM cte
WHERE EOMONTH(DATEADD(Month, -1 * cte.[Incrementor], GETDATE())) < GETDATE();

How to get all the weekend dates of the current year in SQL?

I tried but could not get the right solution. I want an SQL query that lists all the weekend dates of the current year.
I tried this SQL query:
WITH hier(num, lvl) AS (
SELECT 0, 1
UNION ALL
SELECT 100, 1
UNION ALL
SELECT num + 1, lvl + 1
FROM hier
WHERE lvl < 100
)
SELECT lvl [Week],
convert(date,DATEADD(dw, -DATEPART(dw, DATEADD(wk,DATEDIFF(wk,0,'12/31/'+convert(nvarchar,YEAR(getdate()))), 0)+6 ),
DATEADD(wk, DATEDIFF(wk,0,'12/31/'+convert(nvarchar,YEAR(getdate()))), 0)+6 ) - num * 7,101) [End Date]
FROM hier a
where num < 52
ORDER BY [End Date] asc
Its output is like this:
Week End date
52 2012-01-14
51 2012-01-21
50 2012-01-28
49 2012-02-04
I want the dates to start from the beginning – so, the above is missing one weekend, which is 2012-07-01. Also, I want the week numbers to show as 1, 2, 3... instead of 52, 51....
Check out this blog post.
Your question is explained in detail.
DECLARE #Year AS INT,
#FirstDateOfYear DATETIME,
#LastDateOfYear DATETIME
-- You can change #year to any year you desire
SELECT #year = 2010
SELECT #FirstDateOfYear = DATEADD(yyyy, #Year - 1900, 0)
SELECT #LastDateOfYear = DATEADD(yyyy, #Year - 1900 + 1, 0)
-- Creating Query to Prepare Year Data
;WITH cte AS (
SELECT 1 AS DayID,
#FirstDateOfYear AS FromDate,
DATENAME(dw, #FirstDateOfYear) AS Dayname
UNION ALL
SELECT cte.DayID + 1 AS DayID,
DATEADD(d, 1 ,cte.FromDate),
DATENAME(dw, DATEADD(d, 1 ,cte.FromDate)) AS Dayname
FROM cte
WHERE DATEADD(d,1,cte.FromDate) < #LastDateOfYear
)
SELECT FromDate AS Date, Dayname
FROM CTE
WHERE DayName IN ('Saturday','Sunday') -- For Weekend
/*
WHERE DayName LIKE 'Sunday'
WHERE DayName NOT IN ('Saturday','Sunday') -- For Weekday
WHERE DayName LIKE 'Monday' -- For Monday
WHERE DayName LIKE 'Sunday' -- For Sunday
*/
OPTION (MaxRecursion 370)
Will this help
DECLARE #startDate DATETIME, #endDate DATETIME
SELECT #startDate = '2012-01-01', #endDate = '2012-12-31'
;WITH Calender AS (
SELECT #startDate AS dt
UNION ALL
SELECT dt + 1 FROM Calender
WHERE dt + 1 <= #endDate
)
SELECT
dt
,NameMonth = DATENAME(Month, dt)
,NameDay = DATENAME (Weekday,dt)
,WeekofYr = DATEPART(WEEK, dt) FROM Calender
WHERE DATENAME (Weekday,dt) IN ('Sunday')
Option(MaxRecursion 0)
Result(Partial)
dt NameMonth NameDay WeekofYr
2012-01-01 00:00:00.000 January Sunday 1
2012-01-08 00:00:00.000 January Sunday 2
...............................................
...............................................
2012-12-30 00:00:00.000 December Sunday 53
you can try this
DECLARE #FirstDateOfYear DATETIME
SET #FirstDateOfYear = ’2010-01-01′
SELECT DISTINCT DATEADD(d, number, #FirstDateOfYear),
CASE DATEPART(dw, DATEADD(d, number, #FirstDateOfYear))
WHEN 7 THEN ‘Saturday’
WHEN 1 THEN ‘Sunday’
ELSE ‘Work Day’
END
FROM master..spt_values
WHERE number BETWEEN 0 AND 364
AND (DATEPART(dw, DATEADD(d, number, #FirstDateOfYear)) = 1 OR DATEPART(dw, DATEADD(d, number, #FirstDateOfYear)) = 7)
ORDER BY DATEADD(d, number, #FirstDateOfYear)
Try to find the first Saturday by doing this:
Start on 2012-01-01
If it's not a Saturday, add a day
Goto 2
Then, into a temporary table, add that date and the following date (Sunday).
After that, loop the following:
Add 7 and 8 days to the last Saturday you found (you get the following Saturday and Sunday)
Check whether they are still in 2012
If they are, store them in temp table and goto 1
There may be more elegant ways, but that's my quick & dirty solution. As you didn't post any code of what you've tried, I'll leave the implementation up to you.
this also works
declare #dat datetime, #add int
set #dat = '20120101'
set #add = datepart(w,#dat)
set #add = 5 - #add -- friday
set #dat = dateadd(d,#add,#dat)
while #dat <= '20121231'
begin
print #dat
set #dat = dateadd(d,7,#dat)
end
;with AllDaysOfYear (Day) as (
select DATEADD(year,DATEDIFF(year,0,CURRENT_TIMESTAMP),0) --Jan 1st
union all
select DATEADD(day,1,Day) from AllDaysOfYear
where DATEPART(year,DATEADD(day,1,Day)) = DATEPART(year,CURRENT_TIMESTAMP)
)
select
ROW_NUMBER() OVER (ORDER BY Day) as WeekNo,
Day
from
AllDaysOfYear
where
DATEPART(weekday,Day) = DATEPART(weekday,'20120714')
option (maxrecursion 0)
First, generate a set of all of the days in the current year (AllDaysInYear). Then, select those whose weekday is a saturday. The value I've used ('20120714') isn't terribly important - it just has to be any saturday, from any year. I'm just using it to avoid needing to have particular DATEFIRST or language settings.
This query shows how to get the first day of this year and the first day of the next year in the first part. The first day of the next year is calculated once so as not to keep getting and comparing the year parts.
;WITH cte(TheDate,NextYear) AS
(
SELECT CAST(CONVERT(CHAR(4),GETDATE(),112)+'0101' AS DATETIME),
CAST(YEAR(GETDATE())*10000+10101 AS CHAR(8))
UNION ALL
SELECT DateAdd(d,1,TheDate),NextYear
FROM cte
WHERE DateAdd(d,1,TheDate)<NextYear
)
SELECT Week = DatePart(wk,TheDate),
TheDate
FROM cte
WHERE DateName(dw,TheDate) in ('Saturday')
ORDER BY TheDate
OPTION (MAXRECURSION 366)
with t as
(
select 1 b
union all
select 1 b
union all
select 1 b
union all
select 1 b
union all
select 1 b
union all
select 1 b
union all
select 1 b
union all
select 1 b
)
select * from
(
select
current_timestamp
-datepart(dy,current_timestamp)
+row_number() over (order by t.b) d
from t, t t1, t t2
) tmp
where datepart(yyyy,d)=datepart(yyyy,current_timestamp)
and
DATENAME(dw,d)='sunday'
DECLARE #Year AS INT
SELECT #Year = 2020
;WITH weekends AS (
SELECT DATEFROMPARTS(#Year, 1, 1) AS dt
UNION ALL
SELECT DATEADD(DAY, 1, dt)
FROM weekends
WHERE dt < DATEFROMPARTS(#Year, 12, 31)
)
SELECT dt, DATENAME(MONTH, dt), DATENAME(DW, dt)
FROM weekends
WHERE DATEPART(DW, dt) IN (1, 7)
OPTION(MaxRecursion 366)

Getting Number of weeks in a Month from a Datetime Column

I have a table called FcData and the data looks like:
Op_Date
2011-02-14 11:53:40.000
2011-02-17 16:02:19.000
2010-02-14 12:53:40.000
2010-02-17 14:02:19.000
I am looking to get the Number of weeks in That Month from Op_Date. So I am looking for output like:
Op_Date Number of Weeks
2011-02-14 11:53:40.000 5
2011-02-17 16:02:19.000 5
2010-02-14 12:53:40.000 5
2010-02-17 14:02:19.000 5
This page has some good functions to figure out the last day of any given month: http://www.sql-server-helper.com/functions/get-last-day-of-month.aspx
Just wrap the output of that function with a DATEPART(wk, last_day_of_month) call. Combining it with an equivalent call for the 1st-day-of-week will let you get the number of weeks in that month.
Use this to get the number of week for ONE specific date. Replace GetDate() by your date:
declare #dt date = cast(GetDate() as date);
declare #dtstart date = DATEADD(day, -DATEPART(day, #dt) + 1, #dt);
declare #dtend date = dateadd(DAY, -1, DATEADD(MONTH, 1, #dtstart));
WITH dates AS (
SELECT #dtstart ADate
UNION ALL
SELECT DATEADD(day, 1, t.ADate)
FROM dates t
WHERE DATEADD(day, 1, t.ADate) <= #dtend
)
SELECT top 1 DatePart(WEEKDAY, ADate) weekday, COUNT(*) weeks
FROM dates d
group by DatePart(WEEKDAY, ADate)
order by 2 desc
Explained: the CTE creates a result set with all dates for the month of the given date. Then we query the result set, grouping by week day and count the number of occurrences. The max number will give us how many weeks the month overlaps (premise: if the month has 5 Mondays, it will cover five weeks of the year).
Update
Now, if you have multiple dates, you should tweak accordingly, joining your query with the dates CTE.
Here is my take on it, might have missed something.
In Linq:
from u in TblUsers
let date = u.CreateDate.Value
let firstDay = new DateTime(date.Year, date.Month, 1)
let lastDay = firstDay.AddMonths(1)
where u.CreateDate.HasValue
select Math.Ceiling((lastDay - firstDay).TotalDays / 7)
And generated SQL:
-- Region Parameters
DECLARE #p0 Int = 1
DECLARE #p1 Int = 1
DECLARE #p2 Float = 7
-- EndRegion
SELECT CEILING(((CONVERT(Float,CONVERT(BigInt,(((CONVERT(BigInt,DATEDIFF(DAY, [t3].[value], [t3].[value2]))) * 86400000) + DATEDIFF(MILLISECOND, DATEADD(DAY, DATEDIFF(DAY, [t3].[value], [t3].[value2]), [t3].[value]), [t3].[value2])) * 10000))) / 864000000000) / #p2) AS [value]
FROM (
SELECT [t2].[createDate], [t2].[value], DATEADD(MONTH, #p1, [t2].[value]) AS [value2]
FROM (
SELECT [t1].[createDate], CONVERT(DATETIME, CONVERT(NCHAR(2), DATEPART(Month, [t1].[value])) + ('/' + (CONVERT(NCHAR(2), #p0) + ('/' + CONVERT(NCHAR(4), DATEPART(Year, [t1].[value]))))), 101) AS [value]
FROM (
SELECT [t0].[createDate], [t0].[createDate] AS [value]
FROM [tblUser] AS [t0]
) AS [t1]
) AS [t2]
) AS [t3]
WHERE [t3].[createDate] IS NOT NULL
According to this MSDN article: http://msdn.microsoft.com/en-us/library/ms174420.aspx you can only get the current week in the year, not what that month returns.
There may be various approaches to implementing the idea suggested by #Marc B. Here's one, where no UDFs are used but the first and the last days of month are calculated directly:
WITH SampleData AS (
SELECT CAST('20110214' AS datetime) AS Op_Date
UNION ALL SELECT '20110217'
UNION ALL SELECT '20100214'
UNION ALL SELECT '20100217'
UNION ALL SELECT '20090214'
UNION ALL SELECT '20090217'
),
MonthStarts AS (
SELECT
Op_Date,
MonthStart = DATEADD(DAY, 1 - DAY(Op_Date), Op_Date)
/* alternatively: DATEADD(MONTH, DATEDIFF(MONTH, 0, Op_Date), 0) */
FROM FcData
),
Months AS (
SELECT
Op_Date,
MonthStart,
MonthEnd = DATEADD(DAY, -1, DATEADD(MONTH, 1, MonthStart))
FROM FcData
)
Weeks AS (
SELECT
Op_Date,
StartWeek = DATEPART(WEEK, MonthStart),
EndWeek = DATEPART(WEEK, MonthEnd)
FROM MonthStarts
)
SELECT
Op_Date,
NumberOfWeeks = EndWeek - StartWeek + 1
FROM Weeks
All calculations could be done in one SELECT, but I chose to split them into steps and place every step in a separate CTE so it could be seen better how the end result was obtained.
You can get number of weeks per month using the following method.
Datepart(WEEK,
DATEADD(DAY,
-1,
DATEADD(MONTH,
1,
DATEADD(DAY,
1 - DAY(GETDATE()),
GETDATE())))
-
DATEADD(DAY,
1 - DAY(GETDATE()),
GETDATE())
+1
)
Here how you can get accurate amount of weeks:
DECLARE #date DATETIME
SET #date = GETDATE()
SELECT ROUND(cast(datediff(day, dateadd(day, 1-day(#date), #date), dateadd(month, 1, dateadd(day, 1-day(#date), #date))) AS FLOAT) / 7, 2)
With this code for Sep 2014 you'll get 4.29 which is actually true since there're 4 full weeks and 2 more days.