I need to create a patient census report that shows average number patients present per hour and per day of a week over a given time period. This would allow me to show, for example, over the last 6 months there was an average of 4 people in the ER on Mondays. I have a table valued function that will show the following for patients:
VisitID, FromDateTime, ThruDateTime, LocationID.
I was able to show the number of patients in, for example, the ER for a given day using the code below. But it is limited to one day only. (Adapted from http://www.sqlservercentral.com/Forums/Topic939818-338-1.aspx).
--Census Count by Date Range--
DECLARE #BeginDateParameter DateTime, #EndDateParameter DateTime
SET #BeginDateParameter = '20160201'
SET #EndDateParameter = '2016-02-01 23:59:59.000'
----------------------------------------------------
-- Create a temp table to hold the necessary values
-- plus an extra "x" field to track processing
----------------------------------------------------
IF OBJECT_ID('tempdb..#Temp') IS NOT NULL DROP TABLE #Temp
CREATE TABLE #Temp (ID INT Identity NOT NULL, VisitID VarChar(100), SourceID VarChar(100),
FromDateTime DateTime, ThruDateTime DateTime, x INT)
----------------------------------------------------
-- Populate the temp table with values from the
-- the actual table in the database
----------------------------------------------------
INSERT INTO #Temp
SELECT VisitID, FromDateTime, ThruDateTime
FROM PatientFlowTable(BeginDateParameter,#EndDateParameter)
WHERE (FromDateTime BETWEEN #BeginDateParameter AND #EndDateParameter +1
OR ThruDateTime BETWEEN #BeginDateParameter AND #EndDateParameter +1)
AND LocationID = 'ER'
-- Given Period is taken as inclusive of given hours in the input (eg. 15:25:30 will be taken as 15:00:00)
-- frist make sure that the minutes, seconds and milliseconds are removed from input range for clarity
set #BeginDateParameter = dateadd(hh, datepart(hh,#BeginDateParameter), convert(varchar(12),#BeginDateParameter,112))
set #EndDateParameter = dateadd(hh, datepart(hh,#EndDateParameter), convert(varchar(12),#EndDateParameter,112))
-- you may create this CTE by other ways (eg. from permanent Tally table)...
;with dh
as
(
select top 24
DATEADD(hour,ROW_NUMBER() OVER (ORDER BY [Object_id])-1,convert(varchar(12),#BeginDateParameter,112)) as HoDstart
,DATEADD(hour,ROW_NUMBER() OVER (ORDER BY [Object_id]),convert(varchar(12),#BeginDateParameter,112)) as HoDend
,ROW_NUMBER() OVER (ORDER BY Object_id)-1 as DayHour
from sys.columns -- or any other (not very big) table which have more than 24 raws, just remamber to change
-- [Object_id] in OVER (ORDER BY [Object_id]... to some existing column
)
select d.DayHour, count(w.VisitID) as PatientCount
from dh d
left join #Temp w
on w.[FromDateTime] < d.HoDend
and w.[ThruDateTime] >= d.HoDstart
where d.HoDstart between #BeginDateParameter and #EndDateParameter
group by d.DayHour
order by d.DayHour
SELECT VisitID, FromDateTime, ThruDateTime
FROM PatientFlowTable(BeginDateParameter,#EndDateParameter)
WHERE (FromDateTime BETWEEN #BeginDateParameter AND #EndDateParameter +1
OR ThruDateTime BETWEEN #BeginDateParameter AND #EndDateParameter +1)
AND LocationID = 'ER'
Output example for the first three hours show patients that were present in the ER by taking into account their departure time.
Hour PatientCount
0 2
1 3
2 3
For querying short time periods, I would create a table-valued function that generates the hour entries. The results table can be joined into your query.
CREATE FUNCTION [dbo].[f_hours] (#startDateTime DATETIME,
#endDateTime DATETIME)
RETURNS #result TABLE (
[dateTime] DATETIME PRIMARY KEY
)
AS
BEGIN
DECLARE
#dateTime DATETIME = #startDateTime,
#hours INT = DATEDIFF(hour, #startDateTime, #endDateTime)
WHILE (#dateTime <= #endDateTime)
BEGIN
INSERT
INTO #result
VALUES (#dateTime)
SET #dateTime = DATEADD(hour, 1, #dateTime)
END
RETURN
END
GO
The time required by the function can be output with SET STATISTICS TIME ON. For the generation of over 6000 records needs my computer 53 ms.
SET STATISTICS TIME ON
SELECT *
FROM [dbo].[f_hours]('2016-02-01', '2016-02-10 16:00')
SET STATISTICS TIME OFF
Related
I am dealing with the holiday table of a application and I have to find the next working days based on the holiday list in that table .
If the input is a working day, we expect a blank/NULL to be returned, but if it is a holiday, we expect the next working day to be returned.
My holiday table contains below sample data.
First date column is for startdate and second one is for enddate. Instead of using startdate and enddate for two consecutive holidays. Client have created two separate rows.
Now I have to write a select query which will give the next working days based on that sample data.
Suppose if I am passing '2016-04-20 00:00:00.000' as the conditional date then the query should return '2016-04-22 00:00:00.000' as the working date and there are consecutive two holidays.
2016 2016-04-20 00:00:00.000 2016-04-20 00:00:00.000 Test
2016 2016-04-21 00:00:00.000 2016-04-21 00:00:00.000 Test2
2016 2016-04-28 00:00:00.000 2016-04-28 00:00:00.000 Test3
You can try this:
--create table holidays(y int, ds datetime, de datetime, hname varchar(10));
--insert into holidays values
--(2016,'2016-04-20 00:00:00.000','2016-04-20 00:00:00.000','Test'),
--(2016,'2016-04-21 00:00:00.000','2016-04-21 00:00:00.000','Test2'),
--(2016,'2016-04-28 00:00:00.000','2016-04-28 00:00:00.000','Test3'),
--(2016,'2016-04-22 00:00:00.000','2016-04-22 00:00:00.000','Test4')
CREATE FUNCTION dbo.getNextDate(#dateToCheck datetime) RETURNS Datetime
AS
BEGIN
RETURN(
select top 1 dateadd(d,1,de) from
(select y,
MIN(ds) as ds,
MAX(ds) as de
from
(
Select
*,
ROW_NUMBER() OVER(ORDER BY ds asc) as ranking
from holidays
) t
group by y,(CAST(ds AS INT)-Ranking)
)t
where #dateToCheck BETWEEN ds AND de
)
END
Upon testing:
SELECT dbo.getNextDate('2016-04-23 00:00:00.000')-- returns NULL
SELECT dbo.getNextDate('2016-04-21 00:00:00.000')-- returns 2016-04-23 00:00:00.000
SQL demo link
Let me know if the below mentioned code works.This code first makes a calendar table and then excludes Saturday and Sunday from the dates.
declare #mn date = (select min(yourdate) from table)
select top 1 a.caldate
from
(
select dateadd(dd,row_number() over (order by (select 1)) - 1,#mn) as caldate
from sys.all_objects
) as a
where a.caldate not in (select cast(yourdate as date) as yourdate from TableA) and datename(dw,a.caldate) not in ('Saturday','Sunday') and a.caldate >= '2016-04-20'
Suposing a holidays table with this structure:
CREATE TABLE holidays (
[year] int,
[ds] datetime,
[de] datetime,
[description] nvarchar(50)
)
You can create a function that iterates through dates until it finds the correct one
CREATE FUNCTION dbo.getNextDate(#dateToCheck datetime) RETURNS Datetime
AS
BEGIN
DECLARE #tempDate datetime
SET #tempDate=DATEADD(day,1,#dateToCheck)
WHILE EXISTS(SELECT * FROM holidays WHERE #tempDate BETWEEN ds AND de)
BEGIN
SET #tempDate=DATEADD(day,1,#tempDate)
END
RETURN #tempDate
END
This is a very rudimentary first aproximation but it should work.
I currently have an MS SQL query which calculates the length of time that each User has been logged in to a system during one day. The table I am extracting this information from records each log in/log out as a separate record. Currently, my MS SQL code is as follows:
SELECT
CAST(DateTime As Date),
UserID,
MIN(DateTime),
MAX(DateTime),
DATEDIFF(SS, MIN(DateTime), MAX(DateTime))
FROM
LoginLogoutData
WHERE
CAST(DateTime AS DATE) = '01/01/2015'
GROUP BY
CAST(DateTime As Date),
UserID
This works as required and creates a table similar to the below.
Date UserID FirstLogIn FinalLogOut LoggedInTime
......... ...... .......... ............ ............
01/01/2015 ABC 07:42:57 14:57:13 26056
01/01/2015 DEF 07:45:49 13:57:56 22326
This works fine for one day's-worth of data. However, if I wanted to calculate the length of time that someone was logged into the system for during a larger date range, e.g. a week or month, this would not work; it would calculate the length of time between the user's log in on the first day and their log out on the final day.
Basically, I would like my code to calculate (Max(DateTime) - Min(DateTime)) FOR EACH DAY then sum all these values together into one simple table grouped only by UserId. I would then be able to set my date range as I please and receive the correct results.
So I would have a table as follows:
UserId LoggedInTime
........ .............
ABC 563287
DEF 485823
GEH 126789
I assume I need to use a GROUP BY within the MIN() function but I don't have much experience with this yet.
Does anyone have any experience with this? Any help would be greatly appreciated.
Thank you.
First you need to aggregate by date, and then by larger units of time. For instance, for the year to date:
SELECT UserId, SUM(diffsecs)
FROM (SELECT CAST(DateTime As Date) as thedate, UserID,
DATEDIFF(second, MIN(DateTime), MAX(DateTime)) as diffsecs
FROM LoginLogoutData
GROUP BY CAST(DateTime As Date), UserID
) ud
WHERE thedate between '2015-01-01' and getdate();
You can use another group by statement to the first query like below:
Select UserID,SUM(LoggedTime)
FROM
(
SELECT CAST(DateTime As Date),
UserID, MIN(DateTime),
MAX(DateTime),
DATEDIFF(SS, MIN(DateTime), MAX(DateTime)) AS LoggedTime
FROM LoginLogoutData
WHERE CAST(DateTime AS DATE) = '01/01/2015'
GROUP BY CAST(DateTime As Date), UserID
) As temp
GROUP BY UserID
Here you can change the where clause to match the data range. It will first calculate logged time for each day and then get the sum of all days per user.
Here is an example of how to do this with working sample data and the T-SQL included.
-- original table you described
CREATE TABLE LoginLogoutData (UserID int, DateTime DateTime)
GO
-- clean any previous sample records
TRUNCATE TABLE LoginLogOutData
/*local variables for iteration*/
DECLARE #i int = 1
DECLARE #n int
DECLARE #entryDate DateTime = GETDATE()
--populate the table with some sample data
/* for each of the five sample users, generate sample login and logout
data for 30 days. Each login and logout are simply an hour apart for demo purposes. */
SET NOCOUNT ON
-- iterate over 5 users (userid)
WHILE (#i <= 5)
BEGIN
--set the initial counter for the date loop
SET #n = 1
--dated entry loop
WHILE (#n <= 30)
BEGIN
-- increment to the next day
SET #entryDate = DateAdd(dd,#n,GETDATE())
--logged in entry
INSERT INTO LoginLogoutData (DateTime, UserID)
SELECT #entryDate,#i
-- logged out entry
INSERT INTO LoginLogoutData (DateTime, UserID)
SELECT DateAdd(hh,1,#entryDate),#i
--increment counter
SET #n = #n+1
END
--increment counter
SET #i=#i+1
END
GO
/* demonstrate that for each user each day has entries and that
the code calculates (Max(DateTime) - Min(DateTime)) FOR EACH DAY
*/
SELECT UserID,
MIN(DateTime) AS LoggedIn,
MAX(DateTime) AS LoggedOut,
DATEDIFF(SS, MIN(DateTime), MAX(DateTime)) AS LoginTime
FROM LoginLogoutData
GROUP BY CAST(DateTime As Date), UserID
/*this is a table variable used to support the "sum all these values together into one
simple table grouped only by UserId*/
DECLARE #SummedUserActivity AS TABLE (UserID int, DailyActivity int)
-- group the subtotals from each day per user
INSERT INTO #SummedUserActivity (UserID, DailyActivity)
SELECT UserID, DATEDIFF(SS, MIN(DateTime), MAX(DateTime))
FROM LoginLogoutData
GROUP BY CAST(DateTime As Date), UserID
-- demonstrate the sum of the subtotals grouped by userid
SELECT UserID, SUM(DailyActivity) AS TotalActivity
FROM #SummedUserActivity
GROUP BY UserID
Since you are already calculating LoggedInTime for each date, the following query would be necessary
SELECT USERID,SUM(LoggedInTime) LoggedInTime
FROM YOURTABLE
GROUP BY USERID
UPDATE
Since you have one record for login and the next record for logout(irrespective of date),we can use the concept of SELF JOIN(a table that joins itself) to get logout datetime for corresponding login time.
DECLARE #FROMDATE DATETIME='2014-01-01 07:42:57.000'
DECLARE #TODATE DATETIME='2015-02-01 07:42:57.000'
;WITH CTE AS
(
-- ROW_NUMBER() is taken as logic for self joining.
SELECT ROW_NUMBER() OVER(ORDER BY USERID,[DATETIME]) RNO,*
FROM #TEMP
WHERE [DATETIME] >= #FROMDATE AND [DATETIME] < #TODATE
)
,CTE2 AS
(
-- Since we are using SELF JOIN,we will get the next row's
-- datetime(ie, Logout time for corresponding login time)
SELECT C1.*,C2.[DATETIME] [DATETIME2],
DATEDIFF(SS, C1.[DateTime], C2.[DateTime]) SECONDSDIFF
FROM CTE C1
LEFT JOIN CTE C2 ON C1.RNO=C2.RNO-1
WHERE C1.RNO%2 <> 0
-- Since we get the next row's datetime in current row,
-- we omit each logout time's row
)
SELECT USERID,SUM(SECONDSDIFF) SECONDSDIFF
FROM CTE2
SQL FIDDLE
I have two tables used for storing employee attendance details.
one table stores emp Id and respective in time and out time in date time info second table stores the other employee details as employee id, employee name etc...
I have a requirement to generate a report which shows total hours worked by emp per day, a status column storing details like Present if total hours > 4.5 else absent
also need to count the number of days an employee have status as present, number of days for which an employee's total hour is greater than 6 less than 8.5.
I have written the query to fetch every details but the performance is unacceptable it takes around 30-35 miniutes to fetch all the details
if I exclude the days counting logic it takes around 1-2 minutes
the table structure is
Ist Employee table
EmployeeID, EmployeeName.....other details(not necessary at this moment)
Attendance table
Emp_ID, INOUT_Time
My query
DECLARE #currStartDate DATETIME
DECLARE #currEndDate DATETIME
declare #startDate datetime;
declare #endDate datetime;
set #startDate = CONVERT(Datetime, '12/16/2013');
set #endDate = CONVERT(Datetime, '01/16/2014');
SET #currStartDate=#startDate
SET #currEndDate=dateAdd(day,1,#startDate)
DECLARE #formatTable TABLE
(
EmployeeCode varchar(10),
EmployeeName varchar(100),
[Date] Datetime,
InTime datetime,
OutTime datetime,
TotalHrs varchar(10),
[Status] varchar(10)
)
WHILE #currEndDate <= #endDate
BEGIN
--get the day by day attendance Range
INSERT INTO #formatTable
(
EmployeeCode,
EmployeeName,
[Date],
InTime,
OutTime
)
SELECT
E.EmployeeID,
ISNULL(LTRIM(RTRIM(E.FirstName)),'') +' '+ISNULL(LTRIM(RTRIM(E.LastName)),'') AS EmployeeName,
#currStartDate,
MIN(AD.INOUT_Time) as INTIME,
CASE WHEN MAX(AD.INOUT_Time)=MIN(AD.INOUT_Time) THEN NULL ELSE MAX(AD.INOUT_Time) END as OUTTIME
FROM employees E WITH(NOLOCK)
LEFT OUTER JOIN Attendance AD
ON E.EmployeeID = AD.Emp_ID
AND INOUT_Time BETWEEN #currStartDate AND #currEndDate
GROUP BY E.EmployeeID,DATEADD(dd, 0, DATEDIFF(dd, 0, INOUT_Time ))
UPDATE #formatTable
SET TotalHrs=Convert(varchar(20),DATEDIFF(MINUTE, ISNULL(InTime,GETDATE()),ISNULL(OutTime,InTime))/Convert(decimal(4,2),60))
,[Status] =(CASE WHEN DATEDIFF(MINUTE, ISNULL(InTime,GETDATE()),ISNULL(OutTime,InTime))/Convert(decimal(4,2),60) >= 4.5
THEN 'P'
ELSE 'Abs' END )
,HoursStatus = (CASE WHEN DATEDIFF(MINUTE, ISNULL(InTime,GETDATE()),ISNULL(OutTime,InTime))/Convert(decimal(4,2),60) >= 8.5
THEN 'Greater Than 8.5'
WHEN DATEDIFF(MINUTE, ISNULL(InTime,GETDATE()),ISNULL(OutTime,InTime))/Convert(decimal(4,2),60) BETWEEN 6 AND 8.49
THEN '6-8.49'
WHEN DATEDIFF(MINUTE, ISNULL(InTime,GETDATE()),ISNULL(OutTime,InTime))/Convert(decimal(4,2),60) BETWEEN 4.5 AND 5.99
THEN '4.5-5.99' end)
WHERE [Date]=#currStartDate
-- moving to nextday
SELECT #currStartDate=DATEADD(DAY,1,#currStartDate)
SELECT #currEndDate=DATEADD(DAY,1,#currEndDate)
END
IF OBJECT_ID('tempdb..##output') IS NOT NULL
DROP TABLE ##output
SELECT EmployeeCode,EmployeeName,[Date],Convert(varchar(10),INTime,108) INTime,Convert(varchar(10),Outtime,108) Outtime,TotalHrs,[Status],Convert(varchar,#startDate,105) as StartDate,Convert(varchar,#endDate,105) as EndDate, (SELECT COUNT(*) FROM #formatTable counter
WHERE ft.EmployeeCode = counter.EmployeeCode AND counter.[Status] = 'P' ) AS TotalPresent, (SELECT COUNT(*) FROM #formatTable counter
WHERE ft.EmployeeCode = counter.EmployeeCode AND counter.HoursStatus = 'Greater Than 8.5' ) as gt8point5,(SELECT COUNT(*) FROM #formatTable counter
WHERE ft.EmployeeCode = counter.EmployeeCode AND counter.HoursStatus = '6-8.49' ) as gt6lessthan8,(SELECT COUNT(*) FROM #formatTable counter
WHERE ft.EmployeeCode = counter.EmployeeCode AND counter.HoursStatus = '4.5-5.99' ) as gt4point5lessthan6
INTO ##output FROM #formatTable as ft
GROUP BY EmployeeCode,EmployeeName,[Date],TotalHrs,INTime,Outtime,[Status]
SELECT * FROM ##output AS AttendanceReport
Any suggestion for improving the performance, especially the day counting logic
A quick look at your query suggest the following indexes.
-- Assuming you don't have EmployeeID as a clustered index
CREATE INDEX IX_Employees_EmployeeID
ON Employees (EmployeeID)
INCLUDE (FirstName, LastName)
CREATE INDEX IX_Attendance_EmployeeID_INOUTTime
ON Attendance (EmployeeID, INOUT_Time)
You could also create an index for #formatTable, but that depends what your query plan states and how many rows are generated.
I'm attempting to group contiguous date ranges to show the minimum and maximum date for each range. So far I've used a solution similar to this one: http://www.sqlservercentral.com/articles/T-SQL/71550/ however I'm on SQL 2000 so I had to make some changes. This is my procedure so far:
create table #tmp
(
date smalldatetime,
rownum int identity
)
insert into #tmp
select distinct date from testDates order by date
select
min(date) as dateRangeStart,
max(date) as dateRangeEnd,
count(*) as dates,
dateadd(dd,-1*rownum, date) as GroupID
from #tmp
group by dateadd(dd,-1*rownum, date)
drop table #tmp
It works exactly how I want except for one issue: weekends. My data sets have no records for weekend dates, which means any group found is at most 5 days. For instance, in the results below, I would like the last 3 groups to show up as a single record, with a dateRangeStart of 10/6 and a dateRangeEnd of 10/20:
Is there some way I can set this up to ignore a break in the date range if that break is just a weekend?
Thanks for the help.
EDITED
I didn't like my previous idea very much. Here's a better one, I think:
Based on the first and the last dates from the set of those to be grouped, prepare the list of all the intermediate weekend dates.
Insert the working dates together with weekend dates, ordered, so they would all be assigned rownum values according to their normal order.
Use your method of finding contiguous ranges with the following modifications:
1) when calculating dateRangeStart, if it's a weekend date, pick the nearest following weekday;
2) accordingly for dateRangeEnd, if it's a weekend date, pick the nearest preceding weekday;
3) when counting dates for the group, pick only weekdays.
Select from the resulting set only those rows where dates > 0, thus eliminating the groups formed only of the weekends.
And here's an implementation of the method, where it is assumed, that a week starts on Sunday (DATEPART returns 1) and weekend days are Sunday and Saturday:
DECLARE #tmp TABLE (date smalldatetime, rownum int IDENTITY);
DECLARE #weekends TABLE (date smalldatetime);
DECLARE #minDate smalldatetime, #maxDate smalldatetime, #date smalldatetime;
/* #1 */
SELECT #minDate = MIN(date), #maxDate = MAX(date)
FROM testDates;
SET #date = #minDate - DATEPART(dw, #minDate) + 7;
WHILE #date < #maxDate BEGIN
INSERT INTO #weekends
SELECT #date UNION ALL
SELECT #date + 1;
SET #date = #date + 7;
END;
/* #2 */
INSERT INTO #tmp
SELECT date FROM testDates
UNION
SELECT date FROM #weekends
ORDER BY date;
/* #3 & #4 */
SELECT *
FROM (
SELECT
MIN(date + CASE DATEPART(dw, date) WHEN 1 THEN 1 WHEN 7 THEN 2 ELSE 0 END)
AS dateRangeStart,
MAX(date - CASE DATEPART(dw, date) WHEN 1 THEN 2 WHEN 7 THEN 1 ELSE 0 END)
AS dateRangeEnd,
COUNT(CASE WHEN DATEPART(dw, date) NOT IN (1, 7) THEN date END) AS dates,
DATEADD(d, -rownum, date) AS GroupID
FROM #tmp
GROUP BY DATEADD(d, -rownum, date)
) s
WHERE dates > 0;
Suppose I have a SQL table of Awards, with fields for Date and Amount. I need to generate a table with a sequence of consecutive dates, the amount awarded in each day, and the running (cumulative) total.
Date Amount_Total Amount_RunningTotal
---------- ------------ -------------------
1/1/2010 100 100
1/2/2010 300 400
1/3/2010 0 400
1/4/2010 0 400
1/5/2010 400 800
1/6/2010 100 900
1/7/2010 500 1400
1/8/2010 300 1700
This SQL works, but isn't as quick as I'd like:
Declare #StartDate datetime, #EndDate datetime
Select #StartDate=Min(Date), #EndDate=Max(Date) from Awards
; With
/* Returns consecutive from numbers 1 through the
number of days for which we have data */
Nbrs(n) as (
Select 1 Union All
Select 1+n
From Nbrs
Where n<=DateDiff(d,#StartDate,#EndDate)),
/* Returns all dates #StartDate to #EndDate */
AllDays as (
Select Date=DateAdd(d, n, #StartDate)
From Nbrs )
/* Returns totals for each day */
Select
d.Date,
Amount_Total = (
Select Sum(a.Amount)
From Awards a
Where a.Date=d.Date),
Amount_RunningTotal = (
Select Sum(a.Amount)
From Awards a
Where a.Date<=d.Date)
From AllDays d
Order by d.Date
Option(MAXRECURSION 1000)
I tried adding an index to Awards.Date, but it made a very minimal difference.
Before I resort to other strategies like caching, is there a more efficient way to code the running total calculation?
I generally use a temporary table for this:
DECLARE #Temp TABLE
(
[Date] date PRIMARY KEY,
Amount int NOT NULL,
RunningTotal int NULL
)
INSERT #Temp ([Date], Amount)
SELECT [Date], Amount
FROM ...
DECLARE #RunningTotal int
UPDATE #Temp
SET #RunningTotal = RunningTotal = #RunningTotal + Amount
SELECT * FROM #Temp
If you can't make the date column a primary key then you need to include an ORDER BY [Date] in the INSERT statement.
Also, this question's been asked a few times before. See here or search for "sql running total". The solution I posted is, as far as I know, still the one with the best performance, and also easy to write.
I don't have a database setup in front of me so I hope the below works first shot. A pattern like this should result in a much speedier query...you're just joining twice, similar amount of aggregation:
Declare #StartDate datetime, #EndDate datetime
Select #StartDate=Min(Date), #EndDate=Max(Date) from Awards
;
WITH AllDays(Date) AS (SELECT #StartDate UNION ALL SELECT DATEADD(d, 1, Date)
FROM AllDays
WHERE Date < #EndDate)
SELECT d.Date, sum(day.Amount) Amount_Total, sum(running.Amount) Amount_RunningTotal
FROM AllDays d
LEFT JOIN (SELECT date, SUM(Amount) As Amount
FROM Awards
GROUP BY Date) day
ON d.Date = day.Date
LEFT JOIN (SELECT date, SUM(Amount) As Amount
FROM Awards
GROUP BY Date) running
ON (d.Date >= running.Date)
Group by d.Date
Order by d.Date
Note: I changed your table expression up top, it was leaving out the first day before...if this is intentional just slap a where clause on this to exclude it. Let me know in the comments if this doesn't work or doesn't fit and I'll make whatever adjustments.
Here's a working solution based on #Aaronaught's answer. The only gotcha I had to overcome in T-SQL was that #RunningTotal etc. can't be null (need to be converted to zero).
Declare #StartDate datetime, #EndDate datetime
Select #StartDate=Min(StartDate),#EndDate=Max(StartDate) from Awards
/* #AllDays: Contains one row per date from #StartDate to #EndDate */
Declare #AllDays Table (
Date datetime Primary Key)
; With
Nbrs(n) as (
Select 0 Union All
Select 1+n from Nbrs
Where n<=DateDiff(d,#StartDate,#EndDate)
)
Insert into #AllDays
Select Date=DateAdd(d, n, #StartDate)
From Nbrs
Option(MAXRECURSION 10000) /* Will explode if working with more than 10000 days (~27 years) */
/* #AmountsByDate: Contains one row per date for which we have an Award, along with the totals for that date */
Declare #AmountsByDate Table (
Date datetime Primary Key,
Amount money)
Insert into #AmountsByDate
Select
StartDate,
Amount=Sum(Amount)
from Awards a
Group by StartDate
/* #Result: Joins #AllDays and #AmountsByDate etc. to provide totals and running totals for every day of the award */
Declare #Result Table (
Date datetime Primary Key,
Amount money,
RunningTotal money)
Insert into #Result
Select
d.Date,
IsNull(bt.Amount,0),
RunningTotal=0
from #AllDays d
Left Join #AmountsByDate bt on d.Date=bt.Date
Order by d.Date
Declare #RunningTotal money Set #RunningTotal=0
Update #Result Set #RunningTotal = RunningTotal = #RunningTotal + Amount
Select * from #Result