sum count day each year (SQL) - sql

my example:
Code FromDate ToDate
-- -------- -------
101 15/12/2012 15/01/2013
101 30/11/2013 20/01/2014
I want to count diff between two date,, how many days in this year
, How can i do this with SQL?
to be result like this :
Code No.day 2012 2013 2014
-- ------ ---- ---- ----
101 82 17 46 19

Try something like:
select Code,sum([2012])+sum([2013])+sum([2014]) as 'No.days',sum([2012]),sum([2013]),sum([2014]) from(
select Code,
case when datepart(yy,fromdate)=datepart(yy,todate) and datepart(yy,fromdate)=2012 then datediff(dd,fromdate, todate)
when datepart(yy,fromdate)=2012 and datepart(yy,todate)>2012 then datediff(dd,fromdate,dateadd(yy,1,dateadd(dd,-datepart(dy,DATEADD(yy,1, fromdate)),fromdate)))
when datepart(yy,fromdate)<2012 and datepart(yy,todate)=2012 then DATEPART(dy,todate)
end as '2012',
case when datepart(yy,fromdate)=datepart(yy,todate) and datepart(yy,fromdate)=2013 then datediff(dd,fromdate, todate)
when datepart(yy,fromdate)=2013 and datepart(yy,todate)>2013 then datediff(dd,fromdate,dateadd(yy,1,dateadd(dd,-datepart(dy,DATEADD(yy,1, fromdate)),fromdate)))
when datepart(yy,fromdate)<2013 and datepart(yy,todate)=2013 then DATEPART(dy,todate)
end as '2013',
case when datepart(yy,fromdate)=datepart(yy,todate) and datepart(yy,fromdate)=2014 then datediff(dd,fromdate, todate)
when datepart(yy,fromdate)=2014 and datepart(yy,todate)>2014 then datediff(dd,fromdate,dateadd(yy,1,dateadd(dd,-datepart(dy,DATEADD(yy,1, fromdate)),fromdate)))
when datepart(yy,fromdate)<2014 and datepart(yy,todate)=2014 then DATEPART(dy,todate)
end as '2014'
from myTable
) as daystable
group by Code
If you need more years you will need to add some more fields in the subquery.

You will need to combine a few different techniques, to get the result you are after.
It helps if you have a calendar table. These are incredibly useful things, well worth researching if you are not familar. My query assumes you don't. Here I've used a recursive CTE to create one on the fly.
Sample Data
TIP: Providing sample data in format we can share, improves the odds of your question receiving an answer.
-- Table variables are a good way to share sample data.
DECLARE #Sample TABLE
(
Code INT,
FromDate DATE,
ToDate DATE
)
;
INSERT INTO #Sample
(
Code,
FromDate,
ToDate
)
VALUES
(101, '2012-12-15', '2013-01-15'),
(101, '2013-11-30', '2014-01-20')
;
The Query
This query joins your source data to the calendar table. One record is returned for each day between the from and to dates. Using the year column in the calendar table, we group the results. Counting the records returns the total days passed. To calculate the year subtotals, we use conditional aggregation. This technique uses a case expression to create new columns that are conditionally populated with a 1 or a 0, based on the year.
/* Returns date counts, split by
* year.
*/
WITH CalendarTable AS
(
/* This CTE returns 1 record for each day
* between Jan 1st 2012 and Dec 31st 2014.
*/
SELECT
CAST('2012-01-01' AS DATE) AS [Date],
2012 AS [Year]
UNION ALL
SELECT
DATEADD(DAY, 1, [Date]) AS [Date],
YEAR(DATEADD(DAY, 1, [Date])) AS [Year]
FROM
CalendarTable
WHERE
[Date] < '2014-12-31'
)
SELECT
s.Code,
COUNT(*) AS [No.Day],
SUM(CASE WHEN ct.[Year] = 2012 THEN 1 ELSE 0 END) AS [2012],
SUM(CASE WHEN ct.[Year] = 2013 THEN 1 ELSE 0 END) AS [2013],
SUM(CASE WHEN ct.[Year] = 2014 THEN 1 ELSE 0 END) AS [2014]
FROM
#Sample AS s
INNER JOIN CalendarTable AS ct ON ct.[Date] >= s.FromDate
AND ct.[Date] < s.ToDate
GROUP BY
s.Code
OPTION
(MAXRECURSION 1096)
;
Returns
Code No.day 2012 2013 2014
-- ------ ---- ---- ----
101 82 17 46 19
If you already have a calendar table you can simplify this query, by removing the CTE and updating the join.

