# Average Daily Census

I have records that have start and end dates, denoting a clients' length of stay.  Say client 1 started on 1/2/2015 and ended on 1/5/2015.  Client 2 started 1/3/2015 and ended 1/6/2015, client 3 started... etc...
I need to report census by day:
1/1/2015 - 0
1/2/2015 - 1
1/3/2015 - 2
etc...

What is the cleanest way to do this.  I am using SQL Server 2008 r2.
LVL 7
###### Who is Participating?

Commented:
A "calendar" is best for something like this:

``````CREATE TABLE tblPatientStays (ID int identity(1, 1), PatientID int, StayStart datetime, StayEnd datetime)

INSERT INTO tblPatientStays (PatientID, StayStart, StayEnd) VALUES
(1, '2015-01-02', '2015-01-05'),
(2, '2015-01-04', '2015-01-10')

CREATE TABLE tblCalendar(Dt datetime)

INSERT INTO tblCalendar (Dt) VALUES
('2015-01-01'),
('2015-01-02'),
('2015-01-03'),
('2015-01-04'),
('2015-01-05'),
('2015-01-06'),
('2015-01-07'),
('2015-01-08'),
('2015-01-09'),
('2015-01-10'),
('2015-01-11'),
('2015-01-12'),
('2015-01-13'),
('2015-01-14'),
('2015-01-15'),
('2015-01-16'),
('2015-01-17'),
('2015-01-18'),
('2015-01-19'),
('2015-01-20'),
('2015-01-21'),
('2015-01-22'),
('2015-01-23'),
('2015-01-24'),
('2015-01-25'),
('2015-01-26'),
('2015-01-27'),
('2015-01-28'),
('2015-01-29'),
('2015-01-30'),
('2015-01-31')

SELECT c.Dt, COUNT(p.PatientID) AS NumPatients
FROM tblCalendar c LEFT JOIN
tblPatientStays p ON c.Dt >= p.StayStart AND c.Dt <= p.StayEnd
GROUP BY c.Dt
ORDER BY c.Dt

DROP TABLE tblCalendar
DROP TABLE tblPatientStays
``````
0

Commented:
There is also a quick and dirty method that exploits one of the system tables, but the calendar table approach is definitely better.

``````CREATE TABLE tblPatientStays (ID int identity(1, 1), PatientID int, StayStart datetime, StayEnd datetime)

INSERT INTO tblPatientStays (PatientID, StayStart, StayEnd) VALUES
(1, '2015-01-02', '2015-01-05'),
(2, '2015-01-04', '2015-01-10')

SELECT c.Dt, COUNT(p.PatientID) AS NumPatients
FROM (
SELECT DATEADD(day, number, '2015-01-01') AS Dt
FROM master..spt_values
WHERE Type = 'P' AND number < 31) c LEFT JOIN
tblPatientStays p ON c.Dt >= p.StayStart AND c.Dt <= p.StayEnd
GROUP BY c.Dt
ORDER BY c.Dt

DROP TABLE tblPatientStays
``````
0

Author Commented:
What is wrong with the second option?  Why is the first better?  The reason I ask is I am going to run this for a full year (or possibly more). Although I don't quite understand what is happening :)  the second looks cleaner and you don't seem to hard code any dates.
0

Author Commented:
Ah, I see.  That system table just has values when type = 'P'.  So I would have to adjust the 'where < 32 ' depending on the number of days in each month
0

Senior DBACommented:
If you have two known dates, just use DATEDIFF.  Absolutely no reason to waste resource using a calendar table and aggregation of top of it for that:

SELECT ..., DATEDIFF(DAY, start_date, end_date) + 1 AS total_stay_in_days, ...
FROM ...
0

Author Commented:
Sorry Scott, Patrick is on the right track as that is how they currently calculate average daily census by month.  Getting length of stay alone and averaging will only get you average length of stay, not daily/monthly averages.

But Patrick, any ideas on how to span multiple months or if a client's stay goes over two months?
0

Author Commented:
I was able to tweak Patrick's code to make it a little more flexible, but not sure how to handle multiple months...

``````CREATE TABLE tblPatientStays (ID int identity(1, 1), PatientID int, StayStart datetime, StayEnd datetime)

INSERT INTO tblPatientStays (PatientID, StayStart, StayEnd) VALUES
(1, '2015-01-02', '2015-02-05'),
(2, '2015-01-04', '2015-01-10')
--Get First of month: DATEADD(month, DATEDIFF(month, 0, @mydate), 0)
SELECT c.Dt, COUNT(p.PatientID) AS NumPatients
FROM (
FROM master..spt_values
WHERE Type = 'P'
) c LEFT JOIN
tblPatientStays p ON c.Dt between p.StayStart AND p.StayEnd
GROUP BY c.Dt
ORDER BY c.Dt

DROP TABLE tblPatientStays
``````
0

Commented:
"its quick and dirty"

it uses a system table that in the end you have no control over,

that means it could change as the result of any patch/fix and version release....
or your use of it could start to impact system performance....

for a quick one-off test in a development system , its a quick trick dba's sometimes use to look at things with...

as production level code its better to implement your own solutions ....
0

Senior DBACommented:
A tally table is still perfectly fine.  You don't need a "calendar" table for this.
0

Commented:
The problem with the spt_values approach is that it's kind of a "hidden" feature: not documented, and there is always the possibility that Microsoft kills it in a future release.  Also, you're limited to 2047 items, so you couldn't use this to generate, say, a daily calendar over 10 years.
0

Senior DBACommented:
You can also have  permissions issues if the user running the code isn't allowed to read the *master*.dbo.spt_values table.
0

Author Commented:
I still don't undersrtand how I can handle multiple months.
0
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.