SQL Server - Use while loop to shorten several Union Joins - sql-server

I have the following query that I simplified. Simply it displays a list of counts of records in the last 4 weeks.
SELECT COUNT(*) AS [Counts], Week=1
FROM TableA
WHERE Date >= DATEADD(week,-1,GETDATE())
AND Date <= GETDATE()
UNION
SELECT COUNT(*), 2
FROM TableA
WHERE Date >= DATEADD(week,-2,GETDATE())
AND Date <= DATEADD(week,-1,GETDATE())
UNION
SELECT COUNT(*), 3
FROM TableA
WHERE Date >= DATEADD(week,-3,GETDATE())
AND Date <= DATEADD(week,-2,GETDATE())
UNION
SELECT COUNT(*), 4
FROM TableA
WHERE Date >= DATEADD(week,-4,GETDATE())
AND Date <= DATEADD(week,-3,GETDATE())
Returns:
----------------
| Count | Week |
----------------
| 20 | 1 |
----------------
| 10 | 2 |
----------------
| 30 | 3 |
----------------
| 25 | 4 |
----------------
Suppose I want to modify the query so it returns the last 10 or 20 weeks.
How can I shorten the query so it loops through weeks?
e.g.
declare #w int;
set #w = 10;
while #w <> 0
begin
...;
--how can I do union joins?
set #w = #w - 1;
end

You could avoid UNION and specifying each week by hand by using GROUP BY:
SELECT datepart(week, Date) AS WeekNum, COUNT(*) AS counts
FROM TableA
WHERE Date >= DATEADD(week,-20,GETDATE()) -- num of weeks
GROUP BY datepart(week, Date); -- week of the year
If you need nums from 1 to n then:
WITH cte AS (
SELECT datepart(year, Date) AS [year],
datepart(week, Date) AS WeekNum,
COUNT(*) AS counts
FROM TableA
WHERE Date >= DATEADD(week,-20,GETDATE()) -- num of weeks
GROUP BY datepart(year, Date), datepart(week, Date)
)
SELECT ROW_NUMBER() OVER(ORDER BY [year] DESC, WeekNum DESC) AS WeekNum, counts
FROM cte;
EDIT:
"yes, like if today is wednesday, 20 week will give you a week starting in wednesday"
It could be handled by:
WHERE Date >= DATEADD(week,-20,GETDATE())
=>
WHERE Date >= DATEADD(week,-20,
CAST(DATEADD(DAY, 1-DATEPART(WEEKDAY, GETDATE()), GETDATE()) AS DATE))

Related

SQL Server - Display the number of active sessions for each minute in a time frame

