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.
Related
s_term
w_due
2
2
4
2
Given a table as shown above, I want to extract the corresponding date for the month of August/September (dd-mm-yyyy) as follows:
<s_term, w_due>
(2,2) means a service started on 2nd Monday of August and I need to return the date as the 4th Monday of August as indicated by w_due which indicates the stepsize from the starting point.
Similarly for (4, 2): I want to return the 2nd Monday in September as the service start date is on 4th Monday of August with a w_due of 2.
A Calendar Table would be well worth the effort.
Here is an option where we use an ad-hoc tally table to create an ad-hoc calendar table.
Example
Declare #BaseDate date = '2021-08-01'
;with cte as (
Select D
,WN = row_number() over (partition by month(d) order by D)
From ( Select Top 90 D=DateAdd(DAY,-1+Row_Number() Over (Order By (Select Null)),#BaseDate) From master..spt_values n1 ) D
Where datename(WEEKDAY,D)='Monday'
)
Select A.*
,Date1 = B.D
,Date2 = C.D
From YourTable A
Join cte B on s_term=B.WN and month(B.D)=month(#BaseDate)
Join cte C on w_due =C.WN and month(C.D)=month(dateadd(month,1,#BaseDate))
Results
s_term w_due Date1 Date2
2 2 2021-08-09 2021-09-13
4 2 2021-08-23 2021-09-13
DECLARE #referenceMonth DATE = '2021-08-01';
DECLARE #tbl TABLE (s_term int, w_due int);
INSERT INTO #tbl VALUES (2, 2), (4, 2);
SELECT a.first_monday, s_term, b.s_term_monday, w_due, c.w_due_monday
FROM #tbl
CROSS APPLY (
SELECT DATEADD(d, (9 - DATEPART(dw, #referenceMonth)) % 7, #referenceMonth) as first_monday
) a
CROSS APPLY
(
SELECT DATEADD(WEEK, s_term - 1, a.first_monday) as s_term_monday
) b
CROSS APPLY (
SELECT DATEADD(WEEK, w_due, b.s_term_monday) as w_due_monday
) c;
The first part (a) just gets the first monday of the reference month.
The second part (b) calculates the Nth monday of the reference month (s_term).
The last part (c) calculates de Nth monday from that s_term monday (w_due)
The result is as follows:
As you specified august/2021 as the reference date, I just hard-coded it as a parameter.
I also split the DATE opperations in multiple CROSS APPLY for ease of read, but you could nest the DATEADD inside one another right in the SELECT. Something like this:
SELECT s_term, w_due, DATEADD(WEEK, w_due,
DATEADD(WEEK, s_term - 1,
DATEADD(d, (9 - DATEPART(dw, #referenceMonth)) % 7, #referenceMonth)))
FROM #tbl
Edit: Explaning that misterious logic of DATEADD(d, (9 - DATEPART(dw, #referenceMonth)) % 7, #referenceMonth)
We take the weekday of the first day of that month: DATEPART(dw, #referenceMonth)
Sunday is 1, Monday is 2 and so on...
Now we find out how many days it would need to get from our reference week day to the desired week day (Monday). We do that with that formulae: (9 - X) % 7
That 9 is the number of days minus our WeekDay that would equal to 7 (the number of days in a week). 7 (days of the week) + 2 (monday) = 9.
If you where to find, say, the first Thursday of the week, that 9 would become 13. 7 (days of the week) + 5 (thursday) = 13.
When we take the modulus of 7 we are looking at the number of week days we are away from our desired week day. Let's run a few simulations:
refenceDate is a Monday (2): 9 - 2 % 7 = 0
nothing to do!
refenceDate is a Sunday (1): 9 - 1 % 7 = 1
we are 1 day away from the next monday
refenceDate is a Thursday (5): 9 - 5 % 7 = 4
we are 4 days away from the next monday
Now we finish by adding that number of days to our reference date: DATEADD(d, y, #referenceMonth)
With that, the first day of the month becomes the first Monday of said month.
maybe ?
CREATE TABLE #temptable(
s_term INTEGER
,w_due INTEGER
);
INSERT INTO #temptable(s_term,w_due) VALUES (2,2) ,(4,2);
WITH Mondays AS (
SELECT ROW_NUMBER () OVER (ORDER BY (SELECT NULL)) RN ,
DATEADD(DAY, n, M_fom) AS Mday
FROM (
SELECT fom, DATEADD(DAY, DATEDIFF(DAY, 0, fom) / 7 * 7, 0)
FROM ( VALUES ( DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()), 0)) ) d ( fom )
) e ( fom, M_fom )
CROSS JOIN ( VALUES ( 7), ( 14), ( 21), ( 28), ( 35),(42),(49),(56),(63) ) f ( n )
)
SELECT m1.Mday AS M1, m2.Mday AS M2
FROM #temptable AS t INNER JOIN
Mondays AS m1 ON t.s_term = m1.RN INNER JOIN
Mondays AS m2 ON t.s_term + t.w_due = m2.RN
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
I am writing a query that returns the sum of rows for the last 10 weeks FRI-THURS.
It uses a group by to show the sum of each week:
WITH Vars (Friday) -- get current week Fridays Date
AS (
SELECT CAST(DATEADD(DAY,(13 - (##DATEFIRST + DATEPART(WEEKDAY,GETDATE())))%7,GETDATE()) AS DATE) As 'Friday'
)
SELECT datepart(week, DateField) AS WeekNum, COUNT(*) AS Counts
FROM Table
WHERE DateField >= DATEADD(week,-9, (SELECT Friday from Vars))
GROUP BY datepart(week, DateField)
ORDER BY WeekNum DESC
The problem is every week starts on Monday so the Group By doesn't group the dates on how I want it. I want a week to be defined as FRI-THURS.
One workaround to this is to use DATEFIRST. e.g:
SET DATEFIRST = 5; --set beginning of each week to Friday
WITH Vars (Friday) -- get current week Fridays Date
... rest of query
However due to limitations on the interface I am writing this query I cannot have two separate statements run. It needs to be one query with no semicolons.
How can I achieve this?
This should do it. First pre-compute once the StartingFriday of 9 weeks ago, rather than doing that for each row. Then compute the dfYear and dfWeek giving them alias-es, where their DateField is after the starting friday. Lastly, Count/GroupBy/OrderBy.
Declare #StartingFriday as date =
DATEADD(week,-9, (DATEADD(day, - ((Datepart(WEEKDAY,GETDATE()) +1) % 7) , GETDATE())) ) ;
SELECT dfYear, dfWeek, COUNT(*) AS Counts
FROM
(Select -- compute these here, and use alias in Select, GroupBy, OrderBy
(Datepart(Year,(DATEADD(day, - ((Datepart(WEEKDAY,DateField) +1) % 7) , DateField)) ) )as dfYear
,(Datepart(Week,(DATEADD(day, - ((Datepart(WEEKDAY,DateField) +1) % 7) , DateField)) ) )as dfWeek
From Table
WHERE #StartingFriday <= DateField
) as aa
group by dfYear, dfWeek
order by dfYear desc, dfWeek desc
-- we want the weeknum of the (Friday on or before the DateField)
-- the % (percent sign) is the math MODULO operator.
-- used to get back to the nearest Friday,
-- day= Fri Sat Sun Mon Tue Wed Thu
-- weekday= 6 7 1 2 3 4 5
-- plus 1 = 7 8 2 3 4 5 6
-- Modulo7= 0 1 2 3 4 5 6
-- which are the days to subtract from DateField
-- to get to its Friday start of its week.
I did some testing with this
declare #dt as date = '8/17/18';
select ((DATEPART(WEEKDAY,#dt) +1) % 7) as wd
,(DATEADD(day, - ((Datepart(WEEKDAY,#dt) +1) % 7) , #dt)) as Fri
,(Datepart(Week,(DATEADD(day, - ((Datepart(WEEKDAY,#dt) +1) % 7) , #dt)) ) )as wk
,DATEADD(week,-9, (DATEADD(day, - ((Datepart(WEEKDAY,#dt) +1) % 7) , #dt)) ) as StartingFriday
I'm trying to get a query that returns customers I've attended per day, and i have this dataset:
fecha RecargadorPDV
2016/12/19 1
2016/12/19 2
2016/12/19 3
2016/12/20 1
2016/12/20 4
2016/12/20 5
2016/12/21 2
2016/12/21 6
2016/12/21 7
2016/12/21 8
..
...
2016/12/26 1
2016/12/26 2
2016/12/26 1
2016/12/27 2
2016/12/27 6
2016/12/27 7
2016/12/27 8
but the output I'd want to have is this:
date attended acum_customers
2016/12/19 3 3 -- Every monday it restart
2016/12/20 3 5
2016/12/21 4 8
.
..
2016/12/26 3 3 -- Every monday it restart
2016/12/27 4 3
.
..
As you can see, every monday it restart the values and if some customers are in a date and in the next day are present it needs to be ignored.
Here is a version that returns what you need for any arbitrary date. I have included sample data for a full week + two days to confirm its functionality.
DECLARE #t table (fecha date,
RecargadorPDV int
)
INSERT INTO #t VALUES
('2016/12/19', 1),
('2016/12/19', 2),
('2016/12/19', 3),
('2016/12/20', 1),
('2016/12/20', 4),
('2016/12/20', 5),
('2016/12/21', 2),
('2016/12/21', 6),
('2016/12/21', 7),
('2016/12/21', 8),
('20161222', 12),
('20161222', 1),
('20161222', 8),
('20161223', 11),
('20161223', 13),
('20161223', 15),
('20161223', 9),
('20161224', 1),
('20161225', 22),
('2016/12/26', 1),
('2016/12/26', 2),
('2016/12/26', 1),
('2016/12/27', 2),
('2016/12/27', 6),
('2016/12/27', 7),
('2016/12/27', 8)
;
With a as (
SELECT DISTINCT
fecha,
Dateadd(day, -(
case
when datepart(weekday, fecha) >=2
THEN datepart(weekday, fecha) - 2
ELSE 6
END), fecha) as LastMonday
FROM #t
)
SELECT
a.fecha as [date],
-- count(Distinct(CASE when t.fecha = a.fecha Then t.recargadorPDV else -1 END)) - 1 as attended,
SUM(CASE when t.fecha = a.fecha Then 1 else 0 END) as attended,
Count(distinct recargadorPDV) as acum_customers
FROM #t t
INNER JOIN a
ON t.fecha BETWEEN a.LastMonday and a.fecha
Group by a.fecha
ORDER BY a.fecha
Output of the above (as corrected) is:
date attended acum_customers
2016-12-19 3 3
2016-12-20 3 5
2016-12-21 4 8
2016-12-22 3 9
2016-12-23 4 13
2016-12-24 1 13
2016-12-25 1 14
2016-12-26 3 2
2016-12-27 4 5
I think your second week acum_customers is off based off the test data so check on that. I also assumed that the recargadorPDVcould only attend once per day since it's unique so I removed the one record noted below. With that... this should get you what you want. Let me know if it needs more explanation.
--change the ##DATEFIRST from 7 (english default) to 1 for the start of the week calculations
SET DATEFIRST 1;
--load some test data
declare #table table (fetcha datetime, recargadorPDV int)
insert into #table(fetcha, recargadorPDV)
values
('2016/12/19',1),
('2016/12/19',2),
('2016/12/19',3),
('2016/12/20',1),
('2016/12/20',4),
('2016/12/20',5),
('2016/12/21',2),
('2016/12/21',6),
('2016/12/21',7),
('2016/12/21',8),
--this is the break in the weeks
('2016/12/26',1),
('2016/12/26',2),
--('2016/12/26',1), -- removed this value since a unique ID shouldn't be allowed to attend twice for a single day
('2016/12/27',2),
('2016/12/27',6),
('2016/12/27',7),
('2016/12/27',8)
--temp table to hold some aggregated data
if object_id('tempdb..#tempT') is not null drop table #tempT
select
y.YR
,y.WK
,y.fetcha
,count(y.recargadorPDV) as attend
,sum(y.CTforWK) as accum
into #tempT
from(
select x.*
from
(select
fetcha
,recargadorPDV
,datepart(yy,fetcha) as YR
,datepart(wk,fetcha) as WK
--the case statment is my way of assigning 1 to each recargadorPDV ONLY once for each week so the running total is correct, ignoring duplicates as you stated
,case when count(recargadorPDV) over (partition by datepart(yy,fetcha), datepart(wk,fetcha), recargadorPDV order by fetcha) = 1 then 1 else 0 end as CTforWK
from #table) x) y
group by
y.YR
,y.WK
,y.fetcha
--see the inital results without the running total
select * from #tempT
--see the final results with the running total
select
a.fetcha
,a.attend
,sum(x.accum) as acum_customers
from #tempT a
inner join #tempT x on x.fetcha <= a.fetcha and x.YR = a.YR and x.WK = a.WK
group by a.fetcha, a.attend
order by a.fetcha
--change back the ##DATEFIRST setting
SET DATEFIRST 7;
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.