Below is a list of attendance in a table. Every Month i would need to monitor the attendance based on the cut-off period starting 20/previous month - 19/current month.
Date Branch Attendance
13/04/2017 Branch A 5
14/04/2017 Branch A 5
22/04/2017 Branch A 5
30/04/2017 Branch A 5
17/05/2017 Branch A 6
18/05/2017 Branch A 6
01/04/2017 Branch B 17
15/04/2017 Branch B 14
20/04/2017 Branch B 14
19/05/2017 Branch B 17
20/05/2017 Branch B 15
25/05/2017 Branch B 17
For Example; I would like to show in a Month of May,where the cut-off period will be 20/4/2017 - 19/5/2017.
Below are my code:
SELECT
CONVERT(VARCHAR,Date,103) AS Date,
Branch,
Attendance
FROM
Table_attd
WHERE
Date>=DATEADD(DAY,(SELECT DATEDIFF (DAY,(DATEADD(DAY,0,GETEDATE())),
(DATEADD(MONTH,-1,CONVERT(DATETIME,CAST(YEAR(GETDATE()) AS
VARCHAR)+'/'+CAST(MONTH(GETDATE()) AS
VARCAHR)+'/'+'19',102))))),GETDATE())
AND Date<=DATEADD(DAY,-1,GETDATE())
The results:
Date Branch Attendance
22/04/2017 Branch A 5
30/04/2017 Branch A 5
17/05/2017 Branch A 6
18/05/2017 Branch A 6
20/04/2017 Branch B 14
19/05/2017 Branch B 17
But I realized that my code has a flaw where When the month of Jun (cut-off period will be 20/5/2017 - 19/6/2017), it result will be incorrect.
Kindly advice - Thanks!
Your query is not a correct syntax, you could use this
DECLARE #CurrentDate date = getdate()
DECLARE #20PreviousMonth date = dateadd(day,19,dateadd(month,datediff(month,0,#CurrentDate) - 1,0))
DECLARE #19CurrentMonth date = dateadd(day,18,dateadd(month,datediff(month,0,#CurrentDate) ,0))
SELECT
CONVERT(VARCHAR,[Date],103) AS Date,
Branch,
Attendance
FROM
Table_attd
WHERE
[Date] BETWEEN #20PreviousMonth AND #19CurrentMonth
...where [date] between dateadd(day, 19, dateadd(month, -1, cast(convert(char(6), getdate(), 112) + '01' as date)))
and dateadd(day, 18, cast(convert(char(6), getdate(), 112) + '01' as date))
An alternative way to solve this is to create a 'Date' table .
Date CalendarMonth ReportingMonth
2017-19-05 May May
2017-20-05 May June
Then simply left join this table to your fact table and group by 'ReportingMonth.' This allows you to quickly call on data from any time period, and allows for flexibility in the cut-over date.
Or you can try this.
DECLARE #previous DATETIME = CAST(DATEADD(DAY,
( DATEDIFF(DAY,
( DATEADD(DAY, 0,
GETDATE()) ),
( DATEADD(MONTH,
-1,
CONVERT(DATETIME, CAST(YEAR(GETDATE()) AS VARCHAR)
+ '/'
+ CAST(MONTH(GETDATE()) AS VARCHAR)
+ '/' + '19', 102)) )) ),
GETDATE()) AS DATE)
DECLARE #current DATETIME = CAST(DATEADD(DAY,
( DATEDIFF(DAY,
( DATEADD(DAY, 0,
GETDATE()) ),
( DATEADD(MONTH,
0,
CONVERT(DATETIME, CAST(YEAR(GETDATE()) AS VARCHAR)
+ '/'
+ CAST(MONTH(GETDATE()) AS VARCHAR)
+ '/' + '20', 102)) )) ),
GETDATE()) AS DATE)
SELECT [Date],
Branch ,
Attendance
FROM ( SELECT CONVERT(VARCHAR, Date, 103) AS [Date] ,
Branch ,
Attendance
FROM Table_attd
) T
WHERE ( t.Date >= #previous
AND t.Date <= #current
)
Related
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.
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
-----------------------------|---------------------------------**
I want weekly totals in a month. It will not include any partial week or future weeks. Week starts from Monday to Sunday.
I have a table structure like
Date Value -- Comments
----------------------------------------------------------------------
2016-10-01 7 Ignore this because its not a whole week in a month
2016-10-05 8 Week 1
2016-10-07 5 Week 1
2016-10-11 2 Week 2
2016-10-15 1 Week 2
2016-10-17 9 Ignore this because the week is not finished yet
OUTPUT
WeekNo Total
41 13
42 3
The easier way would be to build a Tally "date" table.
you can generate it from any Tally Table like:
DECLARE #StartDate DATE = '20160101'
, #EndDate DATE = '20161231';
WITH cte AS (
SELECT DATEADD(DAY, n - 1, #StartDate) AS date
FROM tally
WHERE n - 1 <= DATEDIFF(DAY, #StartDate, #EndDate)
)
SELECT
c.date
,YEAR(c.date) AS Year
,MONTH(c.date) AS Month
,DAY(c.date) AS Month
,DATEPART(WEEK,c.date) AS Week
,CASE WHEN 7<>COUNT(c.date) OVER (PARTITION BY YEAR(c.date),MONTH(c.date),DATEPART(WEEK,c.date)) THEN 0 ELSE 1 END AS isFullWeek
FROM cte c
Then you just need to Join it to what ever query you need.
DECLARE #StartDate datetime = '2011-10-01';
DECLARE #EndDate datetime = '2016-10-31';
SELECT
CAST(DATEADD(dd, -DATEPART(dw, tblData.RecordDate) + 2, tblData.RecordDate) AS date) AS WeekStart,
CAST(DATEADD(dd, -DATEPART(dw, tblData.RecordDate) + 8, tblData.RecordDate) AS date) AS WeekEnd,
SUM(Value) AS Total
FROM tblData
WHERE (#StartDate IS NULL
OR CAST(DATEADD(dd, -DATEPART(dw, tblData.RecordDate) + 2, tblData.RecordDate) AS date) >= CAST(#StartDate AS date))
AND (#EndDate IS NULL
OR CAST(DATEADD(dd, -DATEPART(dw, tblData.RecordDate) + 8, tblData.RecordDate) AS date) <= CAST(#EndDate AS date))
AND CAST(DATEADD(dd, -DATEPART(dw, tblData.RecordDate) + 8, tblData.RecordDate) AS date) < CAST(GETDATE() AS date)
GROUP BY CAST(DATEADD(dd, -DATEPART(dw, tblData.RecordDate) + 2, tblData.RecordDate) AS date),
CAST(DATEADD(dd, -DATEPART(dw, tblData.RecordDate) + 8, tblData.RecordDate) AS date)
Create a calendar table that meets your request, like this:
create table calendarTable ([date] date, weekNro int)
go
insert into calendarTable
select dateadd(d,n,'20160101'), DATEPART(WK,dateadd(d,n,'20151231'))
from numbers where n<500
If you don't have a Numbers Table, you must create it first. like this
SET NOCOUNT ON
CREATE TABLE Numbers (n bigint PRIMARY KEY)
GO
DECLARE #numbers table(number int);
WITH numbers(number) as (
SELECT 1 AS number
UNION all
SELECT number+1 FROM numbers WHERE number<10000
)
INSERT INTO #numbers(number)
SELECT number FROM numbers OPTION(maxrecursion 10000)
INSERT INTO Numbers(n) SELECT number FROM #numbers
Then query your table joining calendar table having in mind actual date for completed week, like this:
Similar to #Kilren but translated into postgres and using generate series from https://stackoverflow.com/a/11391987/10087503 to generate the dates
DECLARE #StartDate DATE = '20160101'
, #EndDate DATE = '20161231';
WITH cte AS (
SELECT i::date AS date FROM generate_series(#StartDate,
#EndDate, '1 day'::interval) i
)
SELECT
c.date
,DATE_TRUNC('month' ,c.date) AS month_trunc
,DATE_PART('week',c.date) AS week
,CASE WHEN 7<>COUNT(c.date)
OVER (PARTITION BY DATE_TRUNC('month' ,c.date),DATE_PART('week',c.date))
THEN 0 ELSE 1 END AS is_full_week
FROM cte c
Select DATEPART(ww, date) , SUM(Case When Comments Like '%1' then Value when Comments Like '%2' then Value else Value end)
from schema.tablename
group by DATEPART(ww,date)
I'm sorry if this doesn't work, it's the only way I thought to structure it.
I have a table in SQL server that looks like this (there are other fields, but there are the only 2 I care about):
-----------------------------------------
| DraftDay (int)| MonthlyFee (money)|
-----------------------------------------
1 18.75
2 15.25
2 15.25
3 15.25
3 28.12
4 15.25
4 3.75
4 18.19
5 12.75
6 13.80
6 14.25
... ...
... ...
What I am trying to do is write a query that will group by DraftDay and sum the MonthlyFee field. Simple enough. But the hard part is, I need to get the date of one month ago, plus one day and pull the days from that day to the current day. For example, if today is 4/3/2014, then I need to pull all days between 3/4/2014 and 4/3/2014. My result set should look like this:
3/4/2014 37.19
3/5/2014 12.75
3/6/2014 28.05
... ...
... ...
4/1/2014 18.75
4/2/2014 30.50
4/3/2014 43.37
I have created this query:
SELECT
CAST(CAST(DATEPART(MONTH, DATEADD(DAY, 1, DATEADD(MONTH, -1, GETDATE()))) AS VARCHAR(2)) + '/' +
CAST(CASE WHEN EftDraftDay>28 THEN 28 ELSE EftDraftDay END AS VARCHAR(2)) + '/' +
CAST(DATEPART(YEAR, DATEADD(DAY, 1, DATEADD(MONTH, -1, GETDATE()))) AS VARCHAR(4)) AS DATETIME) AS DraftDate,
SUM(MonthlyPayment) AS MonthlyPayment FROM dbo.Advertiser
WHERE IsDeleted=0
AND IsPaidInFull=0
AND IsAdvertiserActive=1
AND EftDraftDay>0
AND (CAST(CAST(DATEPART(MONTH, DATEADD(DAY, 1, DATEADD(MONTH, -1, GETDATE()))) AS VARCHAR(2)) + '/' +
CAST(CASE WHEN EftDraftDay>28 THEN 28 ELSE EftDraftDay END AS VARCHAR(2)) + '/' +
CAST(DATEPART(YEAR, DATEADD(DAY, 1, DATEADD(MONTH, -1, GETDATE()))) AS VARCHAR(4)) AS DATETIME)) >=
CAST(CAST(DATEPART(MONTH, DATEADD(DAY, 1, DATEADD(MONTH, -1, GETDATE()))) AS VARCHAR(2)) + '/' +
CAST(DATEPART(DAY, DATEADD(DAY, 1, DATEADD(MONTH, -1, GETDATE()))) AS VARCHAR(2)) + '/' +
CAST(DATEPART(YEAR, DATEADD(DAY, 1, DATEADD(MONTH, -1, GETDATE()))) AS VARCHAR(4)) AS DATETIME)
GROUP BY
CAST(CAST(DATEPART(MONTH, DATEADD(DAY, 1, DATEADD(MONTH, -1, GETDATE()))) AS VARCHAR(2)) + '/' +
CAST(CASE WHEN EftDraftDay>28 THEN 28 ELSE EftDraftDay END AS VARCHAR(2)) + '/' +
CAST(DATEPART(YEAR, DATEADD(DAY, 1, DATEADD(MONTH, -1, GETDATE()))) AS VARCHAR(4)) AS DATETIME)
Because today's date is 4/17/2014, I should be getting results back from 3/18/2014 thru 4/17/2014. The results I'm getting back are only from 3/18/2014 thru 3/28/2014. How can I also add 4/1/2014 thru 4/17/2014 into my result set? I thought about a UNION, but those don't seem to work well with GROUP BY's.
After trying to figure this out and looking at my query, I think I am totally over engineering this. My query looks so complex for such a simple thing. Can anyone please help me out with this?
Try this:
declare #lastmonth date = convert(date,dateadd(day,1,dateadd(month,-1,getdate())))
declare #thismonth date = convert(date,getdate())
select
case
when draftday < datepart(day,#thismonth)
then convert(date, cast(datepart(yyyy,#thismonth) as varchar) + right('00'+cast(datepart(MM,#thismonth) as varchar),2) + right('00'+cast(cast(draftday as varchar) as varchar),2))
else convert(date, cast(datepart(yyyy,#lastmonth) as varchar) + right('00'+cast(datepart(MM,#lastmonth) as varchar),2) + right('00'+cast(cast(draftday as varchar) as varchar),2))
end,
sum(monthlyfee) from tbl
group by draftday
This will treat days before current day as belonging to current month, and others as from last month, and then sum up. Please let me know if this is doing what you expect.
I'd use the Calendar table (http://www.dbdelta.com/calendar-table-and-datetime-functions/), something like this:
declare #d datetime ='20140403'
select c.CalendarDate, sum(#t.MonthlyFee)
from calendar c
left join #t on c.CalendarDay = #t.DraftDay
where CalendarDate between dateadd(month, -1, #d)+1 and #d
group by c.CalendarDate
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.