how to perform division with multiple conditions in a group SSRS - sql

How can I calculate change Year\Month over Year\Month for the past 4 years when Year and Month in a group in SSRS.
I need like that:
Row 2016 vs 2015 % Change is a division of January 2016 Premium/ January 2015 Premium and so on
Something like CASE WHEN year=2016 and month = 1 then 2016 premium/2015 premium
I am trying to:
IIF(Fields!YearNum.Value=2016 and Fields!MonthNum.Value=1, Fields.Premium.Value/IIF(Fields!YearNum.Value=2015 and Fields!MonthNum.Value=1,Fields.Premium.Value,1),Nothing)
My query looks like that:
SELECT b.YearNum,
b.MonthNum,
b.MonthName,
SUM(Premium) as Premium,
ISNULL(sum(case when TransactionType IN ('Policy', 'Reinstatement') then 1 ELSE 0 END),0) as Bound,
FROM tblCalendar b
LEFT JOIN Test_Plaza_ProductionReport a ON b.MonthNum = MONTH(a.EffectiveDate) AND b.YearNum=YEAR(a.EffectiveDate)
WHERE YEAR(EffectiveDate) <> 2017
GROUP BY b.YearNum,
b.MonthNum,
b.MonthName

it seems you are using matrix so you don't need to specify the month
try expression like:
=(sum(IIF(Fields!YearNum.Value=2016, Fields.Premium.Value,0)) - sum(IIF(Fields!YearNum.Value=2015, Fields.Premium.Value,0)))
/sum(IIF(Fields!YearNum.Value=2016, Fields.Premium.Value,0))

Setup:
To mimic your setup, I manually created the following table (dbo.Unpivoted):
Pivot Query:
Next, I pivoted the data to yield the following result:
SELECT [Year] AS [Year], [January], [February]
FROM
(SELECT * FROM dbo.Unpivoted) src
PIVOT
(
Sum(src.Premium)
FOR src.[Month] IN ("January", "February")
) as PivotTable
ORDER BY [Year] DESC;
Self-Join Query:
Based on the above results (now called dbo.Premium), I used a self-join to calculate the year-over-year percentage increase:
Select Cast(upyear.[Year] as varchar) + ' vs ' + Cast(downyear.[Year] as varchar) + ' % Change' [Year],(upyear.January/downyear.January - 1) January, (upyear.February/downyear.February - 1) February
From dbo.Premium upyear
INNER JOIN
dbo.Premium downyear
ON upyear.[Year] = downyear.[Year] + 1;
Update: The combined query
CREATE TABLE #Combined([Year] VARCHAR(50), January FLOAT, February FLOAT)
INSERT INTO #Combined SELECT CAST([Year] AS varchar) AS [Year], [January], [February]
FROM
(SELECT * FROM dbo.Unpivoted) src
PIVOT
(
Sum(src.Premium)
FOR src.[Month] IN ("January", "February")
) as PivotTable;
INSERT INTO #Combined SELECT CAST(upyear.[Year] AS VARCHAR) + ' vs ' + CAST(downyear.[Year] as VARCHAR) + ' % Change' [Year],(upyear.January/downyear.January - 1) January, (upyear.February/downyear.February - 1) February
From
#Combined upyear
INNER JOIN
#Combined downyear
ON upyear.[Year] = downyear.[Year] + 1;
SELECT * FROM #Combined
ORDER BY
CASE
WHEN LEN([Year]) = 4 THEN 1
ELSE 0
END DESC,
[Year] DESC;
Begin
Drop Table #Combined
End

Related

Add count() to pivot table with totals