I have the below table:
SessionID | UserName | Started | Ended
----------------------------------------------------------------
100 Test1 2015-07-26 00:03:05 2015-07-26 00:08:12
As the title says, I need to extract between a given #FromDate and a #ToDate parameters, for each minute, how many active sessions were. What I have tried so far does not select the non-active session (when no customers were online in that minute) and I cannot figure it out how to do this.
My SQL Statement
CREATE PROCEDURE [dbo].[ActiveSessionsByMinute] #FromDate datetime, #ToDate datetime
AS
BEGIN
SET NOCOUNT ON
SELECT DATEADD(MINUTE, DATEPART(MINUTE, Started), CAST(CONVERT(varchar(20), Started, 112) AS datetime)) AS DateMinute,
COUNT(SessionID) AS ActiveSessions
FROM ApplicationSessionHistory
GROUP BY DATEADD(MINUTE, DATEPART(MINUTE, Started), CAST(CONVERT(varchar(20), Started, 112) AS datetime))
END
GO
Output
DateMinute | ActiveSessions
-----------------------------------------
2015-07-26 00:03:00.000 | 1
If I execute the below statement, I should get the desired output (below):
EXEC dbo.ActiveSessionsByMinute
#FromDate = '2015-07-26 00:00',
#ToDate = '2015-07-26 00:10'
Desired Output
DateMinute | ActiveSessions
-----------------------------------------
2015-07-26 00:00:00.000 | 0
2015-07-26 00:01:00.000 | 0
2015-07-26 00:02:00.000 | 0
2015-07-26 00:03:00.000 | 1
2015-07-26 00:04:00.000 | 1
2015-07-26 00:05:00.000 | 1
2015-07-26 00:06:00.000 | 1
2015-07-26 00:07:00.000 | 1
2015-07-26 00:08:00.000 | 1
2015-07-26 00:09:00.000 | 0
2015-07-26 00:00:00.000 | 0
Does anyone can give me a tip? Thanks
I would do this with a CTE tally table. Notice I added an extra Session in the sample data.
HERE IS A DEMO
--Sample data
declare #table table (SessionID int, UserName varchar(16), Started datetime, Ended datetime)
insert into #table
values
(100,'Test1','2015-07-26 00:03:05','2015-07-26 00:08:12')
,(101,'Test1','2015-07-26 00:04:05','2015-07-26 00:05:12')
--used as a beginning anchor for the tally table
declare #startDate datetime = (select min(cast(Started as date)) from #table)
--take the original data, and truncate the seconds
;with NewTable as(
select
SessionID
,UserName
,Started = CAST(DateAdd(minute, DateDiff(minute, 0, Started), 0) AS smalldatetime)
,Ended = CAST(DateAdd(minute, DateDiff(minute, 0, Ended), 0) AS smalldatetime)
from #table
),
--tally table to get 10K minutes.
--This can be expanded for larger date ranges, and is faster than recursive CTE
E1(N) AS (select 1 from (values (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))dt(n)),
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS
(
SELECT TallyDate = dateadd(minute,ROW_NUMBER() OVER (ORDER BY (SELECT NULL)),#startDate) FROM E4
)
--use cross apply and and a case statement to find if it falls in the range
select
DateMinute = N
,SessionID
,Started
,Ended
,IsActive = case when (Started <=N and Ended >= N) then 1 else 0 end
from NewTable t
cross apply cteTally
where N <= (select max(Ended) from #table)
order by SessionID, N
For the sum part, you can simply aggregate. Replace the last SELECT with this one
--based on the above output, just do the SUM
select
DateMinute = N
,ActiveSessions = sum(case when (Started <=N and Ended >= N) then 1 else 0 end)
from NewTable t
cross apply cteTally
where N <= (select max(dateadd(minute,1,Ended)) from #table)
group by N
order by N
You'll want to SELECT from a tally table with all the minutes and LEFT JOIN to your ApplicationSessionHistory table:
CREATE PROCEDURE [dbo].[ActiveSessionsByMinute]
#FromDate DATETIME
, #ToDate DATETIME
AS
BEGIN
SET NOCOUNT ON;
SELECT allminutes.alltimes AS DateMinute
, COUNT(SessionID) AS ActiveSessions
FROM
(
SELECT DATEADD(MINUTE, myrows.rn, #FromDate) AS alltimes
FROM
(
SELECT ROW_NUMBER() OVER (ORDER BY s.id) - 1 rn
FROM master.sys.syscolumns AS s
) myrows
) allminutes
LEFT OUTER JOIN ApplicationSessionHistory ON allminutes.alltimes BETWEEN ApplicationSessionHistory.Started AND ApplicationSessionHistory.Ended
WHERE allminutes.alltimes <= #ToDate
GROUP BY DATEADD(MINUTE, DATEPART(MINUTE, Started), CAST(CONVERT(VARCHAR(20), Started, 112) AS DATETIME));
END;

Select N rows before and N rows after the record

I have a table where some values are stored for months and years.
Example:
Month | Year | Value
1 | 2013 | 1.86
2 | 2013 | 2.25
3 | 2013 | 2.31
...
3 | 2016 | 1.55
4 | 2016 | 1.78
Month and Year combination is a complex primary key. It is guaranteed that all values for all past years exist in the table.
User can select specific month and specific year. Let's say user selected 2014 as year and 6 as month, I need to show 15 rows before and 15 rows after the selected combination.
But if there are not enough rows (less than 15) after the selected combination than I need to get more rows before.
Basically all i need is to return 31 rows (always 31 unless there are not enough rows in the entire table) of data where the selected combination will be as close as possible to the center.
What is the proper way to do that?
Currently I'm stuck with this:
;WITH R(N) AS
(
SELECT 0
UNION ALL
SELECT N+1
FROM R
WHERE N < 29
)
SELECT * FROM MyTable e
LEFT OUTER JOIN (
SELECT N, MONTH(DATEADD(MONTH,-N,iif(#year != Year(GETDATE()), DATEFROMPARTS(#year, 12, 31) ,GETDATE()))) AS [Month],
YEAR(DATEADD(MONTH,-N,iif(#year!= Year(GETDATE()), DATEFROMPARTS(#year, 12, 31) ,GETDATE()))) AS [Year]
FROM R) s
ON s.[Year] = e.[Year] AND s.[Month] = e.[Month]
WHERE s.[N] is not null
This is not really what I want to do, since it just cuts off next year months
How about something simple like this:
;WITH CTE AS (
SELECT Month
,Year
,Value
,ROW_NUMBER() OVER (ORDER BY Year, Month) rn
FROM MyTable
)
SELECT Month
,Year
,Value
FROM CTE
WHERE rn >= (SELECT rn - 15 FROM MyTable WHERE Year = #Year AND Month = #Month)
AND rn <= (SELECT rn + 15 FROM MyTable WHERE Year = #Year AND Month = #Month);
I'm sure there's a more efficient way to do it, but this strikes me as the most maintainable way to do it. It should even work when you pick a value close to the first or last records in the table.
I can't tell if you want 31 rows no matter what. At one point it sounds like you do, and at another point it sounds like you don't.
EDIT: Ok, so you do always want 31 rows if available.
Alright, try this:
;WITH CTE AS (
SELECT Month
,Year
,Value
,ROW_NUMBER() OVER (ORDER BY Year, Month) rn
FROM MyTable
),
CTE_2 AS (
SELECT TOP (31) Month
,Year
,Value
FROM CTE
ORDER BY ABS(rn - (SELECT rn FROM MyTable WHERE Year = #Year AND Month = #Month)) ASC
)
SELECT Month
,Year
,Value
FROM CTE_2
ORDER BY Year, Month;
Basically, you calculate the difference from the target row number, get the first 31 rows there, and then resort them for output.
Check this out,
DECLARE #iPrevRows int
DECLARE #iPostRows int
DECLARE #Year int = 2016
DECLARE #Month int = 2
SELECT #iPrevRows= Count(*)
FROM
[GuestBook].[dbo].[tblTest]
where (year < #Year )
or (year =#Year and month < #Month)
SELECT #iPostRows= count(*) from
[GuestBook].[dbo].[tblTest]
where (year > #Year )
or (year =#Year and month > #Month)
if (#iPrevRows > 15)
select #iPrevRows =15
if (#iPostRows > 15)
select #iPostRows =15
if (#iPrevRows < 15 )
select #iPostRows = #iPostRows + (15-#iPrevRows)
else if (#iPostRows < 15 )
select #iPrevRows = #iPrevRows + (15-#iPostRows)
CREATE TABLE #tempValues
(
Year int NOT NULL,
Month int NOT NULL,
Value float
)
insert into #tempValues
SELECT top (#iPrevRows) Month, Year, Value
from
[GuestBook].[dbo].[tblTest]
where (year < #Year )
or (year =#Year and month < #Month)
order by 2 desc,1 desc
insert into #tempValues
SELECT Month, Year, Value
from
[GuestBook].[dbo].[tblTest]
where (year =#Year and month = #Month)
insert into #tempValues
SELECT top (#iPostRows) Month, Year, Value
from
[GuestBook].[dbo].[tblTest]
where (year > #Year )
or (year =#Year and month > #Month)
order by 2 ,1
select * from #tempValues
order by 2,1
Here is what I've done, seems to be working
select * from (
select top(31) * from MyTable r
order by ABS(DATEDIFF(month, DATEFROMPARTS(r.Year, r.Month, 1), DATEFROMPARTS(#Year, #Month, 1)))) s
order by Year, Month
I did it that way.
DECLARE #year INT = 2014, #month INT = 6;
WITH TableAux
AS (SELECT MyTable.Month
, MyTable.Year
FROM MyTable
WHERE MyTable.Year = #year
AND MyTable.Month = #month)
SELECT tb1.Month
, tb1.Year
, tb1.Value
FROM
(
SELECT TOP 16 MyTable.Month
, MyTable.Year
, MyTable.Value
FROM MyTable
CROSS JOIN TableAux
WHERE MyTable.Month <= TableAux.Month
AND MyTable.Year <= TableAux.Year
ORDER BY MyTable.Month DESC, MyTable.Year DESC
) tb1
UNION ALL
SELECT tb2.Month
, tb2.Year
, tb2.Value
FROM
(
SELECT TOP 15 MyTable.Month
, MyTable.Year
, MyTable.Value
FROM MyTable
CROSS JOIN TableAux
WHERE MyTable.Month > TableAux.Month
AND MyTable.Year > TableAux.Year
ORDER BY MyTable.Month, MyTable.Year
) tb2
ORDER BY Year, Month

How to get number of employees per year

I have a table called Mst_Employee. The fields are:
Emp_No | Emp_Name | Emp_JoiningDate | Emp_ResignedDate | Emp_Status
How do I get the No. of Employees by year for each year somebody joined or resigned? (Joined and Resigned includes by year)
E.g. result should look like this:
Year No. of Employees.
------------------------
2011 125
2012 130
2013 100
One way to solve it is with a recursive cte and group by:
DECLARE #FromYear int, #ToYear int
SELECT #FromYear = YEAR(MIN(Emp_JoiningDate)),
#ToYear = YEAR(GETDATE())
FROM Mst_Employee
;WITH CTE AS
(
SELECT #FromYear As TheYear
UNION ALL
SELECT TheYear + 1
FROM CTE
WHERE TheYear < #ToYear
)
SELECT TheYear as [Year],
COUNT
(
CASE WHEN TheYear <= YEAR(COALESCE(Emp_ResignedDate, GETDATE())) THEN
1
END
) As [No. of Employees.]
FROM CTE
INNER JOIN Mst_Employee ON(TheYear >= YEAR(Emp_JoiningDate))
GROUP BY TheYear
See fiddle here
You can achieve this with:
select y as [Year], count(*) as [No. of Employees.]
from(select Emp_No, YEAR(Emp_JoiningDate) as y from Mst_Employee
union
select Emp_No, YEAR(Emp_ResignedDate) from Mst_Employee
where Emp_ResignedDate is not null)t
group by y

TSQL loop months in sequence

I have an query that I'm feeling out-of-my depth with.
I need to loop through months between two dates and return a subset of data for each month with a blank row for months with no data.
For example:
TransactionID | Date | Value
1 | 01/01/2015 | £10
2 | 16/01/2015 | £15
3 | 21/01/2015 | £5
4 | 15/03/2015 | £20
5 | 12/03/2015 | £15
6 | 23/04/2015 | £10
Needs to return:
Month | Amount
January | £30
February | £0
March | £35
April | £10
My query will rely on specifying a date range so I can set the first and last date of the query.
I feel like I maybe over thinking this, but have gotten to that stage where you start to feel like you tying yourself in knots.
The key is having access to a list of integers to represent the months in the range. If you don't have a Numbers Table, then spt_values will do in a pinch.
SqlFiddle Demo
SELECT
[Year] = YEAR(DATEADD(month,[i],#range_start))
,[Month] = DATENAME(month,DATEADD(month,[i],#range_start))
,[Amount] = ISNULL(SUM([Value]),0)
FROM (
SELECT TOP (DATEDIFF(month,#range_start,#range_end)+1)
ROW_NUMBER() OVER(ORDER BY (SELECT 1))-1 [i]
FROM master.dbo.spt_values
) t1
LEFT JOIN #MyTable t2
ON (t1.[i] = DATEDIFF(month,#range_start,t2.[Date]) )
GROUP BY [i]
ORDER BY [i]
SQL is a tricky language at first. You actually do not want a loop. In fact, you pretty much never want to loop in SQL except in very few cases. Try this out:
DECLARE #StartDate DATE,
#EndDate DATE;
SET #StartDate = '01 January 2015';
SET #EndDate = '30 April 2015';
WITH CTE_Months
AS
(
SELECT #StartDate dates
UNION ALL
SELECT DATEADD(MONTH,1,dates)
FROM CTE_Months
WHERE DATEADD(MONTH,1,dates) < #EndDate
)
SELECT YEAR(B.[date]) AS yr,
DATENAME(MONTH,B.[Date]) AS month_name,
SUM(ISNULL(B.Value,0)) AS Amount
FROM CTE_Months A
LEFT JOIN yourTable B
ON YEAR(A.[date]) = YEAR(B.[date])
AND MONTH(A.[date]) = MONTH(B.[date])
GROUP BY YEAR(B.[date]),DATENAME(MONTH,B.[Date])
One way: create a table called months with a monthnum int field and 12 rows of [1..12]
declare #start date = '01 jan 2015',
#end date = '30 apr 2015'
select
datename(month, dateadd(month, monthnum, 0) - 1),
isnull(Amount, 0)
from months
left join (
select
month(date) Month,
sum(Value) Amount
from tbl
where date between #start and #end
group by month(date)
) T on (T.Month = months.monthnum)
where months.monthnum between month(#start) and month(#end)
order by monthnum
The following code will generate one output row for each month between the first and last transaction dates. Spanning a year boundary, or multiple years, is handled correctly.
-- Some sample data.
declare #Transactions as Table
( TransactionId Int Identity, TransactionDate Date, Value Int );
insert into #Transactions ( TransactionDate, Value ) values
( '20141125', 10 ), ( '20150311', 20 ), ( '20150315', 5 ), ( '20150509', 42 );
select * from #Transactions;
with
-- Determine the first and last dates involved.
Range as (
select Min( TransactionDate ) as FirstDate, Max( TransactionDate ) as LastDate
from #Transactions ),
-- Generate a set of all of the months in the range.
Months as (
select DateAdd( month, DateDiff( month, 0, FirstDate ), 0 ) as Month,
DateAdd( month, DateDiff( month, 0, LastDate ), 0 ) as LastMonth
from Range
union all
select DateAdd( month, 1, Month ), LastMonth
from Months
where Month < LastMonth )
-- Summarize the transactions.
select M.Month, Coalesce( Sum( T.Value ), 0 ) as Total
from Months as M left outer join
#Transactions as T on DateAdd( month, DateDiff( month, 0, T.TransactionDate ), 0 ) = M.Month
group by M.Month
order by M.Month
option ( MaxRecursion 1000 );

SQL Server: How to select all days in a date range even if no data exists for some days

I have an app that needs to show a bar graph for activity over the last 30 days. The graph needs to show all days even if there is no activity for the day.
for example:
DATE COUNT
==================
1/1/2011 5
1/2/2011 3
1/3/2011 0
1/4/2011 4
1/5/2011 0
etc....
I could do post processing after the query to figure out what dates are missing and add them but was wondering if there is an easier way to do it in SQL Server. Thanks much
You can use a recursive CTE to build your list of 30 days, then join that to your data
--test
select cast('05 jan 2011' as datetime) as DT, 1 as val into #t
union all select CAST('05 jan 2011' as datetime), 1
union all select CAST('29 jan 2011' as datetime), 1
declare #start datetime = '01 jan 2011'
declare #end datetime = dateadd(day, 29, #start)
;with amonth(day) as
(
select #start as day
union all
select day + 1
from amonth
where day < #end
)
select amonth.day, count(val)
from amonth
left join #t on #t.DT = amonth.day
group by amonth.day
>>
2011-01-04 00:00:00.000 0
2011-01-05 00:00:00.000 2
2011-01-06 00:00:00.000 0
2011-01-07 00:00:00.000 0
2011-01-08 00:00:00.000 0
2011-01-09 00:00:00.000 0
...
Using CTE:
WITH DateTable
AS
(
SELECT CAST('20110101' AS Date) AS [DATE]
UNION ALL
SELECT DATEADD(dd, 1, [DATE])
FROM DateTable
WHERE DATEADD(dd, 1, [DATE]) < cast('20110201' as Date)
)
SELECT dt.[DATE], ISNULL(md.[COUNT], 0) as [COUNT]
FROM [DateTable] dt
LEFT JOIN [MyData] md
ON md.[DATE] = dt.[DATE]
This is assuming everything's a Date; if it's DateTime, you'll have to truncate (with DATEADD(dd, 0, DATEDIFF(dd, 0, [DATE]))).
#Alex K.'s answer is completely correct, but it doesn't work for versions that do not support Recursive common table expressions (like the version I'm working with). In this case the following would do the job.
DECLARE #StartDate datetime = '2015-01-01'
DECLARE #EndDate datetime = SYSDATETIME()
;WITH days AS
(
SELECT DATEADD(DAY, n, DATEADD(DAY, DATEDIFF(DAY, 0, #StartDate), 0)) as d
FROM ( SELECT TOP (DATEDIFF(DAY, #StartDate, #EndDate) + 1)
n = ROW_NUMBER() OVER (ORDER BY [object_id]) - 1
FROM sys.all_objects ORDER BY [object_id] ) AS n
)
select days.d, count(t.val)
FROM days LEFT OUTER JOIN yourTable as t
ON t.dateColumn >= days.d AND t.dateColumn < DATEADD(DAY, 1, days.d)
GROUP BY days.d
ORDER BY days.d;
My scenario was a bit more complex than the OP example, so thought I'd share to help others who have similar issues. I needed to group sales orders by date taken, whereas the orders are stored with datetime.
So in the "days" lookup table I could not really store as a date time with the time being '00:00:00.000' and get any matches. Therefore I stored as a string and I tried to join on the converted value directly.
That did not return any zero rows, and the solution was to do a sub-query returning the date already converted to a string.
Sample code as follows:
declare #startDate datetime = convert(datetime,'09/02/2016')
declare #curDate datetime = #startDate
declare #endDate datetime = convert(datetime,'09/09/2016')
declare #dtFormat int = 102;
DECLARE #null_Date varchar(24) = '1970-01-01 00:00:00.000'
/* Initialize #days table */
select CONVERT(VARCHAR(24),#curDate, #dtFormat) as [Period] into #days
/* Populate dates into #days table */
while (#curDate < #endDate )
begin
set #curDate = dateadd(d, 1, #curDate)
insert into #days values (CONVERT(VARCHAR(24),#curDate, #dtFormat))
end
/* Outer aggregation query to group by order numbers */
select [Period], count(c)-case when sum(c)=0 then 1 else 0 end as [Orders],
sum(c) as [Lines] from
(
/* Inner aggregation query to sum by order lines */
select
[Period], sol.t_orno, count(*)-1 as c
from (
/* Inner query against source table with date converted */
select convert(varchar(24),t_dldt, #dtFormat) as [shipdt], t_orno
from salesorderlines where t_dldt > #startDate
) sol
right join #days on shipdt = #days.[Period]
group by [Period], sol.t_orno
) as t
group by Period
order by Period desc
drop table #days
Sample Results:
Period Orders Lines
2016.09.09 388 422
2016.09.08 169 229
2016.09.07 1 1
2016.09.06 0 0
2016.09.05 0 0
2016.09.04 165 241
2016.09.03 0 0
2016.09.02 0 0
Either define a static table containing dates or create a temp table \ table variable on the fly to store each date between (and including) the min and max dates in the activity table you're working with.
Use an outer join between the two tables to make sure that each date in your dates table is reflected in the output.
If you use a static dates table you will likely want to limit the date range that is output to only the range needed in the graph.
Without Transact-SQL: MS SQL 2005 - Get a list of all days of a Month:
In my case '20121201' is a predefined value.
SELECT TOp (Select Day(DateAdd(day, -Day(DateAdd(month, 1,
'20121201')),
DateAdd(month, 1, '20121201')))) DayDate FROM ( SELECT DATEADD(DAY,ROW_NUMBER() OVER (ORDER BY (SELECT
NULL))-1,'20121201') as DayDate FROM sys.objects s1 CROSS JOIN
sys.objects s2 ) q
Recursive CTE works for max 80 years which is good enough:
DECLARE #dStart DATE,
#dEnd DATE
SET #dStart = GETDATE ()
SET #dEnd = DATEADD (YEAR, 80, #dStart)
;WITH CTE AS
(
SELECT #dStart AS dDay
UNION ALL
SELECT DATEADD (DAY, 1, dDay)
FROM CTE
WHERE dDay < #dEnd
)
SELECT * FROM CTE
OPTION (MaxRecursion 32767)
create a numbers table and use it like:
declare #DataTable table (DateColumn datetime)
insert #DataTable values ('2011-01-09')
insert #DataTable values ('2011-01-10')
insert #DataTable values ('2011-01-10')
insert #DataTable values ('2011-01-11')
insert #DataTable values ('2011-01-11')
insert #DataTable values ('2011-01-11')
declare #StartDate datetime
SET #StartDate='1/1/2011'
select
#StartDate+Number,SUM(CASE WHEN DateColumn IS NULL THEN 0 ELSE 1 END)
FROM Numbers
LEFT OUTER JOIN #DataTable ON DateColumn=#StartDate+Number
WHERE Number>=1 AND Number<=15
GROUP BY #StartDate+Number
OUTPUT:
----------------------- -----------
2011-01-02 00:00:00.000 0
2011-01-03 00:00:00.000 0
2011-01-04 00:00:00.000 0
2011-01-05 00:00:00.000 0
2011-01-06 00:00:00.000 0
2011-01-07 00:00:00.000 0
2011-01-08 00:00:00.000 0
2011-01-09 00:00:00.000 1
2011-01-10 00:00:00.000 2
2011-01-11 00:00:00.000 3
2011-01-12 00:00:00.000 0
2011-01-13 00:00:00.000 0
2011-01-14 00:00:00.000 0
2011-01-15 00:00:00.000 0
2011-01-16 00:00:00.000 0
(15 row(s) affected)
Maybe something like this:
Create DaysTable countaining the 30 days.
And DataTable containing "day" column and "count" column.
And then left join them.
WITH DaysTable (name) AS (
SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 -- .. And so on to 30
),
DataTable (name, value) AS (
SELECT DATEPART(DAY, [Date]), [Count]
FROM YourExampleTable
WHERE [Date] < DATEADD (day , -30 , getdate())
)
SELECT DaysTable.name, DataTable.value
FROM DaysTable LEFT JOIN
DataTable ON DaysTable.name = DataTable.name
ORDER BY DaysTable.name
For those with a recursion allergy
select SubQ.TheDate
from
(
select DATEADD(day, a.a + (10 * b.a) + (100 * c.a), DATEADD(day, DATEDIFF(day, 0, GETDATE()), 0) - 30) AS TheDate
from
(
(select 0 as a 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) as a
cross join (select 0 as a 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) as b
cross join (select 0 as a 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) as c
)
WHERE a.a + (10 * b.a) + (100 * c.a) < 30
) AS SubQ
ORDER BY TheDate
Try it.
DECLARE #currentDate DATETIME = CONVERT(DATE, GetDate())
DECLARE #startDate DATETIME = DATEADD(DAY, -DAY(#currentDate)+1, #currentDate)
;WITH fnDateNow(DayOfDate) AS
(
SELECT #startDate AS DayOfDate
UNION ALL
SELECT DayOfDate + 1 FROM fnDateNow WHERE DayOfDate < #currentDate
) SELECT fnDateNow.DayOfDate FROM fnDateNow
DECLARE #StartDate DATE = '20110101', #NumberOfYears INT = 1;
DECLARE #CutoffDate DATE = DATEADD(YEAR, #NumberOfYears, #StartDate);
CREATE TABLE Calender
(
[date] DATE
);
INSERT Calender([date])
SELECT d
FROM
(
SELECT d = DATEADD(DAY, rn - 1, #StartDate)
FROM
(
SELECT TOP (DATEDIFF(DAY, '2011-01-01', '2011-12-31'))
rn = ROW_NUMBER() OVER (ORDER BY s1.[object_id])
FROM sys.all_objects AS s1
CROSS JOIN sys.all_objects AS s2
ORDER BY s1.[object_id]
) AS x
) AS y;
create table test(a date)
insert into test values('1/1/2011')
insert into test values('1/1/2011')
insert into test values('1/1/2011')
insert into test values('1/1/2011')
insert into test values('1/1/2011')
insert into test values('1/2/2011')
insert into test values('1/2/2011')
insert into test values('1/2/2011')
insert into test values('1/4/2011')
insert into test values('1/4/2011')
insert into test values('1/4/2011')
insert into test values('1/4/2011')
select c.date as DATE,count(t.a) as COUNT from calender c left join test t on c.date = t.a group by c.date

Resources