SQL Server find the last week of the last 2 months - sql

INSERT INTO #blah (ID, Date)
VALUES (123, '11/12/2012')
VALUES (124, '11/30/2012')
VALUES (125, '11/28/2012')
VALUES (126, '12/1/2012')
VALUES (127, '12/30/2012')
VALUES (128, '12/25/2012')
VALUES (129, '12/26/2012')
I want to get rows where the date is the last week of the respective month going back two months. This month is Jan 2013, so i want the last week of Dec 2012 and Nov 2012.
The ultimate option would be the last full week of a month example: dec 2012 = 12/23-12/29 but for now ill take the last 7 days of the month.
I know how to get the last two months but unsure how to get the last week of the respective month..
select
*
from
#blah
where
dateDiff(month,date,getdate()) < 2 ---only look at the last two months

This meets the stated requirement (last full week of previous two months):
SET DATEFIRST 1;
DECLARE #s DATE = GETDATE(), #s1 DATE, #s2 DATE;
SET #s = GETDATE();
-- last day of last month:
SET #s1 = DATEADD(DAY, -DAY(#s), #s);
-- last day of previous month:
SET #s = DATEADD(MONTH, -1, #s);
SET #s2 = DATEADD(DAY, -DAY(#s), #s);
SELECT
#s1 = DATEADD(DAY, -7, DATEADD(DAY, -DATEPART(WEEKDAY, #s1) % 7, #s1)),
#s2 = DATEADD(DAY, -7, DATEADD(DAY, -DATEPART(WEEKDAY, #s2) % 7, #s2));
SELECT col1, col2, etc.
FROM dbo.table
WHERE
(date_column >= #s1 AND date_column < DATEADD(DAY, 7, #s1)
OR
(date_column >= #s2 AND date_column < DATEADD(DAY, 7, #s2);
To make this more dynamic (you should do your best to state these requirements FIRST, not after people have put in a bunch of work), you can say:
DECLARE #NumberOfMonthsIReallyWanted INT = 3;
DECLARE #i INT = 1, #d DATE = GETDATE();
DECLARE #t TABLE(d DATE);
WHILE #i <= #NumberOfMonthsIReallyWanted
BEGIN
SET #d = DATEADD(MONTH, -#i, #d)
INSERT #t(s) SELECT DATEADD(DAY, -7, DATEADD(DAY,
-DATEPART(WEEKDAY, DATEADD(DAY, -DAY(#d), #d)) % 7,
DATEADD(DAY, -DAY(#d), #d)));
SET #i += 1;
END
SELECT src.col1, src.col2, etc.
FROM dbo.table AS src
INNER JOIN #t AS t
ON src.date_column >= t.d AND src.date_column < DATEADD(DAY, 7, t.d);
Please don't let anyone convince you to use LIKE for date comparison queries. Not only does this kill sargability (meaning no index can be used), but, for a problem like this, how do you determine what string patterns to match? The difficulty is not in constructing the WHERE clause, but rather what to fill in for the magic (Your Dates go here) placeholder. And when you do find the range of dates, do you really want 14 individual LIKE expressions? I wouldn't.

declare #blah table (ID int, [Date] datetime)
INSERT INTO #blah (ID, [Date])
select 123, '20121112'
union select 124, '20121130'
union select 125, '20121128'
union select 126, '20121201'
union select 127, '20121230'
union select 128, '20121225'
union select 129, '20121226'
select ID, [Date], datepart(week, [Date])
from #blah
where
datediff(month, [Date], getdate()) in (1,2)
and
datepart(week, [Date]) = datepart(
week,
dateadd(
day,
-datepart(day,dateadd(month, 1, [Date])),
dateadd(month, 1, [Date])))

This works in Oracle - may give you some ideas and hopefully helps:
-- Last weeks of last two months --
SELECT mydate
, TRUNC(mydate, 'iw') wk_starts
, TRUNC(mydate, 'iw') + 7 - 1/86400 wk_ends
, TO_NUMBER (TO_CHAR (mydate, 'IW')) ISO_wk#
FROM
( -- Last week = Last day of the year (hardcoded) - 1 week --
SELECT(CASE WHEN start_date = To_Date('12/31/2012', 'mm/dd/yyyy') THEN start_date-7 ELSE start_date END) mydate
FROM
( -- Last 2 months --
SELECT Add_Months(Last_Day(Trunc(SYSDATE)), - LEVEL) start_date
FROM dual
CONNECT BY LEVEL <= 2
)
)
/

This is somewhat dependent on your sql server. This example is similar to Oracle. Each server has its own functions, like SYSDATE and NOW.
For example.
SELECT * from blah WHERE TO_CHAR(TRUNC(data), MM/DD/YYYY) < '01/14/2013'

Related

How to get the last sunday of the year using TSQL?

I need to check if a given day is the last sunday of any year, if yes the return 1 using TSQL only.
I do not have much idea about TSQL.
SQL Server has a problem with weekdays, because they can be affected by internationalization settings. Assuming the defaults, you can do:
select dateadd(day,
1 - datepart(weekday, datefromparts(#year, 12, 31)),
datefromparts(#year, 12, 31)
)
Otherwise, you'll need to do a case expression to turn the day of the week into a number.
In an older version of SQL Server, you could do:
select dateadd(day,
1 - datepart(weekday, cast(#year + '0101' as date)),
cast(#year + '0101' as date)
)
I haven't worked with tsql specifically but if my sql knowledge and googling is good enough then something like this should do the trick:
... WHERE DATEPART(dw, date) = 7 and DATEDIFF (d, date, DATEFROMPARTS (DATEPART(yyyy, date), 12, 31)) <= 6
Basically we check if that day is Sunday at first and then if it's less than week away from last day of the year
Using Mr. Gordon's query, following IIF() returns 1 if given day is last Sunday of the year, returns 0 if it is not.
Using 2018 as year and 2018-12-30 as given date. You can replace values with variables.
select IIF( DATEDIFF(DAY,'2018-12-30',
DATEADD(day,
1 - datepart(weekday, datefromparts(2018, 12, 31)),
datefromparts(2018, 12, 31)
)) = 0, 1, 0)
You can use this function
Function Code :
create FUNCTION CheckIsSaturday
(
#date DATETIME
)
RETURNS int
AS
BEGIN
-- Declare the return variable here
DECLARE #result INT
DECLARE #DayOfWeek NVARCHAR(22)
DECLARE #LastDayOfYear DATETIME
select #LastDayOfYear=DATEADD(yy, DATEDIFF(yy, 0, #date) + 1, -1)
SELECT #DayOfWeek=DATENAME(dw, #date)
IF(#DayOfWeek='Saturday' AND DATEDIFF(dd,#date,#LastDayOfYear)<7)
RETURN 1;
RETURN 0;
END
GO
function Usage:
SELECT dbo.CheckIsSaturday('2017-12-23')
This becomes quite trivial if you have a Calendar Table
DECLARE #CheckDate DATE = '20181230'
;WITH cteGetDates AS
(
SELECT
[Date], WeekDayName, WeekOfMonth, [MonthName], [Year]
,LastDOWInMonth = ROW_NUMBER() OVER
(
PARTITION BY FirstDayOfMonth, [Weekday]
ORDER BY [Date] DESC
)
FROM
dbo.DateDimension
)
SELECT * FROM cteGetDates D
WHERE D.LastDOWInMonth = 1 AND D.WeekDayName = 'Sunday' and D.MonthName = 'December' AND D.[Date] = #CheckDate
You can also use this one to get every last day of the year:
;WITH getlastdaysofyear ( LastDay, DayCnt ) AS (
SELECT DATEADD(dd, -DAY(DATEADD(mm, 1, DATEADD(yy, DATEDIFF(yy, 0, GETDATE()) + 1, -1))),
DATEADD(mm, 1, DATEADD(yy, DATEDIFF(yy, 0, GETDATE()) + 1, -1))),
0 AS DayCnt
UNION ALL
SELECT LastDay,
DayCnt + 1
FROM getlastdaysofyear
)
SELECT *
FROM ( SELECT TOP 7 DATEADD(DD, -DayCnt, LastDay) LastDate,
'Last ' + DATENAME(Weekday,DATEADD(DD, -DayCnt, LastDay)) AS DayStatus
FROM getlastdaysofyear ) T
ORDER BY DATEPART(Weekday, LastDate)
Hope you like it :)

Simplest way to fill working day table

I have a table working_days with one column date of type date
I need to fill it with working days in USA.
Can you suggest how can I so this?
Manually it is too long.
You can use a recursive CTE to accomplish this. This only excludes the weekends. Using DATEFIRST you can figure out what day is a weekend. This query should work no matter what day of the week is set to DATEFIRST.
;WITH DatesCTE
AS (
SELECT CAST('2016-01-01' AS DATE) AS [workingDays]
UNION ALL
SELECT DATEADD(DAY, 1, workingdays)
FROM DatesCTE
WHERE DATEADD(DAY, 1, workingdays) < '2017-01-01'
)
SELECT *
FROM DatesCTE
WHERE ((DATEPART(dw, workingDays) + ##DATEFIRST) % 7) NOT IN (0, 1)
OPTION (MAXRECURSION 366)
At first fill your table with all dates for year (for example 2016):
DECLARE #date_start date = '2016-01-01',
#date_end date = '2016-12-31';
WITH cte as (
SELECT #date_start as [d], 0 as Level
UNION ALL
SELECT DATEADD(day,1,[d]), [level] + 1 as [level]
from cte
WHERE [level] < DATEDIFF(day,#date_start,#date_end)
),
holidays as ( --table with holidays
SELECT * FROM (VALUES
('2016-01-01'),
('2016-01-18'),
('2016-02-15'),
('2016-05-30'),
('2016-07-04'),
('2016-09-05'),
('2016-10-10'),
('2016-11-11'),
('2016-11-24'),
('2016-12-26')) as t(d)
)
SELECT c.d
FROM cte c
LEFT JOIN holidays h on c.d=h.d
WHERE DATEPART(WEEKDAY,d) NOT IN (1,7) --will show only monday-friday
AND AND h.d is NULL
OPTION (MAXRECURSION 1000); --if you need more than 3 years get MAXRECURSION up
A simple loop will do:
declare #d date = '20160101';
while #d <= '20161231'
begin
if datepart(weekday, #d) not in (1, 7) and <#d not a holiday>
insert into working_days ("date") values (#d);
set #d = dateadd(day, 1, #d);
end

difference between two dates without weekends and holidays Sql query ORACLE

I have 2 tables: the 1st one contains the start date and the end date of a purchase order,
and the 2nd table contains year hollidays
-purchase order
-Holidays
I'm tryign to calculate the number of business days between 2 dates without the weekends and the holidays.
the output should be like this:
Start Date | End Date | Business Days
Could you please help me
You can remove the non-weekend holidays with a query like this:
select (t.end_date - t.start_date) - count(c.date)
from table1 t left join
calendar c
on c.date between t1.start_date and t1.end_date and
to_char(c.date, 'D') not in ('1', '7')
group by t.end_date, t.start_date;
Removing the weekend days is then more complication. Full weeks have two weekend days, so that is easy. So a good approximation is:
select (t.end_date - t.start_date) - (count(c.date) +
2 * floor((t.end_date - t.start_date) / 7))
from table1 t left join
calendar c
on c.date between t1.start_date and t1.end_date and
to_char(c.date, 'D') not in ('1', '7')
group by t.end_date, t.start_date;
This doesn't get the day of week, which is essentially if the end date is before the start date, then it is in the following week. However, this logic gets rather complicated the way that Oracle handles day of the week, so perhaps the above approximation is sufficient.
EDIT: I ignored the presence of the Oracle tag and jumped into scripting this for SQL Server. The concept doesn't change though.
To be super accurate, I would create a table whit the following format.
Year int, month int, DaysInMonth int, firstOccuranceOfSunday int
Create a Procedure to extract the weekends from a specific year and month on that table.
CREATE FUNCTION [dbo].[GetWeekendsForMonthYear]
(
#year int,
#month int
)
RETURNS #weekends TABLE
(
[Weekend] date
)
AS
BEGIN
declare #firstsunday int = 0
Declare #DaysInMonth int = 0
Select #DaysInMonth = DaysInMonth, #firstsunday = FirstSunday from Months
Where [Year] = #year and [month] = #month
Declare #FirstSaterday int = #firstsunday - 1
declare #CurrentDay int = 0
Declare #CurrentDayIsSunday bit = 0
if #FirstSaterday !< 1
Begin
insert into #Weekends values(DATEADD(year, #year -1900, DATEADD(month, #month -1, DATEADD(day, #Firstsaterday -1, 0))))
insert into #Weekends values(DATEADD(year, #year -1900, DATEADD(month, #month -1, DATEADD(day, #FirstSunday -1, 0))))
set #CurrentDayIsSunday = 1
set #CurrentDay = #firstsunday
END
else
begin
insert into #Weekends values(DATEADD(year, #year -1900, DATEADD(month, #month -1, DATEADD(day, #FirstSunday -1, 0))))
set #FirstSaterday = #firstsunday + 6
insert into #Weekends values(DATEADD(year, #year -1900, DATEADD(month, #month -1, DATEADD(day, #Firstsaterday -1, 0))))
set #CurrentDayIsSunday = 0
set #CurrentDay = #FirstSaterday
end
declare #done bit = 0
while #done = 0
Begin
if #CurrentDay <= #DaysInMonth
Begin
If #CurrentDayIsSunday = 1
begin
set #CurrentDay = #CurrentDay + 6
set #CurrentDayIsSunday = 0
if #CurrentDay <= #DaysInMonth
begin
insert into #Weekends Values(DATEADD(year, #year -1900, DATEADD(month, #month -1, DATEADD(day, #CurrentDay -1, 0))))
end
end
else
begin
set #CurrentDay = #CurrentDay + 1
set #CurrentDayIsSunday = 1
if #CurrentDay <= #DaysInMonth
begin
insert into #Weekends Values(DATEADD(year, #year -1900, DATEADD(month, #month -1, DATEADD(day, #CurrentDay -1, 0))))
end
end
end
ELSE
begin
Set #done = 1
end
end
RETURN
END
When called and provided with a year and month this will return a list of dates that represent weekends.
Now, using that function, create a procedure to call this function once for every applicable row in a specific date rang and return the values in a temptable.
Note, I'm posting this now so you can see what's going on but I am continuing to work on the code. I will post updates as they arise.
More to come: Get list of weekends(formatted) for a specific daterange, remove any dates from that list which can be found on your holidays table.
Unfortunately, I have to work tomorrow and am off to bed.
This query should produce exact number of business days for each range in purchase table:
with days as (
select rn, sd + level - 1 dt, sd, ed
from (select row_number() over (order by start_date) rn,
start_date sd, end_date ed from purchase_order)
connect by prior rn = rn and sd + level - 1 <= ed
and prior dbms_random.value is not null)
select sd start_date, ed end_date, count(1) business_days
from days d left join holidays h on holiday_date = d.dt
where dt - trunc(dt, 'iw') not in (5, 6) and h.holiday_date is null
group by rn, sd, ed
SQLFiddle demo
For each row in purchase_orders query generates dates from this range (this is done by subquery dates).
Main query checks if this is weekend day or holiday day and counts rest of dates.
Hierarchical query used to generate dates may cause slowdowns if there is big number of data in purchase_orders
or periods are long. In this case preferred way is to create calendar table, as already suggested in comments.
Since you already have a table of holidays you can count the holidays between the starting and ending date and subtract that from the difference in days between your ending and starting date. For the weekends, you either need a table containing weekend days similar to your table of holidays, or you can generate them as below.
with sample_data(id, start_date, end_date) as (
select 1, date '2015-03-06', date '2015-03-7' from dual union all
select 2, date '2015-03-07', date '2015-03-8' from dual union all
select 3, date '2015-03-08', date '2015-03-9' from dual union all
select 4, date '2015-02-07', date '2015-06-26' from dual union all
select 5, date '2015-04-17', date '2015-08-16' from dual
)
, holidays(holiday) as (
select date '2015-01-01' from dual union all -- New Years
select date '2015-01-19' from dual union all -- MLK Day
select date '2015-02-16' from dual union all -- Presidents Day
select date '2015-05-25' from dual union all -- Memorial Day
select date '2015-04-03' from dual union all -- Independence Day (Observed)
select date '2015-09-07' from dual union all -- Labor Day
select date '2015-11-11' from dual union all -- Veterans Day
select date '2015-11-26' from dual union all -- Thanks Giving
select date '2015-11-27' from dual union all -- Black Friday
select date '2015-12-25' from dual -- Christmas
)
-- If your calendar table doesn't already hold weekends you can generate
-- the weekends with these next two subfactored queries (common table Expressions)
, firstweekend(weekend, end_date) as (
select next_day(min(start_date),'saturday'), max(end_date) from sample_data
union all
select next_day(min(start_date),'sunday'), max(end_date) from sample_data
)
, weekends(weekend, last_end_date) as (
select weekend, end_date from firstweekend
union all
select weekend + 7, last_end_date from weekends where weekend+7 <= last_end_date
)
-- if not already in the same table combine distinct weekend an holiday days
-- to prevent double counting (in case a holiday is also a weekend).
, days_off(day_off) as (
select weekend from weekends
union
select holiday from holidays
)
select id
, start_date
, end_date
, end_date - start_date + 1
- (select count(*) from days_off where day_off between start_date and end_date) business_days
from sample_data;
ID START_DATE END_DATE BUSINESS_DAYS
---------- ----------- ----------- -------------
1 06-MAR-2015 07-MAR-2015 1
2 07-MAR-2015 08-MAR-2015 0
3 08-MAR-2015 09-MAR-2015 1
4 07-FEB-2015 26-JUN-2015 98
5 17-APR-2015 16-AUG-2015 85

Group days by week

Is there is a way to group dates by week of month in SQL Server?
For example
Week 2: 05/07/2012 - 05/13/2012
Week 3: 05/14/2012 - 05/20/2012
but with Sql server statement
I tried
SELECT SOMETHING,
datediff(wk, convert(varchar(6), getdate(), 112) + '01', getdate()) + 1 AS TIME_
FROM STATISTICS_
GROUP BY something, TIME_
ORDER BY TIME_
but it returns the week number of month. (means 3)
How to get the pair of days for current week ?
For example, now we are in third (3rd) week and I want to show 05/14/2012 - 05/20/2012
I solved somehow:
SELECT DATEADD(ww, DATEDIFF(ww,0,<my_column_name>), 0)
select DATEADD(ww, DATEDIFF(ww,0,<my_column_name>), 0)+6
Then I will get two days and I will concatenate them later.
All right, bear with me here. We're going to build a temporary calendar table that represents this month, including the days from before and after the month that fall into your definition of a week (Monday - Sunday). I do this in a lot of steps to try to make the process clear, but I probably haven't excelled at that in this case.
We can then generate the ranges for the different weeks, and you can join against your other tables using that.
SET DATEFIRST 7;
SET NOCOUNT ON;
DECLARE #today SMALLDATETIME, #fd SMALLDATETIME, #rc INT;
SELECT #today = DATEADD(DAY, DATEDIFF(DAY, 0, GETDATE()), 0), -- today
#fd = DATEADD(DAY, 1-DAY(#today), #today), -- first day of this month
#rc = DATEPART(DAY, DATEADD(DAY, -1, DATEADD(MONTH, 1, #fd)));-- days in month
DECLARE #thismonth TABLE (
[date] SMALLDATETIME,
[weekday] TINYINT,
[weeknumber] TINYINT
);
;WITH n(d) AS (
SELECT TOP (#rc+12) DATEADD(DAY, ROW_NUMBER() OVER
(ORDER BY [object_id]) - 7, #fd) FROM sys.all_objects
)
INSERT #thismonth([date], [weekday]) SELECT d, DATEPART(WEEKDAY, d) FROM n;
DELETE #thismonth WHERE [date] < (SELECT MIN([date]) FROM #thismonth WHERE [weekday] = 2)
OR [date] > (SELECT MAX([date]) FROM #thismonth WHERE [weekday] = 1);
;WITH x AS ( SELECT [date], weeknumber, rn = ((ROW_NUMBER() OVER
(ORDER BY [date])-1) / 7) + 1 FROM #thismonth ) UPDATE x SET weeknumber = rn;
-- now, the final query given all that (I've only broken this up to get rid of the vertical scrollbars):
;WITH ranges(w,s,e) AS (
SELECT weeknumber, MIN([date]), MAX([date]) FROM #thismonth GROUP BY weeknumber
)
SELECT [week] = CONVERT(CHAR(10), r.s, 120) + ' - ' + CONVERT(CHAR(10), r.e, 120)
--, SOMETHING , other columns from STATISTICS_?
FROM ranges AS r
-- LEFT OUTER JOIN dbo.STATISTICS_ AS s
-- ON s.TIME_ >= r.s AND s.TIME_ < DATEADD(DAY, 1, r.e)
-- comment this out if you want all the weeks from this month:
WHERE w = (SELECT weeknumber FROM #thismonth WHERE [date] = #today)
GROUP BY r.s, r.e --, SOMETHING
ORDER BY [week];
Results with WHERE clause:
week
-----------------------
2012-05-14 - 2012-05-20
Results without WHERE clause:
week
-----------------------
2012-04-30 - 2012-05-06
2012-05-07 - 2012-05-13
2012-05-14 - 2012-05-20
2012-05-21 - 2012-05-27
2012-05-28 - 2012-06-03
Note that I chose YYYY-MM-DD on purpose. You should avoid regional formatting like M/D/Y especially for input but also for display. No matter how targeted you think your audience is, you're always going to have someone who thinks 05/07/2012 is July 5th, not May 7th. With YYYY-MM-DD there is no ambiguity whatsoever.
Create a calendar table, then you can query week numbers, first/last days of specific weeks and months etc. You can also join on it queries to get a date range etc.
How about a case statement?
case when datepart(day, mydatetime) between 1 and 7 then 1
when datepart(day, mydatetime) between 8 and 14 then 2
...
You'll also have to include the year & month unless you want all the week 1s in the same group.
It's not clear of you want to "group dates by week of month", or alternately "select data from a given week"
If you mean "group" this little snippet should get you 'week of month':
SELECT <stuff>
FROM CP_STATISTICS
WHERE Month(<YOUR DATE COL>) = 5 --april
GROUP BY Year(<YOUR DATE COL>),
Month(<YOUR DATE COL>),
DATEDIFF(week, DATEADD(MONTH, DATEDIFF(MONTH, 0, <YOUR DATE COL>), 0)
, <YOUR DATE COL>) +1
Alternately, if you want "sales for week 1 of April, ordered by date" You could do something like..
DECLARE #targetDate datetime2 = '5/3/2012'
DECLARE #targetWeek int = DATEDIFF(week, DATEADD(MONTH,
DATEDIFF(MONTH, 0, #targetDate), 0), #targetDate) +1
SELECT <stuff>
FROM CP_STATISTICS
WHERE MONTH(#targetDate) = Month(myDateCol) AND
YEAR(#targetDate) = Year (myDateCol) AND
#targetWeek = DATEDIFF(week, DATEADD(MONTH,
DATEDIFF(MONTH, 0, myDateCol), 0), myDateCol) +1
ORDER BY myDateCol
Note, things would get more complicated if you use non-standard weeks, or want to reach a few days into an earlier month for weeks that straddle a month boundary.
EDIT 2
From looking at your 'solved now' section. I think your question is "how do I get data out of a table for a given week?"
Your solution appears to be:
DECLARE #targetDate datetime2 = '5/1/2012'
DECLARE #startDate datetime2 = DATEADD(ww, DATEDIFF(ww,0,targetDate), 0)
DECLARE #endDate datetime2 = DATEADD(ww, DATEDIFF(ww,0,#now), 0)+6
SELECT <stuff>
FROM STATISTICS_
WHERE dateStamp >= #startDate AND dateStamp <= #endDate
Notice how if the date is 5/1 this solution results in a start date of '4/30/2012'. I point this out because your solution crosses month boundaries. This may or may not be desirable.

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.