Find missing period and determine whether consecutive before - sql

I am currently working on a requirement to find the missing period and then determine whether it was consecutive before the missing period
Following is my sample table structure.
First step: In the following sample table look at last 6 months (2014 7 - 2014 12). Its missing two months 8 & 11. Take the first instance 8.
Second step: Go back 6 months from the first instance missing - (2014 2- 2014 7) - see whether they are missing any months. If No (everything is consecutive) - Select/Include this record if Yes (missing some months) - don't select this record.
Year Month
2014 1
2014 2
2014 3
2014 4
2014 5
2014 6
2014 7
2014 9
2014 10
2014 12
I can select last 6 records and partition by row number and see whether its missing any. But I am not sure how to find if its consecutive and also select the missing period.
I am trying to do as much filtration in Transact-SQL so that I can focus on other validations on c#.
Query I created to find first period missing(not complete)
SELECT f.TYEAR,f.TMONTH, f.TMONTH+1 AS MISSING FROM #TEMPTABLE AS F
LEFT OUTER JOIN #TEMPTABLE AS F2 ON f.TMONTH+1 = f2.TMONTH
WHERE f2.TAXPERIOD IS NULL
Note: The above example can span b/w two calendar years. 2013 mm - 2014 mm

This one has been tested, I've spent some time on it, please let me know if you're happy with the result and vote if yes, cheers :-) PS: The table dbo.Months_and_Years contains your data (with the 2 missing months)
CREATE TABLE dbo.Test_Table (
[Year] SMALLINT,
[Month] TINYINT
);
INSERT INTO dbo.Test_Table
VALUES
(2014, 1),
(2014, 2),
(2014, 3),
(2014, 4),
(2014, 5),
(2014, 6),
(2014, 7),
(2014, 9),
(2014, 10),
(2014, 12);
DECLARE #MinValue TINYINT = 1
DECLARE #MaxValue TINYINT = 100
WHILE #MinValue < = #MaxValue
BEGIN
DECLARE #Missing_Month TINYINT = (
SELECT TOP 1 A.RowID
FROM (
SELECT DENSE_RANK() OVER ( ORDER BY [month]) AS RowID ,
*
FROM dbo.Test_Table
) AS A
WHERE A.RowID <> A.[Month]
)
SELECT #Missing_Month
IF #Missing_Month IS NULL
BREAK
ELSE
INSERT INTO dbo.Test_Table
VALUES (2014, #Missing_Month)
SET #MinValue = #MinValue + 1
END
-- Check your results ---
SELECT A.*
FROM dbo.Test_Table AS A
LEFT JOIN dbo.Months_and_Years AS B ON A.[Month] = B.[Month]
WHERE B.[Month] IS NULL;

I think below code should work, though I've not tested it properly. But you will get the idea -
DECLARE #TmpBaseTable TABLE ([Year] SMALLINT, [Month] TINYINT)
DECLARE #TmpSixMonthTable TABLE (TmpYear SMALLINT, TmpMonth TINYINT)
DECLARE #TmpDate DATE
DECLARE #MissingYear SMALLINT
DECLARE #MissingMonth TINYINT
DECLARE #TmpCount TINYINT
INSERT INTO #TmpBaseTable ([Year], [Month])
SELECT 2014, 1 UNION ALL
SELECT 2014, 2 UNION ALL
SELECT 2014, 3 UNION ALL
SELECT 2014, 4 UNION ALL
SELECT 2014, 5 UNION ALL
SELECT 2014, 6 UNION ALL
SELECT 2014, 7 UNION ALL
SELECT 2014, 8 UNION ALL
SELECT 2014, 9 UNION ALL
SELECT 2014, 10 UNION ALL
SELECT 2014, 11 UNION ALL
SELECT 2014, 12 UNION ALL
SELECT 2015, 1 UNION ALL
SELECT 2015, 3 UNION ALL
SELECT 2015, 4
SELECT TOP 1 #TmpDate = CAST(CAST(tmpYear AS VARCHAR) + '-' + CAST(tmpMonth AS VARCHAR) + '-' + CAST(1 AS VARCHAR) AS DATE)
FROM
(
SELECT ROW_NUMBER() OVER(ORDER BY [Year], [Month]) AS RowID, [Month] AS tmpMonth, [Year] AS tmpYear
FROM #TmpBaseTable
)
tmp ORDER BY RowID DESC
INSERT INTO #TmpSixMonthTable (TmpMonth, TmpYear)
SELECT DATEPART(MONTH, DATEADD (MONTH, -5, #TmpDate)), DATEPART(YEAR, DATEADD (MONTH, -5, #TmpDate)) UNION ALL
SELECT DATEPART(MONTH, DATEADD (MONTH, -4, #TmpDate)), DATEPART(YEAR, DATEADD (MONTH, -4, #TmpDate)) UNION ALL
SELECT DATEPART(MONTH, DATEADD (MONTH, -3, #TmpDate)), DATEPART(YEAR, DATEADD (MONTH, -3, #TmpDate)) UNION ALL
SELECT DATEPART(MONTH, DATEADD (MONTH, -2, #TmpDate)), DATEPART(YEAR, DATEADD (MONTH, -2, #TmpDate)) UNION ALL
SELECT DATEPART(MONTH, DATEADD (MONTH, -1, #TmpDate)), DATEPART(YEAR, DATEADD (MONTH, -1, #TmpDate)) UNION ALL
SELECT DATEPART(MONTH, DATEADD (MONTH, -0, #TmpDate)), DATEPART(YEAR, DATEADD (MONTH, -0, #TmpDate))
SELECT TOP 1 #MissingMonth = tmpSix.TmpMonth, #MissingYear = tmpSix.TmpYear FROM #TmpSixMonthTable tmpSix
LEFT OUTER JOIN #TmpBaseTable tmpBase ON tmpSix.TmpMonth = tmpBase.[Month] AND tmpSix.TmpYear = tmpBase.[Year]
WHERE tmpBase.[Year] IS NULL
SET #TmpDate = CAST(CAST(#MissingYear AS VARCHAR) + '-' + CAST(#MissingMonth AS VARCHAR) + '-' + CAST(1 AS VARCHAR) AS DATE)
DELETE FROM #TmpSixMonthTable
INSERT INTO #TmpSixMonthTable (TmpMonth, TmpYear)
SELECT DATEPART(MONTH, DATEADD (MONTH, -6, #TmpDate)), DATEPART(YEAR, DATEADD (MONTH, -6, #TmpDate)) UNION ALL
SELECT DATEPART(MONTH, DATEADD (MONTH, -5, #TmpDate)), DATEPART(YEAR, DATEADD (MONTH, -5, #TmpDate)) UNION ALL
SELECT DATEPART(MONTH, DATEADD (MONTH, -4, #TmpDate)), DATEPART(YEAR, DATEADD (MONTH, -4, #TmpDate)) UNION ALL
SELECT DATEPART(MONTH, DATEADD (MONTH, -3, #TmpDate)), DATEPART(YEAR, DATEADD (MONTH, -3, #TmpDate)) UNION ALL
SELECT DATEPART(MONTH, DATEADD (MONTH, -2, #TmpDate)), DATEPART(YEAR, DATEADD (MONTH, -2, #TmpDate)) UNION ALL
SELECT DATEPART(MONTH, DATEADD (MONTH, -1, #TmpDate)), DATEPART(YEAR, DATEADD (MONTH, -1, #TmpDate))
SELECT #TmpCount = COUNT(1) FROM #TmpSixMonthTable tmp1
INNER JOIN #TmpBaseTable tmp2 ON tmp1.TmpMonth = tmp2.[Month] AND tmp1.TmpYear = tmp2.[Year]
IF(#TmpCount = 6)
BEGIN
SELECT tmp2.[Month], tmp2.[Year] FROM #TmpSixMonthTable tmp1
INNER JOIN #TmpBaseTable tmp2 ON tmp1.TmpMonth = tmp2.[Month] AND tmp1.TmpYear = tmp2.[Year]
END
There can be a smarter way than this :)

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 :)

SQL Server grouped rows return with default values if no row available for a date period

I'm trying to write a stored procedure which groups up rows based on their month and return a sum of all items if they exist and 0 if they don't.
For the date part of the query, what I am trying to get is today's date - extract the month and go back 5 months to gather any data if it exists.
At this stage, the query runs fine as is but I'm wondering if there's any way to optimise this as it looks like I'm running the same set of data over and over again and also it's hard coded to an extent.
The dataset I am trying to achieve is as follows:
Month TotalAmount TotalCount
-----------------------------------
2017-11 0 0
2017-12 200.00 2
2018-01 300.00 3
2018-02 0 0
2018-03 300.00 3
2018-04 100.00 1
Using the following query below, I was able to achieve what I want but as you can see, it's hard coding back the past 5 months so if I wanted to go back 12 months, I'd have to add in more code.
DECLARE #5MonthAgo date = CAST(DATEADD(MONTH, -5, GETDATE()) + 1 - DATEPART(DAY, DATEADD(MONTH, -5, GETDATE())) AS DATE)
DECLARE #4MonthAgo date = CAST(DATEADD(MONTH, -4, GETDATE()) + 1 - DATEPART(DAY, DATEADD(MONTH, -4, GETDATE())) AS DATE)
DECLARE #3MonthAgo date = CAST(DATEADD(MONTH, -3, GETDATE()) + 1 - DATEPART(DAY, DATEADD(MONTH, -3, GETDATE())) AS DATE)
DECLARE #2MonthAgo date = CAST(DATEADD(MONTH, -2, GETDATE()) + 1 - DATEPART(DAY, DATEADD(MONTH, -2, GETDATE())) AS DATE)
DECLARE #1MonthAgo date = CAST(DATEADD(MONTH, -1, GETDATE()) + 1 - DATEPART(DAY, DATEADD(MONTH, -1, GETDATE())) AS DATE)
DECLARE #CurrentMonth date = CAST(GETDATE() + 1 - DATEPART(DAY, GETDATE()) AS DATE)
-- Table to return grouped and sum data
DECLARE #StatsTable TABLE ([Month] DATE,
[Total Amount] DECIMAL(18,2),
[Total Count] INT
)
-- Temporary table to hold onto data batch - so table isn't used later on
DECLARE #TempGenTable TABLE ([Id] INT,
[Date] DATETIME,
[Lines] INT NULL,
[Amount] DECIMAL(18, 2) NULL
)
INSERT INTO #TempGenTable
SELECT
Id, Date, Lines, Amount
FROM
TallyTable
WHERE
Date >= #5MonthAgo
INSERT INTO #StatsTable
SELECT
#5MonthAgo,
COALESCE((SELECT SUM(Amount)
FROM #TempGenTable
WHERE Date >= #5MonthAgo AND Date < #4MonthAgo
GROUP BY DATEADD(MONTH, DATEDIFF(MONTH, 0, Date), 0)), 0),
COALESCE((SELECT COUNT(Id)
FROM #TempGenTable
WHERE Date >= #5MonthAgo AND Date < #4MonthAgo
GROUP BY DATEADD(MONTH, DATEDIFF(MONTH, 0, Date), 0)), 0)
UNION
SELECT
#4MonthAgo,
COALESCE((SELECT SUM(Amount)
FROM #TempGenTable
WHERE Date >= #4MonthAgo AND Date < #3MonthAgo
GROUP BY DATEADD(MONTH, DATEDIFF(MONTH, 0, Date), 0)), 0),
COALESCE((SELECT COUNT(Id)
FROM #TempGenTable
WHERE Date >= #4MonthAgo AND Date < #3MonthAgo
GROUP BY DATEADD(MONTH, DATEDIFF(MONTH, 0, Date), 0)), 0)
...
Is there an easier way to be able to get the above data with more flexibility in the number of months?
Is it better to just have the query pass in a month variable and it checks just the current month and have a loop within the controller to go back x number of months?
I would generate the data using a recursive CTE and then use left join:
with months as (
select datefromparts(year(getdate()), month(getdate()), 1) as month_start, 5 as n
union all
select dateadd(month, -1, month_start), n - 1
from months
where n > 0
)
select m.month_start, count(s.id), sum(s.amount)
from months m left join
#StatsTable s
on m.month_start = s.month
group by m.month_start
order by m.month_start;
You haven't provided sample data, so I'm not sure what s.month looks like. You might want the join condition to be:
on s.month >= m.month_start and s.month < dateadd(month, 1, m.month_start)
Below is a set-based method to generate the needed monthly periods:
--sample data
CREATE TABLE dbo.TallyTable (
Id int
, Date datetime
, Lines int
, Amount decimal(18, 2)
);
INSERT INTO dbo.TallyTable
VALUES
(1, '2017-12-05', 1, 50.00)
,(2, '2017-12-06', 1, 150.00)
,(3, '2018-01-10', 1, 100.00)
,(4, '2018-01-11', 1, 100.00)
,(5, '2018-01-12', 1, 100.00)
,(6, '2018-03-15', 1, 225.00)
,(7, '2018-03-15', 1, 25.00)
,(8, '2018-03-15', 1, 50.00)
,(9, '2018-04-20', 1, 100.00);
GO
DECLARE #Months int = 5; --number of historical months
WITH
t10 AS (SELECT n FROM (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0)) t(n))
,t100 AS (SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 0)) - 1 AS num FROM t10 AS a CROSS JOIN t10 AS b)
, periods AS (SELECT
CONVERT(varchar(7), DATEADD(month, DATEDIFF(month, '', GETDATE()) - num, ''),121) AS Month
, DATEADD(month, DATEDIFF(month, '', CAST(GETDATE() AS date)) - num, '') AS PeriodStart
, DATEADD(month, DATEDIFF(month, '', CAST(GETDATE() AS date)) - num + 1, '') AS NextPeriodStart
FROM t100
WHERE num <= #Months
)
SELECT periods.Month, COALESCE(SUM(Amount), 0) AS TotalAmount, COALESCE(COUNT(ID), 0) AS TotalCount
FROM periods
LEFT JOIN dbo.TallyTable ON
TallyTable.Date >= PeriodStart
AND TallyTable.Date < NextPeriodStart
GROUP BY periods.Month
ORDER BY periods.Month;

SQL Server find the last week of the last 2 months

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'

Display the month names available in quarters

I want to display the month names available in the Quarters.
Means like we have four quarters 1,2,3,4 in a year. I have following query.
select datename(month,(DATEADD(QUARTER, DATEDIFF(QUARTER, 0, GETDATE()), 0)) )
Output is :October
But How should I modify this query so that I should get the output as:
October
November
December
declare #quarter datetime
set #quarter = DATEADD(QUARTER, DATEDIFF(QUARTER, 0, GETDATE()), 0)
select datename(month, #quarter)
union all
select datename(month, (DATEADD(month, 1, #quarter) ) )
union all
select datename(month, (DATEADD(month, 2, #quarter) ) )
Try this
DECLARE #d datetime = Dateadd(mm,0,getdate())
-- set #d = Dateadd(mm,-3,getdate())
;With Dates
AS
(
SELECT DATEADD(qq,DATEDIFF(qq,0,#d),0) AS StartDt,DATEPART(qq,#d) AS Qr
UNION ALL
SELECT DATEADD(mm,1,StartDt),DATEPART(qq,DATEADD(mm,1,StartDt+1)-1)
FROM Dates
WHERE DATEPART(qq,DATEADD(mm,1,StartDt+1)-1) = DATEPART(qq,#d)
)
SELECT
datename(month,StartDt) Mnth, Qr,StartDt
FROM Dates
SELECT DATENAME(m,DATEADD(qq, DATEDIFF(qq,0,getdate()), 0)) AS MonthsInQuarter
UNION ALL
SELECT DATENAME(m,DATEADD(mm,1,DATEADD(qq,DATEDIFF(qq,0,getdate()),0)))
UNION ALL
SELECT DATENAME(m,DATEADD(mm,2,DATEADD(qq,DATEDIFF(qq,0,getdate()),0)))

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.