Use this:
SELECT Code,SUM(No.day) AS No.day, SUM(2012) AS 2012, SUM(2013) AS 2013, SUM(2014) AS 2014
FROM TABLENAME GROUP BY Code
According to your current(Edited) question, you should use
SELECT DATEDIFF(day,'2012-06-05','2012-08-05') AS Year2012 From TABLENAME .
For example:
SELECT DATEDIFF(day, '2014/01/01', '2014/04/28');
Result: 117
SELECT DATEDIFF(hour, '2014/04/28 08:00', '2014/04/28 10:45');
Result: 2
SELECT DATEDIFF(minute, '2014/04/28 08:00', '2014/04/28 10:45');
Result: 165
You can manipulate it based on your requirement.

SELECT code,fromdate,todate,datediff(dd,fromdate,todate),
datediff(dd,case when year(fromdate) < 2012 then '1/1/2012' when year(fromdate) = 2012 then fromdate else '12/31/2012' end,case when year(todate) = 2012 then todate else '12/31/2012'end)'2012',
datediff(dd,case when year(fromdate) < 2013 then '1/1/2013' when year(fromdate) = 2013 then fromdate else '12/31/2013' end,case when year(todate) = 2013 then todate else '12/31/2013'end) '2013',
datediff(dd,case when year(fromdate) < 2014 then '1/1/2014' when year(fromdate) = 2014 then fromdate else '12/31/2014' end,case when year(todate) < 2014 then '1/1/2014' when year(todate) = 2014 then todate else '12/31/2014'end) '2014' FROM (SELECT '101' AS code, datefromparts(2012,12,15) AS fromdate, datefromparts(2013,1,15) AS todate
UNION
SELECT '101' AS code, datefromparts(2013,11,30) AS fromdate, datefromparts(2014,1,20) AS todate
Union
SELECT '101' AS code, datefromparts(2014,11,30) AS fromdate, datefromparts(2016,1,20) AS todate) AS s
Hope This helps

Related

Dynamically SELECT a column based on value of row at first of month

