Microsoft SQL Calculating Backlog - sql-server

I would like to calculate the backlog for every week in the past month. Date format is in (MM/DD/YY)
| mutation | issued_date | queryno_i | status |
-----------------------------------------------
01/05/14 12/31/13 321 OPEN
01/02/14 08/01/13 323 CLOSED
01/01/14 06/06/13 123 OPEN
01/01/14 01/01/14 1240 CLOSED
01/02/14 01/01/14 1233 OPEN
01/03/14 01/03/14 200 CLOSED
01/05/14 01/04/14 300 OPEN
01/06/14 01/05/14 231 OPEN
01/07/14 01/06/14 232 CLOSED
01/09/14 01/10/14 332 OPEN
01/11/14 01/11/14 224 CLOSED
01/15/14 01/14/14 225 CLOSED
01/16/14 01/15/14 223 OPEN
I want my result set to look like this:
WeekNum | Opened | Closed | Total Open
--------------------------------------
1 4 3 4 <= (2-4)+ data in week 2 so (2-4)+(1-2)+7
2 4 2 6 <= (1-2)+7
3 2 1 7 <= total count
My Code is below however I am not sure how to query the last part. I am not even sure if this is possible or not.
WITH
issued_queries AS
(
SELECT DATEPART(wk, issued_date) AS 'week_number'
,COUNT(queryno_i) AS 'opened'
FROM t.tech_query
WHERE DATEADD(D,-12,issued_date) > GETDATE()-40
GROUP BY DATEPART(wk, issued_date)
),
closed_queries AS
(
SELECT DATEPART(wk, mutation) AS 'week_number'
,COUNT(queryno_i) AS 'closed'
FROM t.tech_query
WHERE status=3 AND DATEADD(D,-12,issued_date) > GETDATE()-40
GROUP BY DATEPART(wk, mutation)
),
total as
(
SELECT COUNT(*) AS 'total'
FROM t.tech_query
WHERE status!='3'
)
SELECT issued_queries.week_number
, issued_queries.opened
, closed_queries.closed
FROM issued_queries JOIN closed_queries
ON (issued_queries.week_number = closed_queries.week_number)
ORDER BY week_number

