Google Answers Logo
View Question
 
Q: DateDiff SQL Feature ( No Answer,   2 Comments )
Question  
Subject: DateDiff SQL Feature
Category: Computers > Programming
Asked by: ttg-ga
List Price: $35.00
Posted: 02 Dec 2003 13:28 PST
Expires: 01 Jan 2004 13:28 PST
Question ID: 282750
i am familiar with the datediff function in sql that returns the
number of days between two dates. My questions is once for example I
have that number of days is there a way to view the day break out. For
example, if the count is 35:
i want to know for January = 31, February = 4. Basically I need to
know the count per month, and which month. ASP, SQL, or ASP.net

Clarification of Question by ttg-ga on 02 Dec 2003 13:50 PST
We are running SQL Server 7.0

Request for Question Clarification by mathtalk-ga on 02 Dec 2003 18:22 PST
Hi, ttg-ga:

Will the two dates always be in the same calendar year, or can they be
spread across a year boundary (or more)?

regards, mathtalk-ga

Clarification of Question by ttg-ga on 02 Dec 2003 23:52 PST
Yes to your question and here is the a little more detail:

Table Name: ResidentInfo
Fields: id (int),firstname (varchar), lastname (varchar) ,moveindate
(datetime),moveoutdate (datetime),active (numeric) "1" is active,
siteid (numeric)

OK.

We need an sql statement or stored procedure that will create a report
that will calculate the number of days a resident has lived at a
particular site (nursing home facility) based on their move in date. 
Some residents have null values for the move out dates. The twist is
that we need the report to caculate per month and grand total: For
example:

*** This example is based on a current date of 4/31/2003

John Doe       
ResidentDays          Month          MoveInDate          MoveOutDate
31                          1                 1/1/2003             3/31/2003
28                          2                 1/1/2003             3/31/2003
31                          3                 1/1/2003             3/31/2003
continued ... til the current date....

Don Brown       
ResidentDays          Month          MoveInDate          MoveOutDate
31                          1                 1/1/2003             3/15/2003
28                          2                 1/1/2003             3/15/2003
16                          3                 1/1/2003             3/15/2003
continued ... til the current date.

The query must take in account leap year, and have the "year" as a
variable so that we could just pass it a year and id.
Well let us know what you think!

Request for Question Clarification by mathtalk-ga on 03 Dec 2003 07:17 PST
Hi, ttg-ga:

What would you think of creating a user-defined function?  The
prospect of this depends, naturally, on which database you are using: 
MS SQL Server, Oracle, Sybase ???

regards, mathtalk-ga

Clarification of Question by ttg-ga on 03 Dec 2003 08:19 PST
Sounds like a good idea:
Here is a stored procedure that does what i need i think. Take a look.

CREATE PROCEDURE GetResidentDays

     @ResidentId int = -1,
     @SiteId int = -1

AS
SELECT     COUNT(*) as ResidentDays, 
          YEAR(SqlDate) 'Year',
          MONTH(SqlDate) 'Month', 
          CONVERT(CHAR(10), moveindate, 101) 'Move In Date', 
          ISNULL(CONVERT(CHAR(10), moveoutdate, 101) , 'None') 'Move Out Date', 
          RTRIM(firstname) + ' ' + RTRIM(lastname)as Name,
          [Id],
          Siteid
FROM     ResidentInfo RI
     JOIN Dates D ON
          D.SqlDate BETWEEN RI.MoveinDate AND ISNULL(RI.MoveOutDate, getdate())
WHERE     Siteid = CASE WHEN @SiteId = -1 THEN SiteId ELSE @SiteId END AND
          [Id] = CASE WHEN @ResidentId = -1 THEN [Id] ELSE @ResidentId END
GROUP BY YEAR(SqlDate),
          MONTH(SqlDate), 
        Moveindate, 
        Moveoutdate, 
        Firstname, 
        Lastname,
          [Id],
          Siteid
ORDER BY 6, 2, 3

GO

Request for Question Clarification by mathtalk-ga on 03 Dec 2003 14:53 PST
Hi, ttg-ga:

Conceptually you could do this sort of thing, which of course solves
the problem with a simple "group by" query.  But the database engine
would need to scan an exhaustive Dates table that physically realizes
records for all the potential days of interest, and that seems a
little wasteful of both computation and storage.

I have in mind a stored procedure with a user-defined function that
churns out records into a temp table.  But I don't think you ever
pinned down which DBMS you are using...

regards, mathtalk-ga

Clarification of Question by ttg-ga on 03 Dec 2003 15:45 PST
Hello there,
We are running SQL Server 7.0. I am very interested in your idea.