The following query:
SELECT Confirmed, Interim, Declared, Date
FROM Interest_Hist
WHERE Date BETWEEN '2019-08-01' AND '2019-12-04'
ORDER BY Date ASC
Returns the following sample data:
Confirmed Interim Declared Date
Y 0.314 0.0788 2019-08-01
0.317 0 2019-08-02
...
0.245 0 2019-08-31
0.222 0.219 2019-09-01
0.198 0 2019-09-02
...
Y 0.50 0.454 2019-12-01
0.51 0 2019-12-02
0.52 0 2019-12-03
0.53 0 2019-12-04
Where on the first of the month, Confirmed = Y, I need to return the Declared column for that month.
Note, Confirmed = Y will only exist on the first of the month. That column is blank in all other cases
Otherwise, I need to return each Interim column for the month.
Thus far, I have been able to return the SUM of either column, but not the individual values.
SELECT
CASE WHEN SUM(CASE WHEN IRc_Confirmed = 'Y' THEN 1 ELSE 0 END) = 0
THEN Interim
ELSE Declared
END AS Rate
FROM Fund_Interest
WHERE Date BETWEEN '2019-08-01' AND '2019-12-04'
GROUP BY
DATEADD(month, DATEDIFF(month, 0, Date), 0), Interim, Declared
ORDER BY
DATEADD(month, DATEDIFF(month, 0, Date), 0)
The expected output given the data at the top is as follows
0.0788
0
...
0
0.222
0.198
...
0.454
0
0
0
Find all the year months where the first day is Y:
SELECT year([date]) as yr, month([date]) as mo
FROM fund_interest
WHERE DAY([date]) = 1 and confirmed = 'Y'
This gives you a list of years and months where there is a Y on the first eg Aug 2019
Now make it a cte and left join it back to your data on year and month, and where the join succeeds return declared else interim:
WITH x AS(
SELECT year([date]) as yr, month([date]) as mo
FROM fund_interest
WHERE DAY([date]) = 1 and confirmed = 'Y'
)
SELECT
CASE WHEN x.yr IS NOT NULL THEN f.declared ELSE f.interim END AS something
FROM
fund_interest f
LEFT OUTER JOIN x ON x.yr = year(f.[date]) AND x.mo = month(f.[date])
All of the rows of different days from Aug 2019 and Dec 2019 will succeed in the join. They will have a NOT NULL yr value and hence the declared will show. For all Sep 2019 rows there is no match in the join (Sep 2019 is not returned by the query in the CTE), yr is null, interim shows instead
For a better idea of what is going on do a SELECT *
If you want to use just a single column the EOMONTH function could be used to return a consistent date every month. Replace MONTH with EOMONTH. Remove calls to YEAR from the query
Do not use reserved words like DATE as column names, by the way
You can use a CTE to group by month and year and then join to your original table (Interest_Hist) on the month and year parts of your date field. You can then select the Interim or Declared value using a simple case statement
;WITH CTE AS
(
SELECT DATEPART(month, DateFld) Mnth, DATEPART(year, DateFld) Yr,
MAX(Confirmed) ConfirmedVal
FROM Interest_Hist
GROUP BY DATEPART(month, DateFld), DATEPART(year, DateFld)
)
SELECT
CASE WHEN c.ConfirmedVal= 'Y' THEN interest.Declared ELSE interest.Interim END
FROM Interest_Hist interest
INNER JOIN CTE c ON
DATEPART(month, interest.DateFld) = c.Mnth AND
DATEPART(year, interest.DateFld) = c.Yr
You can see the query in action here
This took me way longer than it probably should have.
SELECT IIF( MAX(Confirmed) OVER(PARTITION BY CONVERT(VARCHAR(6), Date, 112)) = 'Y', Declared, Interim) Interest_Rate
FROM Interest_Hist
WHERE DateBETWEEN '01-AUG-2019' AND '04-DEC-2019'
ORDER BY Date

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

Dates by quarter

I would like to count number of birthdays by quarters in SQL Server
i.e. Between Jan 1 - March 31, April 1 - June 30, July 1 - Sep 30 & Oct 1 - Dec 31.
Please help me with the Date function in Sql Server to do this.
I pass BDate as DATETIME . I want to use this BDate to populate 4 fields of type int with counts for number of birthdays in each quarter.
Thanks.
As long as your "BDate" column is datetime, you can achieve this quite easily using this query:
SELECT DATEPART(QUARTER,BDATE), COUNT(*)
FROM TABLE1
GROUP BY DATEPART(QUARTER,BDATE)
ORDER BY DATEPART(QUARTER,BDATE) ASC
Here's a working fiddle using some random data: http://sqlfiddle.com/#!6/7734b/1
Data set up:
create table test (
d1 varchar(10),
d2 datetime
);
insert into test (d1,d2) values ('2015-01-28','2015-01-28');
insert into test (d1,d2) values ('2015-02-13','2015-02-13');
insert into test (d1,d2) values ('2015-07-19','2015-07-19');
insert into test (d1,d2) values ('2015-11-04','2015-11-04');
If you just want to get counts for each of the quarters present in your data:
select DATEPART(QUARTER, d2), count(*)
from test
group by DATEPART(QUARTER, d2);
You can use d1 or d2 (SQL Server will handle the varchar or datetime properly).
If you want to include all four quarters, even if they're not present in your data:
select qs.q, count(t.d2) as c
from (
SELECT 1 as q
UNION ALL
SELECT 2 as q
UNION ALL
SELECT 3 as q
UNION ALL
SELECT 4 as q) qs
left join test t
on qs.q = DATEPART(QUARTER, t.d2)
group by qs.q;
Again, you can use either d1 or d2, it doesn't matter. The "qs" query just gets the numbers 1 to 4, then that is outer joined to the table with the birth dates.
Please try this:
--replace '19000102' with your int column
select SUM(CASE WHEN DATEPART(MONTH,CONVERT (datetime,convert(char(8),19000102)))
IN (1,2,3)
THEN 1
ELSE 0
END) as 'Q1',
SUM(CASE WHEN DATEPART(MONTH,CONVERT (datetime,convert(char(8),19000102)))
IN (4,5,6)
THEN 1
ELSE 0
END) as 'Q2',
SUM(CASE WHEN DATEPART(MONTH,CONVERT (datetime,convert(char(8),19000102)))
IN (7,8,9)
THEN 1
ELSE 0
END) as 'Q3',
SUM(CASE WHEN DATEPART(MONTH,CONVERT (datetime,convert(char(8),19000102)))
IN (10,11,12)
THEN 1
ELSE 0
END) as 'Q4'

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.