I have the following query: (simplified to one year)
WITH myTable
AS (SELECT cv.Company,
cv.Zip,
DATEPART(year, od.OrderDate) AS TheYear,
'' + DATEPART(year, od.OrderDate) AS TheYear2,
od.OrderNumber AS countOrders,
STUFF(
(
SELECT '| ' + FORMAT(DateVisited, 'MM-yyyy') + ' ' + MeetingType + ' '
FROM CustomerVisits cv1
WHERE(RIGHT(od.Email, LEN(od.Email) - CHARINDEX('#', od.email))) = cv1.EmailDomain
AND cv1.Zip = od.Zip
GROUP BY MeetingType,
FORMAT(DateVisited, 'MM-yyyy')
ORDER BY FORMAT(DateVisited, 'MM-yyyy') ASC FOR XML PATH('')
), 1, 1, '') AS Meetings,
od.FinalProductTotal AS total
FROM orders od
LEFT JOIN CustomerVisits cv ON od.Zip = cv.Zip
WHERE(RIGHT(od.Email, LEN(od.Email) - CHARINDEX('#', od.email))) = cv.EmailDomain
--AND od.OrderDate > #fromDate
AND approved = 1
AND cancelled = 0
AND od.OrderDate > '01-JAN-2010')
--and the PIVOT
SELECT Company,
Zip,
Meetings,
[02020] AS [Orders - 2020],
ISNULL(CAST([2020] AS INT), 0) AS [Total - 2020],
Total = CAST((SUM(ISNULL([2020], 0))) AS INT)
FROM myTable PIVOT(COUNT(countOrders) FOR TheYear2 IN([02020])) AS myPvt PIVOT(SUM(total) FOR TheYear IN([2020])) AS myPvt2
GROUP BY Company,
zip,
Meetings,
[2020],
[02020];
For it I'm trying to add the count of orders for each year, but I'm having problems understanding how should I proceed, I might be complicating it too much, but I'm not getting the proper count
I think that the myTable table is getting properly the "countOrders" but I'm not sure if I'm doing it properly on the pivot table to group by them and show the count by year
right now it kinda seems that is counting all the orders as I needed, however when I manually check on the DB seems that the data is off.
also, something I don't understand is why is it repeating the "company" so many times when I'm doing grouping by it?
example
I ended up case instead of pivot table. this solved the issue, with format:
CAST(SUM(CASE WHEN TheYear = 2010 THEN 1 ELSE 0 END)AS INT) AS [Orders - 2010] ,
CAST(SUM(CASE WHEN TheYear = 2010 THEN Total ELSE 0 END)AS INT) AS [Total - 2010],
in case it works for someone :)

SQL Server: finding change between selected month and previous month's sales

