Not able to display correct data using pivot table - sql-server

Table structure:
This is the data for which i want to use pivot table
i want to display the result as
Teacher Activity [2013-11-22] and so on The date field will display the count of the attendance.
Below is the code which i am using, result is coming as 0 under every date, which is not correct:
DECLARE #temp AS TABLE(dates datetime)
;with cte (datelist, maxdate) as
(
select DATEADD(dd,-3,GETDATE()), GETDATE()
union all
select dateadd(dd, 1, datelist), maxdate
from cte
where datelist < maxdate
)
INSERT INTO #temp
SELECT c.datelist
FROM cte c
DECLARE #listdate nvarchar(MAX)
SELECT #listdate = (select(STUFF( (SELECT ', ' + QUOTENAME(convert(CHAR(10), dates, 120))
from #temp ORDER BY dates asc
FOR XML PATH('')),1, 1, '')))
print #listdate
exec('(SELECT * INTO ##temptable from
(select distinct teachername as Teacher,activityname AS Activity,memberid as attendance,
QUOTENAME(convert(CHAR(10), attendancedate, 120)) AS DATES from
tbteachertimelog inner join tbteacher on
teacherid = tbteacher.id
inner join tbactivity on tbactivity.id = tbteachertimelog.activityid
left join tbattendance on tbattendance.activityid = tbteachertimelog.activityid and
convert(CHAR(10), tbattendance.attendancedate, 120) = convert(CHAR(10), tbteachertimelog.date, 120)
group by teachername,activityname,memberid,
attendancedate
) p
PIVOT
(
count(attendance)
FOR DATES IN ('+#listdate+')) AS pvt
)
')
alter table ##temptable add TotalStudents int,meetings int,total64 int
update ##temptable set TotalStudents =
(SELECT SUM(memcount) FROM (select count(distinct memberid) as memcount from tbteachertimelog
inner join tbattendance on tbattendance.activityid = tbteachertimelog.activityid and
convert(CHAR(10), tbattendance.attendancedate, 120) = convert(CHAR(10), tbteachertimelog.date, 120)
where --teacherid = ##temptable.teacherid and tbteachertimelog.activityid = ##temptable.activityid
--and
tbattendance.attendancedate >= dateadd(dd,-7,getdate()) and tbattendance.attendancedate <= getdate()
group by convert(CHAR(10), tbattendance.attendancedate, 120)) x)
update ##temptable set meetings =
(select count(distinct convert(CHAR(10), tbattendance.attendancedate, 120)) as dayscount from tbteachertimelog
inner join tbattendance on tbattendance.activityid = tbteachertimelog.activityid and
convert(CHAR(10), tbattendance.attendancedate, 120) = convert(CHAR(10), tbteachertimelog.date, 120)
where teacherid = ##temptable.teacherid and tbteachertimelog.activityid = ##temptable.activityid)
select * from ##temptable
drop table ##temptable

The problem is with the following line in your subquery:
QUOTENAME(convert(CHAR(10), attendancedate, 120)) AS DATES
The QUOTENAME is required when you are creating the list of dates as the column headers but since you added this to your subquery your dates in the table appear:
DATES
[2013-11-22]
[2013-11-23]
[2013-11-24]
But you want the dates being returned inside your subquery should be without the brackets:
DATES
2013-11-22
2013-11-23
2013-11-24
The PIVOT function is looking for dates that match your data but since you have the dates surrounded with square brackets you don't get any matches, as a result you are returning zeros for everything.
Your columns don't match your data because of the bracket -- for example:
Header -- YourDate
2013-11-22 -- does not match [2013-11-22]
2013-11-23 -- does not match [2013-11-23]
2013-11-24 -- does not match [2013-11-24]
The query should be:
exec('
with data as
(
SELECT *
from
(
select distinct teachername as Teacher,
activityname AS Activity,
memberid as attendance,
convert(CHAR(10), attendancedate, 120) AS DATES
from tbteachertimelog
inner join tbteacher
on teacherid = tbteacher.id
inner join tbactivity
on tbactivity.id = tbteachertimelog.activityid
inner join tbattendance
on tbattendance.activityid = tbactivity.id
) p
PIVOT
(
count(attendance)
FOR DATES IN ('+#listdate+')
) AS pvt
)
select *
from data
')

Related

Using Max with CTE and CASE T-SQL

I am trying to use MAX() to select the most recent placement date within our database, and use Table_CTE so I can then select and filter between the dates desired.
BEGIN
DECLARE #Rangetill DATE, #Rangefrom DATE
SET #rangefrom = DATEADD(day, -50, GETDATE())
SET #Rangetill = DATEADD(day, -90, GETDATE());
WITH Table_CTE (Name, ID, Rangefrom, Rangetill, StatusID, Statusdate) AS
(
SELECT
PE.Personname + ' ' + PE.Surname [Name],
A.ApplicantId,
#rangefrom [Expiry warning from],
#rangetill [Expiry warning till],
A.Statusid,
selected = CASE
WHEN P.EndDate IS NOT NULL AND P.EndDate > A.StatusDate
THEN CONVERT(DATE, P.EndDate, 103)
ELSE CONVERT(DATE, A.StatusDate, 103)
END
FROM
Applicants AS A
LEFT JOIN
Person AS PE ON A.ApplicantId = PE.PersonID
LEFT JOIN
Placements AS P on A.applicantid = P.Applicantid
)
SELECT *
FROM Table_CTE
WHERE table_cte.Statusdate BETWEEN #Rangetill AND #Rangefrom
AND (Table_CTE.StatusID = 58 OR Table_CTE.statusid = 63)
ORDER BY Name DESC
END
The above selects the right information but also selects duplicate applicants with placement end dates (p.enddate) as they could have been placed more than once. The WHERE clause also limits the most recent enddate to within the range provided by the Variables and as there needs to be a log there will be multiple end dates. so my solution or idea would be to uses a max() within the Case or CTE Select. However I am not sure how to use or work with Max() in this case.
In this case I would like to check and return the Max(p.enddate) if it exists and store that in the statusdate of Table_CTE.
Is this possible and is it the best way to provide this information in a stored procedure?
In the CTE would be more efficient but this is easier
SELECT c.[Name], max(c.Statusdate)
FROM Table_CTE c
WHERE c.Statusdate Between #Rangetill and #Rangefrom
AND c.StatusID in (58, 63)
group by c.[Name]
Add the other columns on your own
Declare
#Rangetill date,
#Rangefrom date
SET #rangefrom = DATEADD(day, -50, GETDATE())
SET #Rangetill = DATEADD(day, -90, GETDATE());
With Table_CTE ( ID, Rangefrom, Rangetill, Statusdate)
AS (
Select
A.ApplicantId
, #rangefrom [Expiry warning from]
, #rangetill [Expiry warning till]
, selected = CASE
WHEN max(P.EndDate) IS NOT NULL AND max(P.EndDate) > max(A.StatusDate)
THEN max(CONVERT(DATE, P.EndDate, 103))
ELSE max(CONVERT(DATE, A.StatusDate, 103))
END
FROM Applicants AS A
LEFT JOIN Person AS PE ON A.ApplicantId = PE.PersonID
LEFT JOIN Placements AS P on A.applicantid = P.Applicantid
GROUP BY A.ApplicantId
)
SELECT
PE.PersonName + ' ' + PE.Surname [NAME]
, A.ApplicantId
, Table_CTE.ID
, Table_CTE.Statusdate
, #Rangefrom [Range from]
, #Rangetill [Range till]
FROM Table_CTE
LEFT JOIN Applicants AS A ON A.ApplicantId = Table_CTE.ID
LEFT JOIN Person as PE on PE.PersonID = A.ApplicantId
WHERE table_cte.Statusdate Between #Rangetill and #Rangefrom
AND (A.StatusID = 58 or A.statusid = 63 )
Order by PE.PersonName + ' '+ PE.Surname desc
END
Really messy way around things but I got my solution by removing all but the variables and ID from CTE so I could select Max(DATE) on both A.statusdate and P.EndDate.
By doing this I could group by A.ApplicantID and rejoin specific tables outside of the CTE in order to get Applicant Name and Status ID back into my results set.
Thank you for your help everyone.

Arithmetic overflow error converting expression to data type datetime sql

I cannot figure out where I've messed this up. Full code:
DECLARE #start_date DATETIME, #end_date DATETIME
DECLARE #Table TABLE (StartDate DATETIME, Enddate DATETIME, WeekNo INT)
SET #start_date = '2017-01-01'
SET #end_date = '2017-12-31'
INSERT INTO #Table
SELECT
MIN(dt), MAX(dt), w
FROM
(SELECT
dt, year(dt) y, DATEPART(week, dt) w
FROM
(SELECT
#start_date + (ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1) dt
FROM
sys.columns s1
CROSS JOIN
sys.columns s2) q
WHERE
dt BETWEEN #start_date AND #end_date) a
GROUP BY
y, w
I've narrowed down where the error is happening. It happens here:
SELECT
dt, year(dt) y, DATEPART(week, dt) w
FROM
(SELECT
#start_date + (ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1) dt
FROM
sys.columns s1
CROSS JOIN
sys.columns s2) q
WHERE
dt BETWEEN #start_date AND #end_date
If I run just this part I get the error:
Arithmetic overflow error converting expression to data type datetime.
I've tried adding in some CONVERT here and there trying to find where it's messing up, but I can not figure it out.
I have seen a couple of others who had a similar problem, but neither this one or this one have any solution that is helpful to me.
I'm new to the set based SQL programming, I'm self-taught in loops so this is out of my depth. Any help is appreciated.
ROW_NUMBER() returns a bigint value but adding days to a date is limited to int; so if the number of rows produced by the cross join exceeds the maximum value of an int you will produce that error message.
The number of rows in sys.columns only needs to exceed the square root of 2,147,483,647 (>=46,341) for the Cartesian product used in your code to produce that error. I was able to reproduce the same error on a much smaller sys.columns table by repeating the cross joins. e.g:
SELECT
#start_date + (ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1) dt
FROM
sys.columns s1
CROSS JOIN
sys.columns s2
CROSS JOIN
sys.columns s3
CROSS JOIN
sys.columns s4
One way to avoid the problem would be instead of adding row_number() to a date in the inner query, just return row_number() instead and use the where clause to avoid the error, e.g:
DECLARE #start_date DATETIME, #end_date DATETIME
DECLARE #Table TABLE (StartDate DATETIME, Enddate DATETIME, WeekNo INT)
SET #start_date = '2017-01-01'
SET #end_date = '2017-12-31'
INSERT INTO #Table
SELECT
MIN(dt), dateadd(day,7,MIN(dt)), w
FROM
(SELECT
dateadd(day,q.i,#start_date) dt, year(dateadd(day,q.i,#start_date)) y, DATEPART(week, dateadd(day,q.i,#start_date)) w
FROM (
SELECT
ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) - 1 i
FROM
sys.columns s1
CROSS JOIN
sys.columns s2
) q
WHERE
i <= datediff(day,#start_date,#end_date)
) q2
GROUP BY
y, w
;
Note there are many alternatives, particularly as you are really only want week ranges. You could use a permanent "tally table" instead of sys.columns, or use repeated cross joins of a 10 row CTE to create the wanted rows.
The accepted answer to this question Tally Table to insert missing dates between two dates? SQL is an example of using a permanent table of numbers (aka "tally table"). Also note by doing this you could add 7 days per row thus avoiding the need to group the days into weeks.
Here is an example using a repeated cross join of a CTE:
DECLARE #start_date DATETIME, #end_date DATETIME
DECLARE #Table TABLE (StartDate DATETIME, Enddate DATETIME, WeekNo INT)
SET #start_date = '2017-01-01'
SET #end_date = '2017-12-31'
;WITH
cteDigits AS (
SELECT 0 AS digit UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL
SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9
),
cteTally AS (
SELECT [100s].digit * 100 + [10s].digit * 10 + [1s].digit AS i
FROM cteDigits [1s]
CROSS JOIN cteDigits [10s]
CROSS JOIN cteDigits [100s]
)
SELECT
dateadd(day,q.i*7,#start_date) startDate
, dateadd(day,(q.i*7)+7,#start_date) endDate
, year(dateadd(day,q.i*7,#start_date)) y
, DATEPART(week, dateadd(day,q.i*7,#start_date)) w
FROM cteTally q
WHERE dateadd(day,(q.i*7)+7,#start_date) <= #end_date
ORDER BY q.i

How to get a list of months and year between two dates in SQL Server

I have to get the list of months and year in between my dates. Currently it only returns month and year for dates that has data associated with it.
for example my dates is between: '8'+'/1'+'/'+'2015' and DATEADD(mm, 15, '8'+'/1'+'/'+'2016'
It only prints out: May2016, June2016, July2016, Auguest2016, September2016
I want it to print out all of the months and year in between. Here is my sql queries:
select d.id_base as case_id,
c.C_LAST_ACTION AS Docketed,
c.C_CASE_TYPE AS caseType,
ct.C_NAME As caseName,
ct.C_DESCRIPTION AS caseNameDescription,
case when d.c_mod_decision_id is not null then '' else DATENAME(mm, d.c_issue_date) + DATENAME(yyyy, d.c_issue_date) end as display
from t_case_decision d JOIN T_CASE_INPUT c on c.id = d.id_base JOIN T_CASE_TYPE ct on C_CASE_TYPE = ct.id
where cast(d.c_issue_date AS date) BETWEEN '8'+'/1'+'/'+'2015' and DATEADD(mm, 15, '8'+'/1'+'/'+'2016')
First, create a numbers table
CREATE TABLE Numbers(N INT)
insert into Numbers(N)
select top 1000000 row_number() over(order by t1.number) as N
from master..spt_values t1
cross join master..spt_values t2
then use DATEADD to list dates between desired values, like this
declare #iniDate as date
set #iniDate='20150801'
select dateadd(MONTH,N,#iniDate) dates
from Numbers
where N<15 order by N
These returns dates from #iniDate up to 15 months later
EDIT: try this, I don't have sql right now
select datename(mm, dateadd(MONTH,N,#iniDate))+datename(yyyy ,dateadd(MONTH,N,#iniDate)) display
from ( select top 15row_number() over(order by t1.number) as N
from master..spt_values t1
cross join master..spt_values t2) numbers right join (
select d.id_base as case_id,
c.C_LAST_ACTION AS Docketed,
c.C_CASE_TYPE AS caseType,
ct.C_NAME As caseName,
ct.C_DESCRIPTION AS caseNameDescription,
case when d.c_mod_decision_id is not null then '' else DATENAME(mm, d.c_issue_date) + DATENAME(yyyy, d.c_issue_date) end as display
from t_case_decision d JOIN T_CASE_INPUT c on c.id = d.id_base JOIN T_CASE_TYPE ct on C_CASE_TYPE = ct.id
where cast(d.c_issue_date AS date) BETWEEN '8'+'/1'+'/'+'2015' and DATEADD(mm, 15, '8'+'/1'+'/'+'2016')
sql-server
) qq
on datename(mm, dateadd(MONTH,N,#iniDate))+datename(yyyy ,dateadd(MONTH,N,#iniDate)) = qq.display
where N<15 order by N
If I understand what you're trying to accomplish, a recursive CTE might help. Here's a quick example of what you can do. The CTE will expand out into a list of dates, which you can then use as the base for your query.
The contents of the TargetData CTE may need to be adjusted, as I don't have a complete picture of your data structure.
DECLARE #startDate DATE = '1/1/2015';
DECLARE #endDate DATE = '7/31/2016';
-- Recursive CTE to generate a list of months within the date range:
WITH Months AS (
SELECT CONVERT(DATE, DATEADD(D, -(DAY(#startDate)) + 1, #startDate)) [MonthDate]
UNION ALL
SELECT DATEADD(M, 1, MonthDate)
FROM Months
WHERE MonthDate <= DATEADD(M, -1, #endDate)
),
TargetData AS (
-- This is a slightly modified version of the original query:
select
d.id_base as case_id,
c.C_LAST_ACTION AS Docketed,
c.C_CASE_TYPE AS caseType,
ct.C_NAME As caseName,
ct.C_DESCRIPTION AS caseNameDescription,
case when d.c_mod_decision_id is not null then '' else DATENAME(mm, d.c_issue_date) + DATENAME(yyyy, d.c_issue_date) end as display,
-- Return the "MonthDate" so that it can be left joined to the Months table:
DATEADD(D, -(DAY(d.c_issue_date)) + 1, d.c_issue_date) [MonthDate]
from t_case_decision d JOIN T_CASE_INPUT c on c.id = d.id_base JOIN T_CASE_TYPE ct on C_CASE_TYPE = ct.id
where cast(d.c_issue_date AS date) BETWEEN #startDate AND #endDate
)
SELECT
m.MonthDate,
DATENAME(mm, m.MonthDate) + DATENAME(yyyy, m.MonthDate),
td.*
FROM Months m
LEFT JOIN TargetData td ON td.MonthDate = m.MonthDate;
You need to join on primary keys between tables, I haven't seen a between statement with that syntax. So I suggest trying the following:
SELECT d.id_base as case_id, c.C_LAST_ACTION AS 'Docketed',c.C_CASE_TYPE AScaseType,ct.C_NAME As 'caseName', ct.C_DESCRIPTION AS 'caseNameDescription'
,CASE
WHEN d.c_mod_decision_id is not null THEN '' AS 'null_val'
ELSE CONCAT(YEAR(d.c_issue_dateDATENAME), MONTH(d.c_issue_date))
END AS 'display'
FROM t_case_decision d INNER JOIN T_CASE_INPUT c on c.id = d.id_base
INNER JOIN T_CASE_TYPE ct on c.id = ct.id
WHERE CONVERT(DATE,d.c_issue_date) BETWEEN '08/01/2015'
AND '08/01/2016';
I hope this helps or points you in the right direction :)

How to change the query to give last 15 weeks of data instead of last 15 days from the SQL Server

The following query gives playing time of the users from the database on daily basis for the last 15 days. It adds 0 if no game is played. Now I want to get the data of playing time on weekly basis and 0 if no game is played in the whole week. So I want the query to give the last 15 weeks of data.
Here is the daily query.
CREATE PROCEDURE [dbo].[spGetPlayingTimeOfthepeoplesPerDay] #email NVARCHAR(50)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #MinDate DATE
,#MaxDate DATE
,#LastXDays INT
SELECT #LastXDays = - 15
SELECT #MaxDate = peoples.l_update
FROM peoples
WHERE peoples.email = #email
DECLARE #test TABLE (
quantity VARCHAR(100)
,DATE DATE
,TimePerDay DECIMAL(5, 2)
);
WITH CTE
AS (
SELECT peoples.email
,peoples.l_update
,act.quantity
,act.starttime
,act.endtime
,act.duration AS [Totaltime]
FROM peoples
INNER JOIN MPeoples ON peoples.Id = MPeoples.parent_id
INNER JOIN slines ON MPeoples.id = slines.movesuser_id
INNER JOIN seg ON slines.id = seg.sline_id
INNER JOIN act ON seg.id = act.seg_id
WHERE act.quantity = 'playing'
AND (peoples.email = #email)
GROUP BY peoples.email
,act.quantity
,act.duration
,act.starttime
,act.endtime
,peoples.l_update
)
INSERT INTO #test (
quantity
,DATE
,TimePerDay
)
SELECT quantity
,Cast(starttime AS DATE) AS DATE
,SUM(datediff(second, starttime, endtime)) / 60.0 AS TimePerDay
FROM cte WITH (NOLOCK)
WHERE starttime >= dateadd(day, #LastXDays, l_update)
GROUP BY quantity
,cast(starttime AS DATE)
SELECT #MaxDate = #MaxDate
,#MinDate = dateadd(day, (#LastXDays + 1), #MaxDate);
WITH AllDates
AS (
SELECT #MinDate AS xDate
UNION ALL
SELECT Dateadd(Day, 1, xDate)
FROM AllDates AS ad
WHERE ad.xDate < #MaxDate
)
SELECT 'playing' AS quantity
,ad.xDate
,Isnull(t.TimePerDay, 0) AS TimePerDay
FROM AllDates AS ad WITH (NOLOCK)
LEFT JOIN #test AS t ON ad.xDate = t.DATE
END
Change the DATEADD from day to week. Hence, two changes:
dateadd(week, #LastXDays, l_update)
and
dateadd(week, (#LastXDays + 1), #MaxDate)
In this case, I would also rename the #LastXDays variable to #LastXWeeks.
CREATE PROCEDURE [dbo].[spGetPlayingTimeOfthepeoplesPerDay] #email NVARCHAR(50)
AS
BEGIN
SET NOCOUNT ON;
DECLARE #MinDate DATE
,#MaxDate DATE
,#LastXDays INT
SELECT #LastXWeeks = - 15
SELECT #MaxDate = peoples.l_update
FROM peoples
WHERE peoples.email = #email
DECLARE #test TABLE (
quantity VARCHAR(100)
,DATE DATE
,TimePerDay DECIMAL(5, 2)
);
WITH CTE
AS (
SELECT peoples.email
,peoples.l_update
,act.quantity
,act.starttime
,act.endtime
,act.duration AS [Totaltime]
FROM peoples
INNER JOIN MPeoples ON peoples.Id = MPeoples.parent_id
INNER JOIN slines ON MPeoples.id = slines.movesuser_id
INNER JOIN seg ON slines.id = seg.sline_id
INNER JOIN act ON seg.id = act.seg_id
WHERE act.quantity = 'playing'
AND (peoples.email = #email)
GROUP BY peoples.email
,act.quantity
,act.duration
,act.starttime
,act.endtime
,peoples.l_update
)
INSERT INTO #test (
quantity
,DATE
,TimePerDay
)
SELECT quantity
,Cast(starttime AS DATE) AS DATE
,SUM(datediff(second, starttime, endtime)) / 60.0 AS TimePerDay
FROM cte WITH (NOLOCK)
WHERE starttime >= dateadd(week, #LastXWeeks, l_update)
GROUP BY quantity
,cast(starttime AS DATE)
SELECT #MaxDate = #MaxDate
,#MinDate = dateadd(week, (#LastXWeeks + 1), #MaxDate);
WITH AllDates
AS (
SELECT #MinDate AS xDate
UNION ALL
SELECT Dateadd(Day, 7, xDate)
FROM AllDates AS ad
WHERE ad.xDate < #MaxDate
)
SELECT 'playing' AS quantity
,ad.xDate
,Isnull(t.TimePerDay, 0) AS TimePerDay
FROM AllDates AS ad WITH (NOLOCK)
LEFT JOIN #test AS t ON ad.xDate = t.DATE
END
Also, a piece of advice: don't use query hints (NOLOCK) if you do not understand their use. In this case, using NOLOCK can have disastrous effects on your results.
Here are a few articles which you should read before deciding if you are going to keep using NOLOCK or not.
Understanding the SQL Server NOLOCK hint
Bad habits : Putting NOLOCK everywhere

Show LastDateOfMonth even where there is no value [duplicate]

This question already has answers here:
Left Join With Where Clause
(7 answers)
Closed 4 years ago.
Here is what I´m doing:
WITH cte AS (
SELECT * FROM TimeDim
)
SELECT t.TimeDimPK, c.ID
FROM CTE AS t
LEFT OUTER JOIN TABLE c ON c.TimeDimFK = t.TimeDimPK
ORDER BY t.TimeDimPK
WHERE c.ID = 1
Result, which is missing dates as shown below:
TimeDimPK ID
20120930 1
20121231 1
20130131 1
What I´m trying to get
TimeDimPK ID
20120930 1
20121031 NULL
20121130 NULL
20121231 1
20130131 1
It looks like your WHERE clause is getting rid of the other dates. Try this instead:
WITH cte
AS ( SELECT TimedimPK
FROM TimeDim
)
SELECT t.TimeDimPK ,
c.ID
FROM cte t
LEFT OUTER JOIN TABLEname c ON c.TimeDimFK = t.TimeDimPK
WHERE c.ID = 1
OR c.ID IS NULL
ORDER BY t.TimeDimPK
You can try to use a "number/date generator", this queries will fill in all missing days, then select the last day of the month. I am not sure about all your data details, so I am currently giving you two suggestions:
1 - Compact suggestion:
DECLARE #minDate DATETIME, #maxDate DATETIME;
SELECT #minDate = MIN(TimeDimPK), #maxDate = MAX(TimeDimPK) FROM TimeDim;
WITH DateGenerator AS
(
--Create a list with a lot of dates from #minDate
SELECT TimeDimPK = CONVERT(DATE, DATEADD(dd, ROW_NUMBER() OVER (ORDER BY OBJECT_ID), #minDate)) FROM sys.objects
)
--List all days, including missing days
SELECT TimeDimPK
, c.ID
FROM DateGenerator n
LEFT JOIN AnotherTable c ON c.TimeDimFK = n.TimeDimPK
WHERE
--Stop the number generator at the last day of the last month from the cte table
n.TimeDimPK <= CONVERT(DATE, DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0, #maxDate )+1,0)))
--This will get the last day of every month
AND TimeDimPK = CONVERT(DATE, DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,TimeDimPK)+1,0)))
2 - Suggestion with your cte clause, if you want to use it for some tweaking:
WITH NumberGenerator AS
(
--Create a number list 1,2,3,4,5,6,7,8,++
SELECT Number = ROW_NUMBER() OVER (ORDER BY OBJECT_ID) FROM sys.objects
), cte AS (
--Your cte query with a date number based on the days between the firts and current days
SELECT
TimeDimPK = CONVERT(DATETIME, TimeDimPK)
--Get the number of days to add from the first day in your table
, DateNumber = DATEDIFF(dd, MIN(TimeDimPK) OVER (), TimeDimPK)
FROM #TimeDim
), TableWithMissingDates AS
(
--Fill missing days
SELECT TimeDimPK = CONVERT(DATE, DATEADD(dd, n.Number - 1, MIN(t.TimeDimPK) OVER ()))
, c.ID
FROM NumberGenerator n
LEFT JOIN cte t ON t.DateNumber = n.Number
LEFT JOIN #Test2 c ON c.TimeDimFK = t.TimeDimPK
--Stop the number generator at the last day of the last month from the cte table
WHERE CONVERT(DATE, DATEADD(dd, n.Number - 1, (SELECT MIN(TimeDimPK) FROM cte))) < CONVERT(DATE, DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0, (SELECT MAX(TimeDimPK) FROM cte) )+1,0)))
)
SELECT * FROM TableWithMissingDates
WHERE
--This will get the last day of every month
TimeDimPK = CONVERT(DATE, DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,TimeDimPK)+1,0)))
Both queries will return the same table:

Resources