Request for Question Clarification by mathtalk-ga on 03 Dec 2003 18:16 PST
Hi, ttg-ga:

Sorry, should have seen that in your first "clarification".  SQL
Server 7, alas, doesn't support user-defined functions.  But that's
minor.  I'll work up a prototype stored proc on my SQL Server 2000
without any user-defined function, and that should be backwards
compatible.

regards, mathtalk-ga

Clarification of Question by ttg-ga on 03 Dec 2003 21:53 PST
No problem! If I could get the same results as the current info,
without the dates table that would be wonderful! I will give a bonus
too. Thx!
Answer  
There is no answer at this time.

Comments  
Subject: Re: DateDiff SQL Feature
From: mathtalk-ga on 01 Jan 2004 08:46 PST
 
Hi, ttg-ga:

With the conflicts of my work and holiday schedules (and lack of
coffee!), I haven't been able to polish my answer for you before this
question expires.  So I'm posting a Comment now, which will allow me
to provide a tested solution for you as another Comment shortly.

At one point you had requested that "Year" be included as a parameter
to the stored procedure (see your Dec. 2nd Clarification) along with
(resident) id.

I'm adding "Year" as an argument and defaulting it and the other two
to Null, with the interpretation that if an argument is missing, the
request is for "all" records.  In the case of "Year" this would mean
going back to the earliest "moveindate", although some of your early
Clarifications might point to defaulting to only the "current" year.

Incidentally the default (SQL-92) behavior in MS SQL Server for quoted
identifiers is to use double quotes for them and single quotes for
string literals, see:

[MS SQL Server - SET QUOTED_IDENTIFIER (Transact-SQL)]
http://msdn.microsoft.com/library/en-us/tsqlref/ts_set-set_9jxu.asp


regards, mathtalk-ga
Subject: Re: DateDiff SQL Feature
From: mathtalk-ga on 04 Jan 2004 13:29 PST
 
Hi, ttg-ga:

I have naturally made a few design decisions in the course of writing
the stored procedure below.  I changed the defaults to arguments to be
Null's as this is a more familiar practice for me.  Thus my version of
GetResidentDays takes three parameters, @ResidentId, @SiteId, and
@Year, any of which limit the record set if specified as non-Null, but
otherwise do not limit the results.

First the test data I used:

CREATE table ResidentInfo
(
    id              int         not Null,
    firstname       varchar(20) not Null,
    lastname        varchar(35) not Null,
    moveindate      datetime    not Null,
    moveoutdate     datetime    Null,
    active          numeric     not Null default 1, -- active
    siteid          numeric     not Null
)
go

SELECT count(*) from ResidentInfo
go

insert into ResidentInfo values
(1,'John','Doe','1/1/2003','3/31/2003',1,1)
go

insert into ResidentInfo values
(2,'Don','Brown','1/1/2003','3/15/2003',1,2)
go

It seems a little unusual to me to use the numeric datatype for the
last two fields, esp. if active can only be 0 or 1.  But that's how
you described the existing table, so I went with it.

Notice that in describing your counts for the two test cases above,
you cited 16 days for Don Brown in March 2003.  This should be 15
days.

From your examples, however, I concluded that you mean to include both
the move-in and move-out dates in the overall counts.  The code below
reflects this assumption.

I didn't need to code anything special for leap years because I'm
relying only on the datediff function in Transact-SQL to provide
counts.  However one of the awkward things in SQL is to piece together
a date from its "parts".  Although datepart will extract the month,
day, year, etc. given a valid datetime, there is no system provided
inverse function.  Here the one "error handling" snippet that I wrote
tries to validate that the glueing together of a month and year to
make a first of a month has gone well, or to exit before launching
into the main loop which depends critically on the first of a month
value for termination.

The trickiest logic concerns counting the last day (moveoutdate) in
the right month, but I'll let you look at how I've written the code. 
You can ask if you have questions about it.  I tested this by updating
the ResidentInfo table to add one more day to moveoutdate.

Finally I tested the handling of Null in moveoutdate by setting both
records to have this.  The results returned roll forward through the
current month (Jan. 2004 at this time) unless you specify a year.  If
you specify a year it assumes you want all twelve months of that year
(which projects quite a bit into the future for 2004 if you use that
as an input argument).

I think that about covers the assumptions I made that are worth
reviewing by you in the context of your business needs.  Some of what
you want to do in formatting a report is best managed with a reporting
tool, like Crystal Reports.  That is, while one could construct a lot
of PRINT or SELECT statements to output a report with subtotals
directly from SQL, the language is not designed to make this easy. 
But reporting tools should handle that function nicely.  Let me know
if you are unsure what I'm referring to as a reporting tool.