I have the following table for example. I would like to calculate the changes increase or decrease from previous month. This will show a percentage of change from previous month.
Location Month Sales
A Jan 1753
B Jan 32130
C Jan 71353
D Jan 74885
E Jan 50241
F Jan 66393
A Feb 80633
B Feb 67918
C Feb 73330
D Feb 33269
E Feb 78915
F Feb 98817
A Mar 80633
B Mar 67918
C Mar 73330
D Mar 33269
E Mar 78915
F Mar 98817
I wan to create a table like following. I searched stack overflow but was not able to get table.
Location Selected Current_Month Prvisous_Month Change
A Feb 80633 1753 4500%
B Feb 67918 32130 111%
C Feb 73330 71353 3%
D Feb 33269 74885 -56%
E Feb 78915 50241 57%
F Feb 98817 66393 49%
If you can't change the datatype of the "Month" column, for whatever reason, then this solution may work
DECLARE #Table TABLE ([Location] CHAR(1), [Month] NVARCHAR(3), Sales INT )
INSERT INTO #Table
([Location], [Month], Sales)
VALUES
('A',N'Jan',1753),
('B',N'Jan',32130),
('C',N'Jan',71353),
('D',N'Jan',74885),
('E',N'Jan',50241),
('F',N'Jan',66393),
('A',N'Feb',80633),
('B',N'Feb',67918),
('C',N'Feb',73330),
('D',N'Feb',33269),
('E',N'Feb',78915),
('F',N'Feb',98817),
('A',N'Mar',80633),
('B',N'Mar',67918),
('C',N'Mar',73330),
('D',N'Mar',33269),
('E',N'Mar',78915),
('F',N'Mar',98817)
DECLARE #Selection NVARCHAR(3) = N'Feb' -- Enter Selected Month here
;WITH cteX
AS(
SELECT
T.[Location]
, T.[Month]
, MonthNum = MONTH([T].[Month] + ' 1 1900') --Use some dummy date here
, T.Sales
FROM #Table T
)
SELECT
T.[Location]
, Selected = T.Month
, CurrentMonth = T.Sales
, PreviousMonth = T1.Sales
, Change = CAST((T.Sales - T1.Sales) / (T1.Sales * 1.0) * 100.0 AS DECIMAL)
FROM cteX T
INNER JOIN
cteX T1 ON T1.MonthNum = T.MonthNum - 1
AND T1.[Location] = T.[Location]
WHERE
T.[Month] = #Selection
Output
Something like this should be a good start. As Cool_Br33ze noted, you should rethink the date column of this table.
SELECT
*,
referenceTimePeriod / NULLIF(comparisonTimePeriod, 0) -- avoid DIV0 errors
FROM (
SELECT Location, Sales
FROM myTable
WHERE month = 'jan'
) AS referenceTimePeriod
FULL JOIN(
SELECT Location, Sales
FROM myTable
WHERE month = 'feb'
) AS comparisonTimePeriod ON referenceTimePeriod.Location = comparisonTimePeriod .Location
SELECT A.Location, 'Feb' AS Selected, A.Sales AS Current_Month
, B.Sales AS Prvisous_Month, (A.Sales - B.Sales)/ B.Sales AS Change
FROM YourTable A JOIN YourTable B
ON A.Month = B.Month + 1 -- You will have to represent Months by numbers
WHERE A.Month = 2 -- Selected month
Assuming that you change the Month attribute to date then you can use a LAG window function easily like this
SELECT location,
month,
sales,
lag(sales) over (order by month) previous,
(sales/lag(sales) over (order by month) - 1) * 100 as change
FROM your_table
WHERE month = 'feb'
The major issue now in your task is the correct ordering of Month which would be much more easier with date, or numbers.
EDIT: You can use the ordering solution of Cool_Br33ze for current data:
SELECT location,
month,
sales,
lag(sales) over (order by MONTH([T].[Month] + ' 1 1900')) previous, -- taken from Cool_Br33ze solution
(sales/lag(sales) over (order by MONTH([T].[Month] + ' 1 1900')) - 1) * 100 as change
FROM your_table
WHERE month = 'feb'
However, the best option is to change the data type of Month ...

Count number of days each employee take vacation in a month SQL Server