Backlog for every week in the past month.
I've taken this to mean last 4 weeks, as that appears to be what you are doing.
Assuming "mutation" represents the date a record was updated (maybe set to closed).
So first, I generate a list of dates, so that way there will be an answer for week number X even if there are no new/closed records.
declare #SundayJustGone datetime
-- We need to get rid of the time component, done through convert.
set #SundayJustGone = convert(date, dateadd(d, 1-DATEPART(dw, getdate()), getdate()))
-- If earlier than sql 2008, can get rid of time component through: set #SundayJustGone = SELECT DATEADD(dd, 0, DATEDIFF(dd, 0, #SundayJustGone))
;with
Last4Weeks as
(
-- Get the sunday of the week just gone.
select #SundayJustGone as SundayDate -- Sunday just gone
union all
select dateadd(d, -7, SundayDate) -- Get the previous Sunday
from Last4Weeks
where dateadd(d, -7, SundayDate) > dateadd(Wk, -4, #SundayJustGone) -- where the new date is not more than 4 weeks old
)
select A.SundayDate,
DATEPART(wk, DateAdd(d, -1, A.SundayDate)) as Week_Number, -- SQL considers Sunday the first day of the week, so we need to move it back 1 day to get the right week
(select count(*)
from t.tech_query
where issued_date between DateAdd(d, -6, A.SundayDate) and A.SundayDate -- Was issued this week. (between monday - sunday)
) as Opened,
(select count(*)
from t.tech_query
where status = 3 -- where it is closed
and mutation between DateAdd(d, -6, A.SundayDate) and A.SundayDate -- and the mutation was this week. (between monday - sunday)
) as Closed,
(select count(*)
from t.tech_query
where (status != 3 or datediff(d, mutation, A.SundayDate) < 0 ) -- Is still open, or was closed after this week.
and datediff(d, issued_date, A.SundayDate) >= 0 -- and it was issued on or before the sunday.
) as TotalOpen
from Last4Weeks as A
hopefully this helps.
the results are different to yours, as I assume Monday is the first day of the week. To change start of week back to sunday, saturday needs to be considered end of week, so, change the set #SundayJustGone = convert(date, dateadd(d, 1-DATEPART(dw, getdate()), getdate())) to set #SundayJustGone = convert(date, dateadd(d, -DATEPART(dw, getdate()), getdate())) (1 removed)

Related

SQL Server: Calculate Four Weeks From a Month

I need a simple solution to get 4 weeks for a month based on current date (each week starting from Monday - Friday).
For each week I need to update a table that already has current date and place a counter from Week 1 - 4 and continue to the following month starting from Week 6 - 8. and start from the beginning after week 8.
The query below is returning week number but for 7 days:
can I use something similar just for 5 days?
DECLARE #MyDate DATETIME = '2020-08-03'
--This assumes the weeks starts from Monday - Sunday
DECLARE #WeekNumber INTEGER = (DATEPART(DAY, DATEDIFF(DAY, 0, #MyDate)/7 * 7)/7 +1)
SELECT #WeekNumber
The previous answer was not useful so I got rid of it. This should do what you're looking for
declare #date datetime= '2020-08-03';
select dateadd(d, -4, dt.dt) start_dt,
dt.dt end_dt,
row_number() over (order by v.n) n
from
(select datefromparts(year(#date),month(#date),1) first_dt) fd
cross apply
(select datediff(week, 0, fd.first_dt) wk_diff) wd
cross apply
(values (1),(2),(3),(4),(5),(6)) v(n)
cross apply
(select dateadd(d, -((datepart(weekday, fd.first_dt) + 1 + ##datefirst) % 7), fd.first_dt) calc_dt) calc_dt
cross apply
(select dateadd(d, (v.n-1)*7, calc_dt) dt) dt
where
dt.dt>=fd.first_dt;
Results
start_dt end_dt n
2020-08-03 2020-08-07 1
2020-08-10 2020-08-14 2
2020-08-17 2020-08-21 3
2020-08-24 2020-08-28 4
2020-08-31 2020-09-04 5

SET DATEFIST = 1 not working correctly

I'm having trouble to make SET DATEFIRST to work properly on some simple queries I working on at the moment.
Here is my first example:
SET DATEFIRST = 1
SELECT count(Distinct ID)
FROM Products
WHERE
Location in (12)
and YEAR (CREATED) = '2018'
Group by datepart(wk, created), year(created)
The above code gives me some results correct and some others wrong. It's basically counting from Monday to Monday, but I want it to count from Monday to Sunday. I still don't understand why it's counting 8 days instead of 7 days on some of the weeks.
Also I have multiple selections with SET DATEFIRST = 1 which also doesn't work:
SET DATEFIRST = 1
--Products finished this week
SELECT count(Distinct ID)
FROM Products
WHERE
Created >= dateadd(day, 1-datepart(dw, getdate()), CONVERT(date,getdate()))
AND Created < dateadd(day, 8-datepart(dw, getdate()), CONVERT(date,getdate()))
AND Location in (15,16,17) AND (Location IS NOT NULL OR Location NOT IN(18))
UNION ALL
--------------------------------------------
-- Products received this week
SELECT count(Distinct ID)
FROM Products
WHERE
Created >= dateadd(day, 1-datepart(dw, getdate()), CONVERT(date,getdate()))
AND Created < DATEADD(DAY,0,DATEDIFF(DAY,0,dateadd(day, 8-datepart(dw, getdate()),getdate())))
AND Location in(1)
The above code is not reacting to SET DATEFIRST = 1
It's not counting from Monday to sunday, instead it's counting from Sunday to sunday (8 days)
Here is your issue:
DATEADD(day, 8-datepart(DW, GETDATE()), CONVERT(DATE,GETDATE())))
You are comparing a DATETIME from the database with a TIME to a DATE with no TIME. So the DATE portions on next Monday will be equal and your < will fail.
This will not work because the DB field is a DATETIME and you are comparing to a DATE.
SET #D= '06/25/2018 01:20:23 AM' --Monday next week with a time
IF(#D < DATEADD(day, 8-datepart(DW, GETDATE()), CONVERT(GETDATE()))) --<--The whole day
SELECT 1 -- '06/25/2018 12:00:23 AM' < (DATE)2018-06-25
ELSE
SELECT 2
Returns 2
If you do want to include everything through Sunday then you need to set the cutoff to 12:00:00 AM Monday morning. One way to do that would be something like:
DATEADD(DAY,0,DATEDIFF(DAY,0,dateadd(day, 8-datepart(dw, getdate()),getdate())))

Conditional scoring

I have a table that shows only the 'captured' data. For example in the below exhibit, the emp_no 17 has 2 records - for November and February (for a specified 6 month period, from July 2017). It does not have data for the other 4 months (within the 6-month date range, from previous 6 months to current date).
How can I populate these missing months (Sept, Oct, Dec) with default values for num_differences of 0 for the missing months? (for example, in this case, I want emp_no 17 to have the below (I can ignore 2018 data - only require data up to Dec 2017):
I have the script below:
declare #YMN date;
set #YMN = '20171201';
DECLARE #Emp TABLE (
[date] date,
[emp_no] int,
[num_differences] int
);
INSERT INTO #Emp VALUES
('2017-09-14', 17, 1), ('2017-12-01', 17, 1),('2017-12-18', 17, 1),('2017-12-21', 17, 1),
('2017-09-27', 17, 1), ('2017-12-04', 17, 1);
-------------------------------------------------------------------------------------------get missing dates---------------------------------------------------------------------------
;WITH cte_Emp_No AS (
SELECT DISTINCT [emp_no]
FROM #Emp
),
cte_dates AS (
SELECT [emp_no], DATEADD(month, -6, DATEADD(dd, -(DAY(dateadd(month, 1, #YMN)) - 1), dateadd(month, 1, #YMN))) AS [date]
FROM cte_Emp_No
UNION ALL
SELECT [emp_no], DATEADD(month, 1, [date]) AS [date]
FROM cte_dates
WHERE [date] < dateadd(month, 0, #YMN)
)
SELECT DISTINCT ISNULL(e.emp_no, c.emp_no) emp_no, ISNULL(e.date, c.date) date, ISNULL(e.num_differences, 0) num_differences
into ##new_table
FROM #Emp AS e
RIGHT JOIN cte_dates AS c ON YEAR(c.date) = YEAR(e.date) AND MONTH(c.date) = MONTH(e.date)
-----------------------------------------------------------------------------------------------MAIN CTE------------------------------------------------------------------------------
;with cte_RawScore as
(
select emp_no
, date YMN
,sum(case when datediff(month, convert(datetime, #YMN, 112), date) = 0 then num_differences else 0 end) as thismonth
,sum(case when datediff(month, convert(datetime, #YMN, 112), date) between -2 and 0 then num_differences else 0 end) as last3month
,sum(case when datediff(month, convert(datetime, #YMN, 112), date) between -5 and 0 then num_differences else 0 end) as last6month
from ##new_table d
group by emp_no, date
)
select
emp_no
,YMN
,case when last6month = 0 then 5
when last3month = 0 then 4
when thismonth = 0 then 3
when thismonth <= 3 then 2
else 1 end RawScore
from cte_RawScore
ORDER BY day(YMN) desc
drop table ##new_table
I want this the scoring only to be applicable for 6 months from and after July 2017. i.e. the #YMN is a variable that stores the year month number; and the score, according to the above rule applies to the 6 months from 201707.
So 201707 is 1 month,
201708 is 2 months, etc, up to 201712
I wish to have a list of employees with their associated scores, based on the rules mentioned below .
That’s, :
A score of 5 if 0 differences in 6 consecutive months ( from July to December) ;
A score of 4 if 0 differences in 3 consecutive months (from July to December);
A score of 3 if 0 differences for 1 month ( from July to December);
A score of 2 if 1 to 3 differences for 1 month (from July to December);
A score of 1 if 4 or more differences in 1 month (from July to December).
I get the number of differences from a table, but some employees do not appear for certain months; hence I want to give them a difference of 0 if they do not appear for that particular month.
Please assist.
I think I understand what you're getting at. Let me give you a simplified example. You need a table full of dates to join to. In data warehousing we use a Date dimension which has attributes about every date.
For your example your date dimension table could just have Month names or numbers:
1
2
...
12
Let's call this table Months.
Then you would do something like this, to count a zero for months with no data. Here I'm using what's called a Common Table Expression or CTE (the part with the WITH) in place of a table, since I'm not concerned with creating a permanent table right now.
WITH Months AS (
SELECT 1 AS MonthNumber UNION
SELECT 2 UNION
SELECT 3 UNION
SELECT 4 UNION
SELECT 5 UNION
SELECT 6 UNION
SELECT 7 UNION
SELECT 8 UNION
SELECT 9 UNION
SELECT 10 UNION
SELECT 11 UNION
SELECT 12
)
SELECT M.MonthNumber, COUNT(*)
FROM Months as M
LEFT JOIN MyData as D
ON MONTH(D.SomeDateValue) = M.MonthNumber
GROUP BY M.MonthNumber
This will guarantee every month appears with a count, perhaps of zero.

How to retrieve repetitive data in sql without using loops?

I have a help-desk system in which there is a table with ticket details. I have a table in SQL server with data something like this,
ID status create_date closing_date
---------------------------------------------------------
1 closed 2015-01-01 14:06:30 2015-01-02 18:06:42
2 closed 2015-01-01 15:16:42 2015-01-02 08:12:10
- - - -
- - - -
Now I want to display the number of hours taken to close the tickets in past 5 weeks looking at last 2 months.
For ex., I want to show data for past 5 weeks from today (49,50,51,52,1) and each week should look at the last 2 months closed tickets. (for week 49, the data should be between week 49 - 60 days and week 49 end date).
Currently, I have used loop to get the result like this,
declare #week_start datetime, #week_end datetime, #week int, #last_week int, #week_number int
set #last_week = datepart(wk, dateadd(wk, -1, getdate()))
set #week_number = dbo.F_ISO_WEEK_OF_YEAR(dateadd(wk, -5, getdate()))
set #week = #last_week - 4
set #week_start = dateadd(day, -60, dateadd(wk, -5, DATEADD(week, DATEDIFF(day, 0, getdate())/7, 4)))
set #week_end = dateadd(wk, -5, DATEADD(week, DATEDIFF(day, 0, getdate())/7, 4))
while #week <= #last_week
begin
select #week_number as week_number,
cast(avg(cast(Datediff(hour,create_date,closing_date) as decimal (10,2))) as decimal(10,2)) Hours
FROM tickets
where closing_date between #week_start
and #week_end
--group by dbo.F_ISO_WEEK_OF_YEAR(closing_date )
set #week_start = dateadd(wk, 1, #week_start)
set #week_end = dateadd(wk, 1, #week_end)
set #week = #week + 1
set #week_number = #week_number + 1
end
which returns the following data,
week_number Hours
--------------------
49 121.56
50 129.06
51 125.57
52 125.90
53 130.52
Now, I do not want to use the loop because it messes up the week numbers and I also want to display ISO week numbers.
So here is my modified code,
select dbo.F_ISO_WEEK_OF_YEAR(closing_date) as week_number,
cast(avg(cast(Datediff(hour,create_date,closing_date) as decimal (10,2))) as decimal(10,2)) Hours
FROM tickets
where closing_datebetween DATEADD(wk, -5, getdate()) and DATEADD(wk, -1, getdate())
group by dbo.F_ISO_WEEK_OF_YEAR(closing_date)
But it returns the following data which is incorrect as it's not looking at the past 2 months from a particular week,
week_number Hours
--------------------
49 142.69
50 262.76
51 95.50
52 85.39
1 75.90
I do not know how can I modify my query to get the same result without using loops.
Any help will be appreciated.
Thanks.
It's a little hard to gain full context without seeing what your underlying data it, but my first guess would be that in your iterative code, you have a sliding window (i.e. 5 weeks ago you're looking at past 2 months + 5 week, 4 weeks ago, past 2 month +4 , etc). Your select statement is statically looking at the values as of one point in time.
If you're running SQL Server 2012+ you can use row frames to define such a sliding windows (http://msdn.microsoft.com/en-us/library/ms189461.aspx). Alternatively, and I say this with caution, if you only need to return 5 rows, hitting the same table 5 times for a report like this isn't that bad. If you can provide sample data, you may be able to get some more code-specific replies.

Apply week number to dates for whole weeks only

The time is: (m/d/yyyy) => 2009/01/04
Using this command using datepart(wk,'20090104') I can get the week number (for any given date).
So :
SELECT datepart(wk,'20090101') //1
SELECT datepart(wk,'20090102') //1
SELECT datepart(wk,'20090103') //1
SELECT datepart(wk,'20090104') //2
So far so good.
The problem :
Those 3 first dates are not part of a full week, so I can't put them in a fixed 52-week chart.
Our company needs to see information about each whole week in the 52 weeks of a year. (Each year has 52 whole weeks).
So 20090101 doesn't belong to the first week of 2009 !
It belongs to the previous year (which is irrelevant to my question)
So I need a UDF (I've been searching a lot, and ISOWEEK is not answering my needs) which by a given datetime, will give me the Week Number (week = whole week, so partial weeks aren't considered).
Example :
calcweekNum ('20090101') //52 ...from the last year
calcweekNum ('20090102') //52 ...from the last year
calcweekNum ('20090103') //52 ...from the last year
calcweekNum ('20090104') //1
..
..
calcweekNum ('20090110') //1
calcweekNum ('20090111') //2
calcweekNum ('20090112') //2
...
Here's a different approach. All you need to supply is the year:
DECLARE #year INT = 2009;
DECLARE #start SMALLDATETIME;
SET #start = DATEADD(YEAR, #year-1900, 0);
;WITH n AS
(
SELECT TOP (366) -- in case of leap year
d = DATEADD(DAY, ROW_NUMBER() OVER (ORDER BY name)-1, #start)
FROM sys.all_objects
),
x AS
(
SELECT md = MIN(d) FROM n
WHERE DATEPART(WEEKDAY, d) = 1 -- assuming DATEFIRST is Sunday
),
y(d,wk) AS
(
SELECT n.d, ((DATEPART(DAYOFYEAR, n.d) - DATEDIFF(DAY, #start, x.md)-1)/7) + 1
FROM n CROSS JOIN x
WHERE n.d >= x.md
AND n.d < DATEADD(YEAR, 1, #start)
)
SELECT [date] = d, [week] = wk
FROM y WHERE wk < 53
ORDER BY [date];
Results:
date week
---------- ----
2009-01-04 1
2009-01-05 1
2009-01-06 1
2009-01-07 1
2009-01-08 1
2009-01-09 1
2009-01-10 1
2009-01-11 2
2009-01-12 2
...
2009-12-25 51
2009-12-26 51
2009-12-27 52
2009-12-28 52
2009-12-29 52
2009-12-30 52
2009-12-31 52
Note that week 52 won't necessarily be a full week, and that in some cases (e.g. 2012), the last day or two of the year might fall in week 53, so they're excluded.
An alternative approach is to repeat the MIN expression twice:
DECLARE #year INT = 2009;
DECLARE #start SMALLDATETIME;
SET #start = DATEADD(YEAR, #year-1900, 0);
;WITH n AS
(
SELECT TOP (366) -- in case of leap year
d = DATEADD(DAY, ROW_NUMBER() OVER (ORDER BY name)-1, #start)
FROM sys.all_objects
),
y(d,wk) AS
(
SELECT n.d, ((DATEPART(DAYOFYEAR, n.d) - DATEDIFF(DAY, #start, (SELECT MIN(d)
FROM n WHERE DATEPART(WEEKDAY, d) = 1))-1)/7) + 1
FROM n
WHERE n.d >= (SELECT md = MIN(d) FROM n WHERE DATEPART(WEEKDAY, d) = 1)
AND n.d < DATEADD(YEAR, 1, #start)
)
SELECT [date] = d, [week] = wk
FROM y WHERE wk < 53
ORDER BY d;
Here's a function for you to calculate it on the fly:
CREATE FUNCTION dbo.WholeWeekFromDate (
#Date datetime
)
RETURNS tinyint
AS BEGIN
RETURN (
SELECT DateDiff(Day, DateAdd(Year, DateDiff(Year, 0, CalcDate), 0), CalcDate) / 7 + 1
FROM (SELECT DateAdd(Day, (DateDiff(Day, 0, #Date) + 1) / 7 * 7, 0)) X (CalcDate)
);
END;
I don't recommend you use it, as it may perform badly due to being called once for every row. If you absolutely must have a function to use in real queries, then convert it to an inline function returning a single column and row, and use it as so:
SELECT
OtherColumns,
(SELECT WeekNumber FROM dbo.WholeWeekFromDate(DateColumn)) WeekNumber
FROM
YourTable;
This will allow it to be "inlined" in the execution plan and perform significantly better.
But even better, as others have suggested, is to use a BusinessDate table. Here's a head start on creating one for you:
CREATE TABLE dbo.BusinessDate (
BusinessDate date NOT NULL CONSTRAINT PK_BusinessDate PRIMARY KEY CLUSTERED,
WholeWeekYear smallint NOT NULL
CONSTRAINT CK_BusinessDate_WholeWeekYear_Valid
CHECK (WholeWeekYear BETWEEN 1900 AND 9999),
WholeWeekNumber tinyint NOT NULL
CONSTRAINT CK_BusinessDate_WholeWeekNumber_Valid
CHECK (WholeWeekNumber BETWEEN 1 AND 53),
Holiday bit CONSTRAINT DF_BusinessDate_Holiday DEFAULT (0),
Weekend bit CONSTRAINT DF_BusinessDate_Weekend DEFAULT (0),
BusinessDay AS
(Convert(bit, CASE WHEN Holiday = 0 AND Weekend = 0 THEN 1 ELSE 0 END)) PERSISTED
);
And I'll even populate it from 1900-01-01 through 2617-09-22 (Is that enough for the projected life of your product? And it's only 7.8MB so don't fret over size):
WITH A (N) AS (SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1
UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1 UNION ALL SELECT 1),
B (N) AS (SELECT 1 FROM A F, A A, A L, A C, A O, A N),
C (N) AS (SELECT Row_Number() OVER (ORDER BY (SELECT 1)) FROM B),
Dates AS (
SELECT
N,
DateAdd(Day, N, '18991231') Dte,
DateAdd(Day, N / 7 * 7, '19000101') CalcDate
FROM C
)
INSERT dbo.BusinessDate
SELECT
Dte,
Year(CalcDate),
DateDiff(Day, DateAdd(Year, DateDiff(Year, 0, CalcDate), 0), CalcDate) / 7 + 1,
0,
(N + 6) % 7 / 5 -- calculate weekends
FROM Dates; -- 3-7 seconds or so on my VM server
Then join to the table on the date, and use the WholeWeekNumber column for your output. You might also consider adding a WeekNumberYear because it's going to be a tad difficult to figure out that the 52 of 2009-01-01 really belongs to 2008 without this... a strange data point in there for sure if you don't (laugh).
Example table contents:
BusinessDate WholeWeekYear WholeWeekNumber Holiday Weekend BusinessDay
------------ ------------- --------------- ------- ------- -----------
1/1/2009 2008 52 0 0 1
1/2/2009 2008 52 0 0 1
1/3/2009 2008 52 0 1 0
1/4/2009 2009 1 0 1 0
1/5/2009 2009 1 0 0 1
1/6/2009 2009 1 0 0 1
1/7/2009 2009 1 0 0 1
1/8/2009 2009 1 0 0 1
1/9/2009 2009 1 0 0 1
1/10/2009 2009 1 0 1 0
1/11/2009 2009 2 0 1 0
If you really don't want to use this as a general business date calculation table, you can drop the last 3 columns, otherwise, update the Holiday column to 1 for company holidays.
Note: if you actually make the above table, and your access to it most often uses JOIN or WHERE conditions on a different column than BusinessDate, then make the primary key nonclustered and add a clustered index starting with the alternate column.
Some of the above scripts require SQL 2005 or higher.
It would be relatively easy to setup a custom calendar table with one row for each date of the year in it, and then have other fields that will allow you to rollup however you want. I do this when I have clients using varying calendars, i.e. fiscal years, and it makes the query logic very simple.
Then you just join date-to-date and get the week-number that you want.
date | reporting year | reporting week
-----------|----------------|---------------
2009-01-01 | 2008 | 52
2009-01-02 | 2008 | 52
2009-01-03 | 2008 | 52
2009-01-04 | 2009 | 01
2009-01-05 | 2009 | 01
etc.
and then to use it ( for example to get total sales rollup by your custom weeks, didn't validated my sql):
select reporting_year, reporting_month, sum(sales)
from sales
inner join custom_date_table cdt on cdt.sysdate = sales.sysdate
group by reporting_year, reporting_month
where report_year=2009
DECLARE #StartDate DATE;
SET #StartDate = '20120101';
WITH Calendar AS (
SELECT #StartDate AS DateValue
,DATEPART(DW, #StartDate) AS DayOfWeek
,CASE WHEN DATEPART(DW, #StartDate) = 1 THEN 1 ELSE 0 END AS WeekNumber
UNION ALL
SELECT DATEADD(d, 1, DateValue)
,DATEPART(DW, DATEADD(d, 1, DateValue)) AS DayOfWeek
,CASE WHEN DayOfWeek = 7 THEN WeekNumber + 1 ELSE WeekNumber END
FROM Calendar
WHERE DATEPART(YEAR, DateValue) = DATEPART(YEAR, #StartDate)
)
SELECT DateValue, WeekNumber
FROM Calendar
WHERE WeekNumber BETWEEN 1 AND 52
AND DATEPART(YEAR, DateValue) = DATEPART(YEAR, #StartDate)
OPTION (MAXRECURSION 0);
Don't use a UDF, use a calendar table instead, then you can define week numbers exactly as your company requires and simply query them from the table, which will be much easier and possibly much faster than using a UDF.
A calendar table has numerous uses in SQL (search this site or Google) so you should probably create it anyway.
There is no good answer for this.
A year is NOT 52 weeks long.
It is 52 weeks and one day in normal years, and 52 weeks and two days in leap years.

Resources