Fill date gaps in SQL query with zeros - sql

I have the following structure (which came from some joins across tables,etc) which will be used to produce a chart.
Some IDs do not have data for all the dates, which result in some dashed lines in the chart.
The question is, how can I add the missing dates for each ID and fill their data cell with zeros?
As script:
(67, '2016-09-28 00:00:00.000',1),
(178, '2016-09-28 00:00:00.000',6),
(42, '2016-09-25 00:00:00.000',1),
(66, '2016-09-25 00:00:00.000',122),
(67, '2016-09-25 00:00:00.000',2),
(10, '2016-09-24 00:00:00.000',5),
(13, '2016-09-24 00:00:00.000',4),
(66, '2016-09-24 00:00:00.000',198),
(67, '2016-09-24 00:00:00.000',15),
(178, '2016-09-24 00:00:00.000',4),
(10, '2016-09-23 00:00:00.000',1),
(13, '2016-09-23 00:00:00.000',2),
(42, '2016-09-23 00:00:00.000',4),
(66, '2016-09-23 00:00:00.000',208),
(67, '2016-09-23 00:00:00.000',15)

Here is one method:
with t as (
<your query here>
)
select i.id, rt.roundedtime, coalesce(data, 0) as data
from (select distinct id from t) i cross join
(select distinct roundedtime rt from t) rt left join
t
on t.id = i.id and t.roundedtime = rt.roundedtime;
In other words, create the list of dates and ids using a cross join. Then use a left join to bring in your data.
This query uses select distinct on your original data to get the lists of dates and ids. There may be more efficient ways to get each of these lists.

One more way with calendar table and CROSS JOIN:
;WITH YourQueryOutput AS (
--put your select statement here
), calendar AS (
SELECT CAST(MIN(RoundedTime) as datetime) as d,
MAX(RoundedTime) as e
FROM YourQueryOutput
UNION ALL
SELECT DATEADD(day,1,d),
e
FROM calendar
WHERE d < e
)
SELECT t.ID,
c.d,
COALESCE(t1.[data],0) as [data]
FROM calendar c
CROSS JOIN (
SELECT DISTINCT ID
FROM YourQueryOutput
) t
LEFT JOIN YourQueryOutput t1
ON t.ID = t1.ID and t1.RoundedTime = c.d
ORDER BY t.ID, c.d
OPTION(MAXRECURSION 0)
Output for sample you provided
ID d data
10 2016-09-23 00:00:00.000 1
10 2016-09-24 00:00:00.000 5
10 2016-09-25 00:00:00.000 0
10 2016-09-26 00:00:00.000 0
10 2016-09-27 00:00:00.000 0
10 2016-09-28 00:00:00.000 0
...
178 2016-09-23 00:00:00.000 0
178 2016-09-24 00:00:00.000 4
178 2016-09-25 00:00:00.000 0
178 2016-09-26 00:00:00.000 0
178 2016-09-27 00:00:00.000 0
178 2016-09-28 00:00:00.000 6

You can check ISNULL(YourDataColumn,0) in SELECT statement where you used join.
Example:
SELECT
Q.QuestionId,
Q.SenderType,
ISNULL(Q.Data,0) AS Data
FROM #tblQuestion Q
LEFT JOIN #tblTeacher T ON Q.SenderId=T.Id AND Q.SENDERTYPE='TEACHER'
LEFT JOIN #tblInstitute I ON Q.SenderId=I.Id AND Q.SENDERTYPE='INSTITUTE'
IN above Select Statement Data column return 0 if no data available after join

Related

Returning NULLs for data which does not exist for certain dates [duplicate]

