Reputation: 2628
I currently have the following query:
select
DATEADD(MONTH, DATEDIFF(MONTH, 0, StartDate), 0) as MonthYear, count(*) as Count
from
visit
where
StartDate >= DATEADD(year,-1,GETDATE())
and clientid = 142
group by
DATEADD(MONTH, DATEDIFF(MONTH, 0, StartDate), 0)
which returns the following results:
+-------------------------+----+
| 2015-12-01 00:00:00.000 | 1 |
| 2016-02-01 00:00:00.000 | 13 |
| 2016-03-01 00:00:00.000 | 1 |
| 2016-04-01 00:00:00.000 | 22 |
| 2016-05-01 00:00:00.000 | 22 |
| 2016-06-01 00:00:00.000 | 25 |
| 2016-07-01 00:00:00.000 | 36 |
+-------------------------+----+
I also have the following query:
SELECT TOP (12)
DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()) - test, 0) as MonthYear
FROM
test
which returns the following results:
+-------------------------+
| 2016-10-01 00:00:00.000 |
| 2016-09-01 00:00:00.000 |
| 2016-08-01 00:00:00.000 |
| 2016-07-01 00:00:00.000 |
| 2016-06-01 00:00:00.000 |
| 2016-05-01 00:00:00.000 |
| 2016-04-01 00:00:00.000 |
| 2016-03-01 00:00:00.000 |
| 2016-02-01 00:00:00.000 |
| 2016-01-01 00:00:00.000 |
| 2015-12-01 00:00:00.000 |
| 2015-11-01 00:00:00.000 |
+-------------------------+
What I need to do is combine the two queries into one query and show 0 for those months that are missing from the original query.
Could someone help please?
Upvotes: 9
Views: 530
Reputation: 352
you can format the query as follows
;with CTE AS (select
DATEADD(MONTH, DATEDIFF(MONTH, 0, StartDate), 0) as MonthYear, count(*) as Count
from
visit
where
StartDate >= DATEADD(year,-1,GETDATE())
and clientid = 142
group by
DATEADD(MONTH, DATEDIFF(MONTH, 0, StartDate), 0)
)
, cte2 AS(
SELECT TOP (12)
DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()) - test, 0) as MonthYear
FROM
test )
SELECT ISNULL(cte.MonthYear, cte2.MonthYear) AS MonthYear , ISNULL(cte.Count, 0) as Count
from cte FULL OUTER join cte2 on cte.MonthYear = cte2.MonthYear
Upvotes: 0
Reputation: 14361
so there are a lot of answers and good ones about how to create a MONTH table then left join your visits to get to your answer. But I just wanted to show you how conditional Aggregation could assist you if there will always be at least 1 date from each month in the visit table. Note, I do not mean 1 record for the client you are filtering but rather for any/all clients. In other words will there be at least 1 client in every month? If so you can do conditional aggregation to get to answer like so:
select
DATEADD(MONTH, DATEDIFF(MONTH, 0, StartDate), 0) as MonthYear
,count(CASE WHEN clientid = 142 THEN clientid END) as Count
from
visit
where
StartDate >= DATEADD(year,-1,GETDATE())
group by
DATEADD(MONTH, DATEDIFF(MONTH, 0, StartDate), 0)
If not then yep you will need a date table @pacreely shows you a nice method of doing that.
Upvotes: 0
Reputation: 1931
This method uses CROSS APPLY with VALUES to create your Master Month List.
I recommend replacing GETDATE() with a variable if you can.
CREATE TABLE #Visit(StartDate DATE,clientid int)
INSERT INTO #Visit VALUES
('20160304',142)
,('20160305',142)
,('20160508',142)
,('20160612',142)
,('20160617',142)
SELECT
T.Mnth
,COUNT(V.StartDate) AS [Count]
FROM
(VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12)) c (test)
CROSS APPLY (SELECT DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()) - c.test, 0) Mnth) T
LEFT JOIN #Visit V
ON
DATEDIFF(MM,V.startdate,T.Mnth) = 0
AND StartDate >= DATEADD(year,-1,GETDATE())
AND clientid = 142
GROUP BY
T.Mnth
ORDER BY
T.Mnth DESC
Upvotes: 1
Reputation: 859
After assuming that the "test" table is a table listing the first of each month for at least the past year and may include future dates as well, here is my suggestion:
With VisitList as
(SELECT
DATEADD(MONTH, DATEDIFF(MONTH, 0, StartDate), 0) as MonthYear, count(*) as VisitCount
FROM visit
WHERE
StartDate >= DATEADD(year,-1,GETDATE())
and clientid = 142
GROUP BY
DATEADD(MONTH, DATEDIFF(MONTH, 0, StartDate), 0)
UNION
SELECT testDate, 0 AS VisitCount
FROM test
WHERE testDate BETWEEN DATEADD(YEAR, -1, GETDATE()) AND GETDATE())
SELECT MonthYear, Sum(VisitCount) AS VisitCount FROM VisitList
GROUP BY MonthYear
ORDER BY MonthYear DESC
Upvotes: -1
Reputation: 31
declare @Dates table(date datetime, count int)
select
DATEADD(MONTH, DATEDIFF(MONTH, 0, StartDate), 0) as MonthYear, count(*) as Count
into @Dates
from
visit
where
StartDate >= DATEADD(year,-1,GETDATE())
and clientid = 142
group by
DATEADD(MONTH, DATEDIFF(MONTH, 0, StartDate), 0)
SELECT TOP (12)
into @Dates
DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()) - test, 0) as MonthYear,0
FROM
test
select * from @Dates
Upvotes: -1
Reputation: 9316
;with cteCalendar as
(
SELECT TOP (12)
DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()) - test, 0) as MonthYear
FROM
test
),
cteVisits as
(
select
DATEADD(MONTH, DATEDIFF(MONTH, 0, StartDate), 0) as MonthYear, count(*) as Count
from
visit
where
StartDate >= DATEADD(year,-1,GETDATE())
and clientid = 142
group by
DATEADD(MONTH, DATEDIFF(MONTH, 0, StartDate), 0)
)
SELECT
c.MonthYear,
ISNULL(v.Count, 0) as VisitCount
FROM cteCalendar c
LEFT JOIN cteVisits v
ON v.MonthYear = c.MonthYear
ORDER BY c.MonthYear
Upvotes: 0
Reputation: 82010
I often use a TVF to create dynamic date/time ranges. This udf is faster than the recursive cte approach (especially on larger sets). Furthermore, you get to define the date range, datepart, and increment. I should note, a tally table would do the trick as well.
-- Generate Some Sample Data
Declare @Visit table (StartDate date,Cnt int)
Insert Into @Visit values
('2015-12-01 00:00:00.000', 1),
('2016-02-01 00:00:00.000',13),
('2016-03-01 00:00:00.000', 1),
('2016-04-01 00:00:00.000',22),
('2016-05-01 00:00:00.000',22),
('2016-06-01 00:00:00.000',25),
('2016-07-01 00:00:00.000',36)
-- Set Key Dates
Declare @DateR2 date = '2016-10-01'
Declare @DateR1 date = DateAdd(YY,-1,@DateR2)
-- Execute Query
Select DateR1
,Cnt = isnull(sum(Cnt),0)
From (Select DateR1=RetVal,DateR2=DateAdd(MM,1,RetVal)-1 From [dbo].[udf-Range-Date](@DateR1,@DateR2,'MM',1)) A
Left Join @Visit B on B.StartDate Between A.DateR1 and A.DateR2
Group By DateR1
Order by DateR1
Returns
Period Cnt
2015-10-01 0
2015-11-01 0
2015-12-01 1
2016-01-01 0
2016-02-01 13
2016-03-01 1
2016-04-01 22
2016-05-01 22
2016-06-01 25
2016-07-01 36
2016-08-01 0
2016-09-01 0
2016-10-01 0
The UDF if needed
CREATE FUNCTION [dbo].[udf-Range-Date] (@R1 datetime,@R2 datetime,@Part varchar(10),@Incr int)
Returns Table
Return (
with cte0(M) As (Select 1+Case @Part When 'YY' then DateDiff(YY,@R1,@R2)/@Incr When 'QQ' then DateDiff(QQ,@R1,@R2)/@Incr When 'MM' then DateDiff(MM,@R1,@R2)/@Incr When 'WK' then DateDiff(WK,@R1,@R2)/@Incr When 'DD' then DateDiff(DD,@R1,@R2)/@Incr When 'HH' then DateDiff(HH,@R1,@R2)/@Incr When 'MI' then DateDiff(MI,@R1,@R2)/@Incr When 'SS' then DateDiff(SS,@R1,@R2)/@Incr End),
cte1(N) As (Select 1 From (Values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N(N)),
cte2(N) As (Select Top (Select M from cte0) Row_Number() over (Order By (Select NULL)) From cte1 a, cte1 b, cte1 c, cte1 d, cte1 e, cte1 f, cte1 g, cte1 h ),
cte3(N,D) As (Select 0,@R1 Union All Select N,Case @Part When 'YY' then DateAdd(YY, N*@Incr, @R1) When 'QQ' then DateAdd(QQ, N*@Incr, @R1) When 'MM' then DateAdd(MM, N*@Incr, @R1) When 'WK' then DateAdd(WK, N*@Incr, @R1) When 'DD' then DateAdd(DD, N*@Incr, @R1) When 'HH' then DateAdd(HH, N*@Incr, @R1) When 'MI' then DateAdd(MI, N*@Incr, @R1) When 'SS' then DateAdd(SS, N*@Incr, @R1) End From cte2 )
Select RetSeq = N+1
,RetVal = D
From cte3,cte0
Where D<=@R2
)
/*
Max 100 million observations -- Date Parts YY QQ MM WK DD HH MI SS
Syntax:
Select * from [dbo].[udf-Range-Date]('2016-10-01','2020-10-01','YY',1)
Select * from [dbo].[udf-Range-Date]('2016-01-01','2017-01-01','MM',1)
*/
Upvotes: 0
Reputation: 15185
If you are going to be doing a lot of gaps and island calculation and or date based queries without gaps then I cannot stress how much simpler a Calendar table will make your life.
Basically, all you need is a table similar to:
Calender
Day INT,
Month INT,
Year INT,
StartOfDay DATETIME,
EndOfDay DATETIME,
DayOfWeek INT,
WeekInyear INT,
IsWeekDay BIT
Pick a date range. I chose this century and last century as it fits my business models needs. Next, simply run a loop function once and populate your table, finally apply proper indexes and use as needed.
DECLARE @BeginDate DATETIME ='01/01/2015'
DECLARE @EndDate DATETIME ='01/01/2017'
DECLARE @MONTHS TABLE(Month INT,Year INT)
WHILE @BeginDate<=@EndDate BEGIN
INSERT @Months SELECT DATEPART(MONTH,@BeginDate),DATEPART(YEAR,@BeginDate)
SET @BeginDate=DATEADD(MONTH,1,@BeginDate)
END
SELECT
Calendar.Month,
Calendar.Year,
COUNT(visit.userID)
FROM
@Months Calendar
LEFT JOIN visit ON visit.year=Calendar.Year AND visit.Month=Calendar.Month
GROUP BY
Calendar.Month,
Calendar.Year
Upvotes: 0
Reputation: 39454
A couple of subqueries with a LEFT JOIN
and an ISNULL
should do it:
SELECT sub1.MonthYear,
ISNULL(sub2.Count, 0)
FROM
(SELECT TOP (12) DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()) - test, 0) AS MonthYear
FROM test t) sub1
LEFT JOIN
(SELECT DATEADD(MONTH, DATEDIFF(MONTH, 0, StartDate), 0) as MonthYear,
COUNT(*) AS Count
FROM visit
WHERE StartDate >= DATEADD(year,-1,GETDATE())
AND clientid = 142
GROUP BY DATEADD(MONTH, DATEDIFF(MONTH, 0, StartDate), 0)) sub2
ON sub1.MonthYear = sub2.MonthYear
Upvotes: 0
Reputation: 77
Traditionally this is solved with a calendar table (eg this msdn answer), but in your case a derived table and outer join should work:
select DATEADD(MONTH, DATEDIFF(MONTH, 0, StartDate), 0) as MonthYear,count(*) as Count
from (SELECT TOP (12) DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE())-test, 0) as MonthYear
from test) cal LEFT OUTER JOIN visit on (cal.MonthYear = visit.MonthYear)
where StartDate >= DATEADD(year,-1,GETDATE())
and clientid = 142
group by DATEADD(MONTH, DATEDIFF(MONTH, 0, StartDate), 0)
Upvotes: 3