Okay, here's the code to DROP and CREATE the stored procedure,
followed by a few of the artifacts of my testing.

regards, mathtalk-ga

/*

[MS SQL Server - SET QUOTED_IDENTIFIER (Transact-SQL)]
http://msdn.microsoft.com/library/en-us/tsqlref/ts_set-set_9jxu.asp

*/

DROP proc GetResidentDays
go

CREATE proc GetResidentDays 

    @ResidentId     int = Null,
    @SiteId         int = Null,
    @Year           int = Null

as

CREATE table #ResidentDays
(
    ResidentId      int not Null,
    "Year"          int not Null,
    "Month"         int not Null,
    ResidentDays    int not Null,

    constraint PK_ResDays primary key
    (
        ResidentID,
        "Year",
        "Month"
    )
)

set nocount on

declare @thisYear   int,
        @thisMonth  int,
        @this1stOf  datetime,
        @next1stOf  datetime,
        @stopYear   int,
        @stopMonth  int

-- Find the earliest month to start on
-- and the latest month (to stop with)

-- As written the counts will run through
-- the full end of the current month for
-- residents whose moveoutdate is Null

if ( @Year is Null )
begin
  select @thisYear  = datePart(year,  min(moveindate)),
         @thisMonth = datePart(month, min(moveindate))
  from  ResidentInfo
  where ( @ResidentId is Null
       OR id = @ResidentId
        )
  and   ( @SiteId     is Null
       OR siteid = @SiteId
        )
  and   active = 1

  select @stopYear  = datePart(year,  getdate( )),
         @stopMonth = datePart(month, getdate( ))

end
else
  select @thisYear  = @Year,
         @thisMonth = 1, -- set to beginning of year
         @stopYear  = @Year,
         @stopMonth = 12    -- set to end of year

  select @this1stOf = convert( datetime,
                               convert(varchar,@thisMonth)
                             + '/1/'
                             + convert(varchar,@thisYear)
                             )

-- The idea is to loop through the months
-- and add rows to #ResidentDays as we go

-- A key point, deduced from the examples in
-- the Question & Clarification, is that both
-- a resident's movein and moveout days count
if 0 = ISDATE( convert(varchar,@thisMonth)
             + '/1/'
             + convert(varchar,@thisYear)
             )
begin 
  select @thisMonth, @thisYear, @this1stOf

  return 0

end

while ( @thisYear < @stopYear
     OR (     @thisYear  =  @stopYear
          and @thisMonth <= @stopMonth
        )
      )
begin
  select @next1stOf = dateadd(month, 1, @this1stOf)
  
  insert into #ResidentDays
  select id,
         @thisYear,
         @thisMonth,
         datediff(day,
                  case
                    when ( @this1stOf <= moveindate )
                    then moveindate
                    else @this1stOf
                  end,
                  case
                    when ( @next1stOf > moveoutdate )
                    then dateadd(day, 1, moveoutdate)
                                    -- counts a resident's last day
                    else @next1stOf -- catch if moveoutdate is Null
                  end
                 )
  from  ResidentInfo
  where ( @ResidentId is Null
       OR @ResidentId = id
        )
  and   ( @SiteId is Null
       OR @SiteId = siteid
        )
  and   moveindate  <  @next1stOf   -- limits records to residents
  and ( moveoutdate is Null
     OR moveoutdate >= @this1stOf   -- during thisMonth & thisYear
      )
  and   active = 1                  -- who are still marked active

  select @thisYear  = datepart(year,  @next1stOf),
         @thisMonth = datepart(month, @next1stOf),
         @this1stOf = @next1stOf

end

set nocount off

SELECT * from #ResidentDays

go

GetResidentDays Null, Null, 2004
go

update ResidentInfo
set    moveoutdate = dateadd(day, 1, moveoutdate)
go

update ResidentInfo
set    moveoutdate = Null
go

Important Disclaimer: Answers and comments provided on Google Answers are general information, and are not intended to substitute for informed professional medical, psychiatric, psychological, tax, legal, investment, accounting, or other professional advice. Google does not endorse, and expressly disclaims liability for any product, manufacturer, distributor, service or service provider mentioned or any opinion expressed in answers or comments. Please read carefully the Google Answers Terms of Service.

If you feel that you have found inappropriate content, please let us know by emailing us at answers-support@google.com with the question ID listed above. Thank you.
Search Google Answers for
Google Answers  


Google Home - Answers FAQ - Terms of Service - Privacy Policy