How use the operator IN with a subquery that returns two columns

Hello masters I need your help.
Having the table:
DataCollection
==================
PK Code
smallint RestaurantCode
smallint Year
tinyint Month
money Amount
money AccumulativeMonthsAmount
...
I need the AccumulateAmount for the LastMonth on every Restaurant.
First, I get the last Month per Restaurant for the 'current year'(for this case):
SELECT RestaurantCode, MAX(Month) as Month FROM DataCollection
WHERE (Year >= 2012 AND YEAR <= 2012) GROUP BY RestaurantCode
Now I want to use that as subquery, to get the Last - AccumulativeMonthsAmount :
SELECT AccumulativeMonthsAmount FROM DataCollection
WHERE (RestaurantCode, Month)
IN (SELECT RestaurantCode, MAX(Month) as Month FROM DataCollection
WHERE (Year >= 2012 AND YEAR <= 2012) GROUP BY RestaurantCode)
But the operator IN, don't work, How I should do it?
Sample Data sorted by Year and Month:
RestCode Amount Accumulative Year Month
123 343.3 345453.65 2012 12
123 124.7 345329.00 2012 11
...
122 312.2 764545.00 2012 12
122 123.4 764233.00 2012 11
...
999 500.98 2500.98 2012 6
999 100.59 2000.00 2012 5
...
I wanna to get the Accumulative for the last month of every restaurant:
RestCode Accumulative Month
123 345453.65 12
122 764545.00 12
99 2500.98 6
...
SELECT dc.AccumulativeMonthsAmount
FROM dbo.DataCollection AS dc
INNER JOIN
(
SELECT RestaurantCode, MAX(Month)
FROM dbo.PAL_Entries_Relatives
WHERE [Year] = 2012
GROUP BY RestaurantCode
) AS r(rc, m)
ON dc.RestaurantCode = r.rc
AND dc.[Month] = r.m;
With the changed requirements:
;WITH x AS
(
SELECT RestCode, Accumulative, [Month],
rn = ROW_NUMBER() OVER (PARTITION BY RestCode ORDER BY [Month] DESC)
FROM dbo.DataCollection -- or is it dbo.PAL_Entries_Relatives?
)
SELECT RestCode, Accumulative, [Month]
FROM x
WHERE rn = 1
ORDER BY [Month] DESC, RestCode DESC;
That syntax is not allowed in SQL Server. You can do something similar with EXISTS:
SELECT AccumulativeMonthsAmount
FROM DataCollection dc
WHERE exists (select 1
from PAL_Entries_Relatives er
where (Year >= 2012 AND YEAR <= 2012)
group by RestaurantCode
having er.RestaurantCode = dc.RestaurantCode and
max(er.month) = dc.Month
)
SELECT AccumulativeMonthsAmount
FROM DataCollection
INNER JOIN PAL_Entries_Relatives
ON DataCollection.RestaurantCode = PAL_Entries_Relatives.RestaurantCode
WHERE (Year >= 2012 AND YEAR <= 2012)
GROUP BY DataCollection.RestaurantCode
HAVING AccumulativeMonthsAmount.Month = MAX(PAL_Entries_Relatives.Month)