I have this table:
Vacationtbl:
ID Start End
-------------------------
01 04/10/17 04/12/17
01 04/27/17 05/02/17
02 04/13/17 04/15/17
02 04/17/17 04/20/17
03 06/14/17 06/22/17
Employeetbl:
ID Fname Lname
------------------
01 John AAA
02 Jeny BBB
03 Jeby CCC
I like to count the number of days each employee take vacation in April.
My query:
SELECT
SUM(DATEDIFF(DAY, Start, End) + 1) AS Days
FROM
Vacationtbl
GROUP BY
ID
01 returns 9 (not correct)
02 returns 7 (correct)
How do I fix the query so that it counts until the end of month and stops at end of month. For example, April has 30 days. On second row, Employee 01 should counts 4/27/17 until 4/30/17. And 05/02/17 is for May.
Thanks
The Tally/Calendar table is the way to go. However, you can use an ad-hoc tally table.
Example
Select Year = Year(D)
,Month = Month(D)
,ID
,Days = count(*)
From Vacationtbl A
Cross Apply (
Select Top (DateDiff(DAY,[Start],[End])+1) D=DateAdd(DAY,-1+Row_Number() Over (Order By (Select Null)),[Start])
From master..spt_values
) B
-- YOUR OPTIONAL WHERE STATEMENT HERE --
Group By ID,Year(D),Month(D)
Order By 1,2,3
Returns
Year Month ID Days
2017 4 01 7
2017 4 02 7
2017 5 01 2
EDIT - To Show All ID even if Zero Days
Select ID
,Year = Year(D)
,Month = Month(D)
,Days = sum(case when D between [Start] and [End] then 1 else 0 end)
From (
Select Top (DateDiff(DAY,'05/01/2017','05/31/2017')+1) D=DateAdd(DAY,-1+Row_Number() Over (Order By (Select Null)),'05/01/2017')
From master..spt_values
) D
Cross Join Vacationtbl B
Group By ID,Year(D),Month(D)
Order By 1,2,3
Returns
ID Year Month Days
1 2017 5 2
2 2017 5 0
dbFiddle if it Helps
EDIT - 2 Corrects for Overlaps (Gaps and Islands)
--Create Some Sample Data
----------------------------------------------------------------------
Declare #Vacationtbl Table ([ID] varchar(50),[Start] date,[End] date)
Insert Into #Vacationtbl Values
(01,'04/10/17','04/12/17')
,(01,'04/27/17','05/02/17')
,(02,'04/13/17','04/15/17')
,(02,'04/17/17','04/20/17')
,(02,'04/16/17','04/17/17') -- << Overlap
,(03,'05/16/17','05/17/17')
-- The Actual Query
----------------------------------------------------------------------
Select ID
,Year = Year(D)
,Month = Month(D)
,Days = sum(case when D between [Start] and [End] then 1 else 0 end)
From (Select Top (DateDiff(DAY,'04/01/2017','04/30/2017')+1) D=DateAdd(DAY,-1+Row_Number() Over (Order By (Select Null)),'04/01/2017') From master..spt_values ) D
Cross Join (
Select ID,[Start] = min(D),[End] = max(D)
From (
Select E.*,Grp = Dense_Rank() over (Order By D) - Row_Number() over (Partition By ID Order By D)
From (
Select Distinct A.ID,D
From #Vacationtbl A
Cross Apply (Select Top (DateDiff(DAY,A.[Start],A.[End])+1) D=DateAdd(DAY,-1+Row_Number() Over (Order By (Select Null)),A.[Start]) From master..spt_values ) B
) E
) G
Group By ID,Grp
) B
Group By ID,Year(D),Month(D)
Order By 1,2,3
Returns
ID Year Month Days
1 2017 4 7
2 2017 4 8
3 2017 4 0
Without a dates table, you could use
select Id
,sum(case when [end]>'20170430' and [start]<'20170401' then datediff(day,'20170401','20170430')+1
when [end]>'20170430' then datediff(day,[start],'20170430')+1
when [start]<'20170401' then datediff(day,'20170401',[end])+1
else datediff(day,[start],[end])+1
end) as VacationDays
from Vacationtbl
where [start] <= '20170430' and [end] >= '20170401'
group by Id
There are 3 conditions here
Start is before this month and the end is after this month. In this case you subtract the end and start dates of the month.
End is after month end and start is in the month, in this case subtract month end date from the start.
Start is before this month but the end is in the month. In this case subtract month start date and the end date.
Edit: Based on the OP's comments that the future dates have to be included,
/*This recursive cte generates the month start and end dates with in a given time frame
For Eg: all the month start and end dates for 2017
Change the start and end period as needed*/
with dates (month_start_date,month_end_date) as
(select cast('2017-01-01' as date),cast(eomonth('2017-01-01') as date)
union all
select dateadd(month,1,month_start_date),eomonth(dateadd(month,1,month_start_date)) from dates
where month_start_date < '2017-12-01'
)
--End recursive cte
--Query logic is the same as above
select v.Id
,year(d.month_start_date) as yr,month(d.month_start_date) as mth
,sum(case when v.[end]>d.month_end_date and v.[start]<d.month_start_date then datediff(day,d.month_start_date,d.month_end_date)+1
when v.[end]>d.month_end_date then datediff(day,v.[start],d.month_end_date)+1
when v.[start]<d.month_start_date then datediff(day,d.month_start_date,v.[end])+1
else datediff(day,v.[start],v.[end])+1
end) as VacationDays
from dates d
join Vacationtbl v on v.[start] <= d.month_end_date and v.[end] >= d.month_start_date
group by v.id,year(d.month_start_date),month(d.month_start_date)
Assuming you want only one month and you want to count all days, you can do this with arithmetic. A separate calendar table is not necessary. The advantage is performance.
I think this would be easier if SQL Server supported least() and greatest(), but case will do:
select id,
sum(1 + datediff(day, news, newe)) as vacation_days_april
from vactiontbl v cross apply
(values (case when [start] < '2017-04-01' then cast('2017-04-01' as date) else [start] end),
(case when [end] >= '2017-05-01' then cast('2017-04-30' as date) else [end] end)
) v(news, newe)
where news <= newe
group by id;
You can readily extend this to any month:
with m as (
select cast('2017-04-01' as date) as month_start,
cast('2017-04-30' as date) as month_end
)
select id,
sum(1 + datediff(day, news, newe)) as vacation_days_aprile
from m cross join
vactiontbl v cross apply
(values (case when [start] < m.month_start then m.month_start else [start] end),
(case when [end] >= m.month_end then m.month_end else [end] end)
) v(news, newe)
where news <= newe
group by id;
You can even use a similar idea to extend to multiple months, with a different row for each user and each month.
You can use a Calendar or dates table for this sort of thing.
For only 152kb in memory, you can have 30 years of dates in a table with this:
/* dates table */
declare #fromdate date = '20000101';
declare #years int = 30;
/* 30 years, 19 used data pages ~152kb in memory, ~264kb on disk */
;with n as (select n from (values(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) t(n))
select top (datediff(day, #fromdate,dateadd(year,#years,#fromdate)))
[Date]=convert(date,dateadd(day,row_number() over(order by (select 1))-1,#fromdate))
into dbo.Dates
from n as deka cross join n as hecto cross join n as kilo
cross join n as tenK cross join n as hundredK
order by [Date];
create unique clustered index ix_dbo_Dates_date
on dbo.Dates([Date]);
Without taking the actual step of creating a table, you can use it inside a common table expression with just this:
declare #fromdate date = '20170401';
declare #thrudate date = '20170430';
;with n as (select n from (values(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) t(n))
, dates as (
select top (datediff(day, #fromdate, #thrudate)+1)
[Date]=convert(date,dateadd(day,row_number() over(order by (select 1))-1,#fromdate))
from n as deka cross join n as hecto cross join n as kilo
cross join n as tenK cross join n as hundredK
order by [Date]
)
select [Date]
from dates;
Use either like so:
select
v.Id
, count(*) as VacationDays
from Vacationtbl v
inner join Dates d
on d.Date >= v.[Start]
and d.Date <= v.[End]
where d.Date >= '20170401'
and d.Date <= '20170430'
group by v.Id
rextester demo (table): http://rextester.com/PLW73242
rextester demo (cte): http://rextester.com/BCY62752
returns:
+----+--------------+
| Id | VacationDays |
+----+--------------+
| 01 | 7 |
| 02 | 7 |
+----+--------------+
Number and Calendar table reference:
Generate a set or sequence without loops - 2 - Aaron Bertrand
The "Numbers" or "Tally" Table: What it is and how it replaces a loop - Jeff Moden
Creating a Date Table/Dimension in sql Server 2008 - David Stein
Calendar Tables - Why You Need One - David Stein
Creating a date dimension or calendar table in sql Server - Aaron Bertrand
Try this,
declare #Vacationtbl table(ID int,Startdate date,Enddate date)
insert into #Vacationtbl VALUES
(1 ,'04/10/17','04/12/17')
,(1 ,'04/27/17','05/02/17')
,(2 ,'04/13/17','04/15/17')
,(2 ,'04/17/17','04/20/17')
-- somehow convert your input into first day of month
Declare #firstDayofGivenMonth date='2017-04-01'
Declare #LasttDayofGivenMonth date=dateadd(day,-1,dateadd(month,datediff(month,0,#firstDayofGivenMonth)+1,0))
;with CTE as
(
select *
,case when Startdate<#firstDayofGivenMonth then #firstDayofGivenMonth else Startdate end NewStDT
,case when Enddate>#LasttDayofGivenMonth then #LasttDayofGivenMonth else Enddate end NewEDT
from #Vacationtbl
)
SELECT
SUM(DATEDIFF(DAY, NewStDT, NewEDT) + 1) AS Days
FROM
CTE
GROUP BY
ID

Group a query by every month

I have the following query :
select
(select Sum(Stores) from XYZ where Year = '2013' and Month = '8' )
-
(select Sum(SalesStores) from ABC where Year = '2013' and Month = '8') as difference
Here in the above query Year and Month are also columns of a table.
I would like to know if there is a way to run the same query so that , it is run against every month of the year ?
If there are months without data/rows within XYZ or ABC tables then I would use FULL OUTER JOIN:
SELECT ISNULL(x.[Month], y.[Month]) AS [Month],
ISNULL(x.Sum_Stores, 0) - ISNULL(y.Sum_SalesStores, 0) AS Difference
FROM
(
SELECT [Month], Sum(Stores) AS Sum_Stores
FROM XYZ
WHERE [Year] = '2013'
GROUP BY [Month]
) AS x
FULL OUTER JOIN
(
SELECT [Month], Sum(SalesStores) AS Sum_SalesStores
FROM ABC
WHERE [Year] = '2013'
GROUP BY [Month]
) AS y ON x.[Month] = y.[Month]
;WITH Months(Month) AS
(
SELECT 1
UNION ALL
SELECT Month + 1
FROM Months
where Month < 12
)
SELECT '2013' [Year], m.Month, COALESCE(SUM(Stores), 0) - COALESCE(SUM(SalesStores), 0) [Difference]
FROM months m
LEFT JOIN XYZ x ON m.Month = x.Month
LEFT JOIN ABC a ON a.Month = m.Month
GROUP BY m.Month
You could use GROUP BY in your inner trades, and then run a join, like this:
SELECT left.Month, (left.sum - COALESCE(right.sum, 0)) as difference
FROM (
SELECT Month, SUM(Stores) as sum
FROM XYZ WHERE Year = '2013'
GROUP BY Month
) left
LEFT OUTER JOIN (
SELECT Month, SUM(Stores) as sum
FROM ABC WHERE Year = '2013'
GROUP BY Month
) right ON left.Month = right.Months
Note the use of COALESCE. It lets you preserve the value of the first SUM in case when there are no records for the month in the ABC table.
In the following example uses the UNION ALL operator with CTE
;WITH cte AS
(SELECT SUM(Stores) AS Stores, [Month]
FROM dbo.XYZ
WHERE [Year] = '2013'
GROUP BY [Month]
UNION ALL
SELECT -1.00 * SUM(SalesStores), [Month]
FROM dbo.ABC
WHERE [Year] = '2013'
GROUP BY [Month]
)
SELECT [Month], SUM(Stores) AS Difference
FROM cte
GROUP BY [Month]
Demo on SQLFiddle
;WITH Months(Month) AS
(
SELECT 1
UNION ALL
SELECT Month + 1
FROM Months
where Month < 12
)
SELECT Months. Month ,
(select isnull(Sum(Stores),0) from XYZ where Year = '2013' and Month = Months.Month) - (select isnull(Sum(SalesStores),0) from ABC where Year = '2013' and Month =Months.Month) as difference
FROM Months

Normalization of Year bringing nulls back

I have the following query:
SELECT DISTINCT
YEAR(DateRegistered) as Years,
Months.[MonthName],
COUNT(UserID)as totalReg
FROM
Months WITH(NOLOCK)
LEFT OUTER JOIN
UserProfile WITH(NOLOCK)
ON
Months.MonthID = MONTH(DateRegistered)
AND
DateRegistered > DATEADD(MONTH, -12,GETDATE())
GROUP BY YEAR(DateRegistered), Months.[MonthName]
ORDER BY Months.[MonthName]
As you can tell this will always bring back 12 months worth of data. As such it is working, although there is a bug with this method.
It creates Null values in months where there is no data, now the record should exist(whole point of the query) but Year field is bringing Nulls which is something I dont want.
Now I understand the problem is because there is no data, how is it supposed to know what year?
So my question is - is there any way to sort this out and replace the nulls? I suspect I will have to completely change my methodology.
**YEAR** **MONTH** **TOTAL**
2013 April 1
2013 August 1
NULL December 0
2013 February 8
2013 January 1
2013 July 1
NULL June 0
2013 March 4
NULL May 0
NULL November 0
NULL October 0
2012 September 3
If you want 12 months of data, then construct a list of numbers from 1 to 12 and use these as offsets with getdate():
with nums as (
select 12 as level union all
select level - 1
from nums
where level > 1
)
select YEAR(thedate) as Years,
Months.[MonthName],
COUNT(UserID) as totalReg
FROM (select DATEADD(MONTH, - nums.level, GETDATE()) as thedate
from nums
) mon12 left outer join
Months WITH (NOLOCK)
on month(mon12.thedate) = months.monthid left outer join
UserProfile WITH (NOLOCK)
ON Months.MonthID = MONTH(DateRegistered) and
DateRegistered > DATEADD(MONTH, -12, GETDATE())
GROUP BY YEAR(thedate), Months.[MonthName]
ORDER BY Months.[MonthName];
I find something strange about the query though. You are defining the span from the current date. However, you seem to be splitting the months themselves on calendar boundaries. I also find the table months to be awkward. Why aren't you just using the datename() and month() functions?
Try this out:
;With dates as (
Select DateName(Month, getdate()) as [Month],
DatePart(Year, getdate()) as [Year],
1 as Iteration
Union All
Select DateName(Month,DATEADD(MONTH, -Iteration, getdate())),
DatePart(Year,DATEADD(MONTH, -Iteration, getdate())),
Iteration + 1
from dates
where Iteration < 12
)
SELECT DISTINCT
d.Year,
d.Month as [MonthName],
COUNT(up.UserID)as totalReg
FROM dates d
LEFT OUTER JOIN UserProfile up ON d.Month = DateName(DateRegistered)
And d.Year = DatePart(Year, DateRegistered)
GROUP BY d.Year, d.Month
ORDER BY d.Year, d.Month
Here's my attempt at a solution:
declare #UserProfile table
(
id bigint not null identity(1,1) primary key clustered
, name nvarchar(32) not null
, dateRegistered datetime not null default(getutcdate())
)
insert #UserProfile
select 'person 1', '2011-01-23'
union select 'person 2', '2013-01-01'
union select 'person 3', '2013-05-27'
declare #yearMin int, #yearMax int
select #yearMin = year(MIN(dateRegistered))
, #yearMax= year(MAX(dateRegistered))
from #UserProfile
;with monthCte as
(
select 1 monthNo, DATENAME(month, '1900-01-01') Name
union all
select monthNo + 1, DATENAME(month, dateadd(month,monthNo,'1900-01-01'))
from monthCte
where monthNo < 12
)
, yearCte as
(
select #yearMin yearNo
union all
select yearNo + 1
from yearCte
where yearNo < #yearMax
)
select y.yearNo, m.Name, COUNT(up.id) UsersRegisteredThisPeriod
from yearCte y
cross join monthCte m
left outer join #UserProfile up
on year(up.dateRegistered) = y.yearNo
and month(up.dateRegistered) = m.monthNo
group by y.yearNo, m.monthNo, m.Name
order by y.yearNo, m.monthNo
SQL Fiddle Version: http://sqlfiddle.com/#!6/d41d8/6640
You have to calculate the counts in a Derived Table (or a CTE) first and then join
untested:
SELECT
COALESCE(dt.Years, YEAR(DATEADD(MONTH, -Months.MonthID, GETDATE()))),
Months.[MonthName],
COALESCE(dt.totalReg, 0)
FROM
Months WITH(NOLOCK)
LEFT OUTER JOIN
(
SELECT
YEAR(DateRegistered) AS Years,
MONTH(DateRegistered) AS Mon,
COUNT(UserID)AS totalReg
FROM UserProfile WITH(NOLOCK)
WHERE DateRegistered > DATEADD(MONTH, -12,GETDATE())
GROUP BY
YEAR(DateRegistered),
MONTH(DateRegistered)
) AS dt
ON Months.MonthID = dt.mon
ORDER BY 1, Months.MonthID
I changed the order to Months.MonthID instead of MonthName and i added year because you might have august 2012 and 2013 in your result.