This question already has answers here:
Fill Missing Dates In a Date-Sequenced in SQL using Tally Table
(2 answers)
Closed 1 year ago.
I have a data table similar to the below:
EntityId
Date
Value
1
5/30/2021
42
1
6/30/2021
35
1
7/31/2021
59
1
8/31/2021
61
2
7/31/2021
98
2
8/31/2021
100
3
8/31/2021
34
I want to return all values in the "Value" column between calendar month end dates 5/31/2021 and 8/31/2021 for each unique entityId. However, each unique entityId does not always have a row for all such dates, in which I would like to return a null in the Value column should the date not exist. Using the query on the table should result in the following data:
EntityId
Date
Value
1
5/31/2021
42
1
6/30/2021
35
1
7/31/2021
59
1
8/31/2021
59
2
5/31/2021
NULL
2
6/30/2021
NULL
2
7/31/2021
98
2
8/31/2021
100
3
5/31/2021
NULL
3
6/30/2021
NULL
3
7/31/2021
NULL
3
8/31/2021
34
What would be the easiest way to accomplish this?
You need to start with a list of dates and entities. Let me assume they are all in the table. So, the idea is to use cross join to generate the rows in the result set. Then use left join to bring in the remaining data:
select e.entityid, d.date, t.value
from (select distinct entityid from data) e cross join
(select distinct date from data) d left join
data t
on t.entityid = e.entityid and t.date = d.date;
Note: This uses subqueries to generate the dates and entities. If this information is in other tables, you can directly use those tables.
To create the full range of EntityId's and Date's the query uses the CROSS JOIN of distinct values as 'range_cte'. The original data 'data_cte' is then FULL OUTER JOIN'ed with 'range_cte'. Something like this
declare
#start_dt date='20210531',
#end_dt date='20210831';
;with
data_cte(EntityId, [Date], [Value]) as (
select *
from (values (1, cast('20210531' as date), 42),
(1, cast('20210630' as date), 35),
(1, cast('20210731' as date), 59),
(1, cast('20210831' as date), 59),
(2, cast('20210731' as date), 98),
(2, cast('20210831' as date), 100),
(3, cast('20210831' as date), 34))
v(EntityId, [Date], [Value])),
unq_id_cte(EntityId) as (
select distinct EntityId
from data_cte),
nums_cte(n) as (
select *
from (values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12)) v(n)),
range_cte as (
select top(datediff(month, #start_dt, #end_dt)+1)
eomonth(#start_dt, row_number() over (order by (select null))-1) mo_dt
from nums_cte n1
cross join nums_cte n2)
select ui.EntityId, coalesce(r.mo_dt, d.[Date]) [Date], d.[Value]
from
unq_id_cte ui
cross join range_cte r
full join data_cte d on ui.EntityId=d.EntityId
and r.mo_dt=d.[Date]
order by ui.EntityId, r.mo_dt;
EntityId Date Value
1 2021-05-31 42
1 2021-06-30 35
1 2021-07-31 59
1 2021-08-31 59
2 2021-05-31 NULL
2 2021-06-30 NULL
2 2021-07-31 98
2 2021-08-31 100
3 2021-05-31 NULL
3 2021-06-30 NULL
3 2021-07-31 NULL
3 2021-08-31 34

Conceptualizing SQL Query for data that isn't there

SQL Server 2017.
Having been running simple-to-intermediate SQL queries for many years, I'm having trouble wrapping my head around this one, as it's querying for information that doesn't actually exist.
Given a table called Activity with ProductId (int), PurchaseDate (datetime)
and some rows that look like this:
1 2020-10-31
1 2020-11-01
1 2020-11-02
1 2020-11-03
2 2020-10-31
2 2020-11-01
2 2020-11-03
2 2020-11-04
3 2020-10-31
3 2020-11-01
4 2020-10-31
4 2020-11-01
4 2020-11-03
5 2020-10-20
6 2020-10-31
6 2020-11-01
6 2020-11-02
And then another table called ProductIds with column ProductId (int) and 7 rows, with values 1-7:
I need to return from the Activity table any ProductIds that do not have an entry for a date from a date range, as well as the date that doesn't have the entry. This would be the results:
2 2020-11-02
3 2020-11-02
3 2020-11-03
4 2020-11-02
5 2020-10-31
5 2020-11-01
5 2020-11-02
5 2020-11-03
6 2020-11-03
7 2020-10-31
7 2020-11-01
7 2020-11-02
7 2020-11-03
So the query would be looking for any ProductId from the ProductIds table that does not have an associated entry in the Activity table for dates between 2020-10-31 and 2020-11-03.
This is what I have so far, but bangin' my head trying to figure it out:
SELECT ProductId, PurchaseDate
FROM dbo.Activity
WHERE ProductId NOT IN (SELECT ProductId FROM dbo.ProductIds);
I know there are at least a couple things wrong with that query and I just can't figure out how to go about this. As you can see, the results set is returning information that doesn't exist in the table, hence my confusion.
Try this:
DECLARE #Activity TABLE
(
[ProductId] INT
,[PurchaseDate] DATE
);
DECLARE #ProductIds TABLE
(
[ProductId] INT
);
INSERT INTO #Activity ([ProductId], [PurchaseDate])
VALUES (1, '2020-10-31')
,(1, '2020-11-01')
,(1, '2020-11-02')
,(1, '2020-11-03')
,(2, '2020-10-31')
,(2, '2020-11-01')
,(2, '2020-11-03')
,(2, '2020-11-04')
,(3, '2020-10-31')
,(3, '2020-11-01')
,(4, '2020-10-31')
,(4, '2020-11-01')
,(4, '2020-11-03')
,(5, '2020-10-20')
,(6, '2020-10-31')
,(6, '2020-11-01')
,(6, '2020-11-02');
INSERT INTO #ProductIds ([ProductId])
VALUES (1), (2), (3), (4), (5), (6), (7);
DECLARE #date_beg DATE = '2020-10-31'
,#date_end DATE = '2020-11-03';
SELECT P.[ProductId]
,Dates.[Date]
FROM #ProductIds P
CROSS APPLY
(
SELECT DATEADD(DAY, nbr - 1, #date_beg)
FROM
(
SELECT ROW_NUMBER() OVER ( ORDER BY c.object_id ) AS Nbr
FROM sys.columns c
) nbrs
WHERE nbr - 1 <= DATEDIFF(DAY, #date_beg, #date_end)
) Dates ([Date])
LEFT JOIN #Activity A
ON P.[ProductId] = A.[ProductId]
AND Dates.[Date] = A.[PurchaseDate]
WHERE A.[ProductId] IS NULL
ORDER BY P.[ProductId]
,Dates.[Date];
You can generate the date series with a recursive query, then cross join that with the list of products to generate all possible combinations. Finally, you can use not exists to filter on tuples that do not exists in the activity table.
with dates as (
select convert(date, '20201031') purchasedate
union all select dateadd(day, 1, purchasedate) from dates where purchasedate < '20201103'
)
select p.productid, d.purchasedate
from productids p
cross join dates d
where not exists (
select 1
from activity a
where a.productid = p.productid and a.purchasedate = d.purchasedate
)
If you have a date range that spans over more than 100 days, you need to add option (maxrecusion 0) at the very end of the query.
Relational databases have a basis in set theory, so naturally they have set operators built for all sets operations. We're just used to using operators that result in intersections that we overlook other possibilities.
If you don't have a date table, you can generate a range of dates with a recursive query. Then create a set that's a cross product of ProductId and Date, and exclude and members that are in Activity using Except.
DECLARE #MinDate DATE = '2020-10-31';
DECLARE #MaxDate DATE = '2020-11-03';
WITH Dates AS (
SELECT #MinDate [PurchaseDate]
UNION ALL
SELECT DATEADD(DAY, 1, PurchaseDate)
FROM Dates
WHERE PurchaseDate < #MaxDate
)
SELECT p.ProductId, d.PurchaseDate FROM ProductIds p CROSS JOIN Dates d
EXCEPT SELECT ProductId, PurchaseDate FROM Activity

Given two date ranged discount tables and product price, calculate date ranged final price

I have two tables with seasonal discounts. In each of these two tables are non overlapping date ranges, product id and discount that applies in that date range. Date ranges from one table however may overlap with date ranges in the other table. Given a third table with product id and its default price, the goal is to efficiently calculate seasonal - date ranged prices for product id after discounts from both tables have been applied.
Discounts multiply only in their overlapping period, e.g. if a first discount is 0.9 (10%) from 2019-07-01 to 2019-07-30, and a second discount is 0.8 from 2019-07-16 to 2019-08-15, this translates to: 0.9 discount from 2019-07-01 to 2019-07-15, 0.72 discount from 2019-07-16 to 2019-07-30, and 0.8 discount from 2019-07-31 to 2019-08-15.
I have managed to come to a solution, by first generating a table that holds ordered all of start and end dates in both discount tables, then generating a resulting table of all smallest disjoint intervals, and then for each interval, generating all prices, default, price with only the discount from first table applied (if any applies), price with only the discount from second table applied (if any applies), price with both discounts applied (if so possible) and then taking a min of these four prices. See sample code bellow.
declare #pricesDefault table (product_id int, price decimal)
insert into #pricesDefault
values
(1, 100),
(2, 120),
(3, 200),
(4, 50)
declare #discountTypeA table (product_id int, modifier decimal(4,2), startdate datetime, enddate datetime)
insert into #discountTypeA
values
(1, 0.75, '2019-06-06', '2019-07-06'),
(1, 0.95, '2019-08-06', '2019-08-20'),
(1, 0.92, '2019-05-06', '2019-06-05'),
(2, 0.75, '2019-06-08', '2019-07-19'),
(2, 0.95, '2019-07-20', '2019-09-20'),
(3, 0.92, '2019-05-06', '2019-06-05')
declare #discountTypeB table (product_id int, modifier decimal(4,2), startdate datetime, enddate datetime)
insert into #discountTypeB
values
(1, 0.85, '2019-06-20', '2019-07-03'),
(1, 0.65, '2019-08-10', '2019-08-29'),
(1, 0.65, '2019-09-10', '2019-09-27'),
(3, 0.75, '2019-05-08', '2019-05-19'),
(2, 0.95, '2019-05-20', '2019-05-21'),
(3, 0.92, '2019-09-06', '2019-09-09')
declare #pricingPeriod table(product_id int, discountedPrice decimal, startdate datetime, enddate datetime);
with allDates(product_id, dt) as
(select distinct product_id, dta.startdate from #discountTypeA dta
union all
select distinct product_id, dta.enddate from #discountTypeA dta
union all
select distinct product_id, dtb.startdate from #discountTypeB dtb
union all
select distinct product_id, dtb.enddate from #discountTypeB dtb
),
allproductDatesWithId as
(select product_id, dt, row_number() over (partition by product_id order by dt asc) 'Id'
from allDates),
sched as
(select pd.product_id, apw1.dt startdate, apw2.dt enddate
from #pricesDefault pd
join allproductDatesWithId apw1 on apw1.product_id = pd.product_id
join allproductDatesWithId apw2 on apw2.product_id = pd.product_id and apw2.Id= apw1.Id+1
),
discountAppliedTypeA as(
select sc.product_id, sc.startdate, sc.enddate,
min(case when sc.startdate >= dta.startdate and dta.enddate >= sc.enddate then pd.price * dta.modifier else pd.price end ) 'price'
from sched sc
join #pricesDefault pd on pd.product_id = sc.product_id
left join #discountTypeA dta on sc.product_id = dta.product_id
group by sc.product_id, sc.startdate , sc.enddate ),
discountAppliedTypeB as(
select daat.product_id, daat.startdate, daat.enddate,
min(case when daat.startdate >= dta.startdate and dta.enddate >= daat.enddate then daat.price * dta.modifier else daat.price end ) 'price'
from discountAppliedTypeA daat
left join #discountTypeB dta on daat.product_id = dta.product_id
group by daat.product_id, daat.startdate , daat.enddate )
select * from discountAppliedTypeB
order by product_id, startdate
Calculating a min of all possible prices is unnecessary overhead. I'd like to generate, just one resulting price and have it as a final price.
Here is the resulting set:
product_id start_date end_date final_price
1 2019-05-06 00:00:00.000 2019-06-05 00:00:00.000 92.0000
1 2019-06-05 00:00:00.000 2019-06-06 00:00:00.000 100.0000
1 2019-06-06 00:00:00.000 2019-06-20 00:00:00.000 75.0000
1 2019-06-20 00:00:00.000 2019-07-03 00:00:00.000 63.7500
1 2019-07-03 00:00:00.000 2019-07-06 00:00:00.000 75.0000
1 2019-07-06 00:00:00.000 2019-08-06 00:00:00.000 100.0000
1 2019-08-06 00:00:00.000 2019-08-10 00:00:00.000 95.0000
1 2019-08-10 00:00:00.000 2019-08-20 00:00:00.000 61.7500
1 2019-08-20 00:00:00.000 2019-08-29 00:00:00.000 65.0000
1 2019-08-29 00:00:00.000 2019-09-10 00:00:00.000 100.0000
1 2019-09-10 00:00:00.000 2019-09-27 00:00:00.000 65.0000
2 2019-05-20 00:00:00.000 2019-05-21 00:00:00.000 114.0000
2 2019-05-21 00:00:00.000 2019-06-08 00:00:00.000 120.0000
2 2019-06-08 00:00:00.000 2019-07-19 00:00:00.000 90.0000
2 2019-07-19 00:00:00.000 2019-07-20 00:00:00.000 120.0000
2 2019-07-20 00:00:00.000 2019-09-20 00:00:00.000 114.0000
3 2019-05-06 00:00:00.000 2019-05-08 00:00:00.000 184.0000
3 2019-05-08 00:00:00.000 2019-05-19 00:00:00.000 138.0000
3 2019-05-19 00:00:00.000 2019-06-05 00:00:00.000 184.0000
3 2019-06-05 00:00:00.000 2019-09-06 00:00:00.000 200.0000
3 2019-09-06 00:00:00.000 2019-09-09 00:00:00.000 184.0000
Is there a more efficient to this solution that I am not seeing?
I have a large data set of ~20K rows in real product prices table, and 100K- 200K rows in both discount tables.
Indexing structure of the actual tables is following: product id is clustered index in product prices table, whilst discount tables have an Id surrogate column as clustered index (as well as primary key), and (product_id, start_date, end_date) as a non clustered index.
You can generate the dates using union. Then bring in all discounts that are valid on that date, and calculate the total.
This looks like:
with prices as (
select a.product_id, v.dte
from #discountTypeA a cross apply
(values (a.startdate), (a.enddate)) v(dte)
union -- on purpose to remove duplicates
select b.product_id, v.dte
from #discountTypeB b cross apply
(values (b.startdate), (b.enddate)) v(dte)
),
p as (
select p.*, 1-a.modifier as a_discount, 1-b.modifier as b_discount, pd.price
from prices p left join
#pricesDefault pd
on pd.product_id = p.product_id left join
#discountTypeA a
on p.product_id = a.product_id and
p.dte >= a.startdate and p.dte < a.enddate left join
#discountTypeb b
on p.product_id = b.product_id and
p.dte >= b.startdate and p.dte < b.enddate
)
select p.product_id, price * (1 - coalesce(a_discount, 0)) * (1 - coalesce(b_discount, 0)) as price, a_discount, b_discount,
dte as startdate, lead(dte) over (partition by product_id order by dte) as enddate
from p
order by product_id, dte;
Here is a db<>fiddle.
Here is a version that works out the price for every date. You can then either use this directly, or use one of the many solutions on SO for working out date ranges.
In this example I have hard coded the date limits, but you could easily read them from your tables if you prefer.
I haven't done any performance testing on this, but give it a go. Its quite a bit simpler do if you have the right indexes it might be quicker.
;with dates as (
select convert(datetime,'2019-05-06') as d
union all
select d+1 from dates where d<'2019-09-27'
)
select pricesDefault.product_id, d, pricesDefault.price as baseprice,
discountA.modifier as dA,
discountB.modifier as dB,
pricesDefault.price*isnull(discountA.modifier,1)*isnull(discountB.modifier,1) as finalprice
from #pricesDefault pricesDefault
cross join dates
left join #discountTypeA discountA on discountA.product_id=pricesDefault.product_id and d between discountA.startdate and discountA.enddate
left join #discountTypeB discountB on discountB.product_id=pricesDefault.product_id and d between discountB.startdate and discountB.enddate
order by pricesDefault.product_id, d
Option (MaxRecursion 1000)

Finding time differences between each row in a specific column in SQL Server

Still learning SQL, but I'm trying to see if there are any Customers that have a time frame within 24 hours of each other. So in this example ID 1 and 4 meet this criteria.
CustID Date
1 2018-04-10 11:21:00.000
1 2018-03-05 18:14:00.000
1 2018-03-05 22:53:00.000
2 2018-04-10 11:21:00.000
2 2018-03-27 14:57:00.000
2 2018-04-04 20:00:00.000
3 2018-04-10 11:21:00.000
3 2018-02-10 11:21:00.000
3 2018-04-24 11:29:00.000
4 2018-04-10 11:21:00.000
4 2018-04-10 11:20:00.000
4 2018-04-24 11:29:00.000
I'm thinking about doing something like
SELECT CustId
From Cars c
CROSS APPLY(
SELECT Date
FROM Cars
Where Date != c.Date)
WHERE Date - c.Date < 24 hours
Use lag():
select distinct custid
from (select c.*,
lag(c.date) over (partition by c.custid order by c.date) as prev_date
from cars c
) c
where date < dateadd(hour, 24, prev_date);
This answer is based on sql-server, but you should be able to translate as needed. I also assumed you had a requirement where the same datetime between two customers can't be the same. If that's a false assumption, remove the where clause. A simple self-join should get you there.
declare #t table (id int, dt datetime)
insert into #t values ('1','2018-04-10 11:21:00.000')
insert into #t values ('1','2018-03-05 18:14:00.000')
insert into #t values ('1','2018-03-05 22:53:00.000')
insert into #t values ('2','2018-04-10 11:21:00.000')
insert into #t values ('2','2018-03-27 14:57:00.000')
insert into #t values ('2','2018-04-04 20:00:00.000')
insert into #t values ('3','2018-04-10 11:21:00.000')
insert into #t values ('3','2018-02-10 11:21:00.000')
insert into #t values ('3','2018-04-24 11:29:00.000')
insert into #t values ('4','2018-04-10 11:21:00.000')
insert into #t values ('4','2018-04-10 11:20:00.000')
insert into #t values ('4','2018-04-24 11:29:00.000')
select
t1.id, t2.id
from #t t1
join #t t2 on t2.dt between dateadd(hh, -24,t1.dt) and t1.dt and t1.id<>t2.id
where t1.dt<>t2.dt

Table with dates, table with week numbers, join together?

I have two tables. Table 1:
StuAp_Id StuAp_StaffID StuAp_Date StuAp_Attended
16 77000002659366 2011-09-07 Yes
17 77000002659366 2011-09-14 Yes
18 77000002659366 2011-09-14 Yes
19 77000002659366 2011-09-14 No
20 77000001171783 2011-09-19 Yes
Table 2:
Year Week Start
2011 1 2011-09-05 00:00:00.000
2011 2 2011-09-12 00:00:00.000
2011 3 2011-09-19 00:00:00.000
2011 4 2011-09-26 00:00:00.000
2011 5 2011-10-03 00:00:00.000
2011 6 2011-10-10 00:00:00.000
2011 7 2011-10-17 00:00:00.000
2011 8 2011-10-24 00:00:00.000
2011 9 2011-10-31 00:00:00.000
How would I join these two tables to make something like this:
StuAp_Id StuAp_StaffID StuAp_Date StuAp_Attended Week
16 77000002659366 2011-09-07 Yes 1
17 77000002659366 2011-09-14 Yes 2
18 77000002659366 2011-09-14 Yes 2
19 77000002659366 2011-09-14 No 2
20 77000001171783 2011-09-19 Yes 3
Thanks in advance
You can write simple INNER JOIN using GROUP BY clause.
SELECT Table1.*
,MAX(WEEK) AS WEEK
FROM Table1
INNER JOIN Table2 ON STUAP_DATE >= START
GROUP BY STUAP_ID,STUAP_STAFFID,STUAP_DATE,STUAP_ATTENDED
don't know about specifics on sql2k5 (don't have one around to test) but I would use a sub select eg.
select table_1.*,
[week] = (select isnull(max([week]), 0)
from table_2
where table_1.StuAp_Date >= table_2.start)
from table_1
CTEs to the rescue!
create table StuAp (
StuAp_Id int,
StuAp_StaffID bigint,
StuAp_Date datetime,
StuAp_Attended varchar(3)
)
create table Weeks (
Year int,
Week int,
Start datetime
)
insert into StuAp
values (16, 77000002659366, {d '2011-09-07'}, 'Yes'),
(17, 77000002659366, {d '2011-09-14'}, 'Yes'),
(18, 77000002659366, {d '2011-09-14'}, 'Yes'),
(19, 77000002659366, {d '2011-09-14'}, 'No'),
(20, 77000001171783, {d '2011-09-19'}, 'Yes')
insert into Weeks
values (2011, 1, {d '2011-09-05'}),
(2011, 2, {d '2011-09-12'}),
(2011, 3, {d '2011-09-19'}),
(2011, 4, {d '2011-09-26'}),
(2011, 5, {d '2011-10-03'}),
(2011, 6, {d '2011-10-10'}),
(2011, 7, {d '2011-10-17'}),
(2011, 8, {d '2011-10-24'}),
(2011, 9, {d '2011-10-31'})
;with OrderedWeeks as (
select ROW_NUMBER() OVER (ORDER BY year, week) as row, w.*
from Weeks w
), Ranges as (
select w1.*, w2.Start as Finish
from OrderedWeeks w1 inner join
OrderedWeeks w2 on w1.row = w2.row - 1
)
select s.StuAp_Id, s.StuAp_StaffID, s.StuAp_Date, s.StuAp_Attended, r.Week
from StuAp s inner join
Ranges r on s.StuAp_Date >= r.Start and s.StuAp_Date < r.Finish
This should scale quite well too.
Honestly though, if you find yourself doing queries like this often, you should really consider changing the stucture of your Weeks table to include a finish date. You could even make it an indexed view, or (assuming that the data changes rarely), you could keep your original table and use triggers or a SQL Agent job to keep a copy that contains Finish up to date.
SET ANSI_WARNINGS ON;
GO
DECLARE #Table1 TABLE
(
StuAp_Id INT PRIMARY KEY
,StuAp_StaffID NUMERIC(14,0) NOT NULL
,StuAp_Date DATETIME NOT NULL
,StuAp_Attended VARCHAR(3) NOT NULL
,StuAp_DateOnly AS DATEADD(DAY, DATEDIFF(DAY,0,StuAp_Date), 0) PERSISTED
);
INSERT #Table1
SELECT 16,77000002659366 ,'2011-09-07','Yes'
UNION ALL
SELECT 17,77000002659366 ,'2011-09-14','Yes'
UNION ALL
SELECT 18,77000002659366 ,'2011-09-14','Yes'
UNION ALL
SELECT 19,77000002659366 ,'2011-09-14','No'
UNION ALL
SELECT 20,77000001171783 ,'2011-09-19','Yes';
DECLARE #Table2 TABLE
(
Year INT NOT NULL
,Week INT NOT NULL
,Start DATETIME NOT NULL
,[End] AS DATEADD(DAY,6,Start) PERSISTED
,PRIMARY KEY(Year, Week)
,UNIQUE(Start)
);
INSERT #Table2
SELECT 2011,1 ,'2011-09-05 00:00:00.000'
UNION ALL
SELECT 2011,2 ,'2011-09-12 00:00:00.000'
UNION ALL
SELECT 2011,3 ,'2011-09-19 00:00:00.000'
UNION ALL
SELECT 2011,4 ,'2011-09-26 00:00:00.000'
UNION ALL
SELECT 2011,5 ,'2011-10-03 00:00:00.000'
UNION ALL
SELECT 2011,6 ,'2011-10-10 00:00:00.000'
UNION ALL
SELECT 2011,7 ,'2011-10-17 00:00:00.000'
UNION ALL
SELECT 2011,8 ,'2011-10-24 00:00:00.000'
UNION ALL
SELECT 2011,9 ,'2011-10-31 00:00:00.000';
--Solution 1 : if StuAp_Date has only date part
SELECT a.*, b.Week
FROM #Table1 a
INNER JOIN #Table2 b ON a.StuAp_Date BETWEEN b.Start AND b.[End]
--Solution 2 : if StuAp_Date has only date part
SELECT a.*, b.Week
FROM #Table1 a
INNER JOIN #Table2 b ON a.StuAp_Date BETWEEN b.Start AND DATEADD(DAY,6,b.Start)
--Solution 3 : if StuAp_Date has date & time
SELECT a.*, b.Week
FROM #Table1 a
INNER JOIN #Table2 b ON a.StuAp_DateOnly BETWEEN b.Start AND b.[End]
--Solution 4 : if StuAp_Date has date & time
SELECT a.*, b.Week
FROM #Table1 a
INNER JOIN #Table2 b ON DATEADD(DAY, DATEDIFF(DAY,0,a.StuAp_Date), 0) BETWEEN b.Start AND DATEADD(DAY,6,b.Start)