1

My table has start_date and end_date from which I need to find the hour difference. The issue is that both of these date times are not on the same day.

user    start_date  end_date    difference
Alex    7/25/2016 16:00 7/26/2016 0:30  8.5
Alex    7/24/2016 16:00 7/25/2016 0:30  8.5
Alex    7/21/2016 16:00 7/22/2016 0:30  8.5
Alex    7/20/2016 16:00 7/21/2016 0:30  8.5
Alex    7/19/2016 16:00 7/20/2016 0:30  8.5
Alex    7/18/2016 16:00 7/19/2016 0:30  8.5
Alex    7/17/2016 16:00 7/18/2016 0:30  8.5
Alex    7/14/2016 16:00 7/15/2016 0:30  8.5
Alex    7/13/2016 16:00 7/14/2016 0:30  8.5
Alex    7/12/2016 16:00 7/13/2016 0:30  8.5
Alex    7/11/2016 16:00 7/12/2016 0:30  8.5
Alex    7/10/2016 16:00 7/11/2016 0:30  8.5

Usually it is 5 working days and I get the answer if I group them by start_date. But I need an new date column where I need the output as below. Please note that 15/7/2016 and 22/7/2016 was not present in the above table. I need the additional 0.5 hour & date for the 6th day to be included to my derived table.

User    Date    difference
Alex    7/25/2016   8.5
Alex    7/24/2016   8.5
Alex    7/22/2016   0.5
Alex    7/21/2016   8.0
Alex    7/20/2016   8.5
Alex    7/19/2016   8.5
Alex    7/18/2016   8.5
Alex    7/17/2016   8.5
Alex    7/15/2016   0.5
Alex    7/14/2016   8.0
Alex    7/13/2016   8.5
Alex    7/12/2016   8.5
Alex    7/11/2016   8.5
Alex    7/10/2016   8.5

I calculate the difference as

round(cast(datediff(seconds, start_date, end_date) as decimal)/3600,2)
3
  • Can you clarify the pattern behind your desired calculation? Why is the 14th and the 21st split up into 8.0 for the current and 0.5 for the following day? Is this always the case for Thursdays? Or rather when the following day does not have a start_date of its own? Commented Jul 29, 2016 at 14:53
  • Can you clarify what you are trying to achieve? Are you saying that you want to know the number of hours between start & end that were 'consumed' on each day, broken down by that day? For example, Monday 10pm - Wednesday 3am would be 2 hours for Monday, 24 hours for Tuesday and 3 hours for Wednesday? Commented Jul 31, 2016 at 8:47
  • So the logic behind this is, i would need to break the total number of hours per day. Ex: If start_date = 1/1/2016 15:30 and end_date = 2/1/2016 00:30 then I need the break up with group by start_date: 8+0.5 = 8.5 for 1/1/2016. this continues till the end of the week. But on the last day of the week (day Friday), the total number will reflect as 8.5 which is the correct calculation. However, what I need is to split the additional 0.5 hour (which comes in Saturday) to be published on Saturday (i.e group by end_date) Commented Aug 1, 2016 at 8:05

2 Answers 2

1

Whenever there is sophisticated logic, I'd suggest to use union queries and split the logic into a select query (or even table) each. Then you'd be able to calculate this in two steps. The main difference seems to be whether the 0.5 between 00:00:00 and 00:30:00 should be counted to the previous workday or whether it should stand alone. The latter seems to be determined based on whether the end_date is also a workday itself. I see three cases:

  • Next day is a workday:
    1. Report all hours on start_date
  • Next day is not a workday:
    1. Report hours from start_date to midnight on start_date
    2. Report hours from midnight to end_date on end_date

I used the following example data based on your description:

create temporary table _test (user varchar(20), start_date timestamp, end_date timestamp);
insert into _test values ('Alex', '7/25/2016 16:00', '7/26/2016 0:30'), ('Alex', '7/24/2016 16:00', '7/25/2016 0:30'), ('Alex', '7/21/2016 16:00', '7/22/2016 0:30'), ('Alex', '7/20/2016 16:00', '7/21/2016 0:30'), ('Alex', '7/19/2016 16:00', '7/20/2016 0:30'), ('Alex', '7/18/2016 16:00', '7/19/2016 0:30'), ('Alex', '7/17/2016 16:00', '7/18/2016 0:30'), ('Alex', '7/14/2016 16:00', '7/15/2016 0:30'), ('Alex', '7/13/2016 16:00', '7/14/2016 0:30'), ('Alex', '7/12/2016 16:00', '7/13/2016 0:30'), ('Alex', '7/11/2016 16:00', '7/12/2016 0:30'), ('Alex', '7/10/2016 16:00', '7/11/2016 0:30');

We will need to know whether the next day is a workday, so I suggest using the lead() window function (see documentation) which will give you the start_date from the next row.

create temporary table _differences as (
    select
        user_name
      , start_date::date as start_date
      , end_date::date as end_date
       /** 
        * Calculate difference in hours between start_date and end_date: */
      , round(cast(datediff(seconds, start_date, end_date) as decimal)/3600,2) as hours_start_to_end
       /** 
        * Calculate difference in hours between start_date and midnight: */
      , round(cast(datediff(seconds, start_date, dateadd(day, 1, start_date::date)) as decimal)/3600,2) as hours_start_to_midnight
       /**
        * Calculate difference between midnight on end_date and end_date: */
      , round(cast(datediff(seconds, end_date::date, end_date) as decimal)/3600,2) as hours_midnight_to_end
       /**
        * Calculate number of days from end_date until next start_date: */
      , datediff(day, end_date::date, lead(start_date::date) over(partition by user_name order by start_date::date)) as days_until_next_workday
    from
        _test
);

Then the following query:

    select
        user_name          as user_name
      , start_date         as ref_date
      , hours_start_to_end as difference
    from
        _differences
    where
        days_until_next_workday = 0 -- report all work hours on start_date
union
    select
        user_name               as user_name
      , start_date              as ref_date
      , hours_start_to_midnight as difference
    from
        _differences
    where
        days_until_next_workday > 0 -- report partial work hours on start_date
union
    select
        user_name             as user_name
      , end_date              as ref_date
      , hours_midnight_to_end as difference
    from
        _differences
    where
        days_until_next_workday > 0 -- report partial work hours on end_date
order by
    user_name
  , ref_date desc
;

Would yield the following result:

 user_name |  ref_date  | difference
-----------+------------+------------
 Alex      | 2016-07-24 |       8.50
 Alex      | 2016-07-22 |       0.50
 Alex      | 2016-07-21 |       8.00
 Alex      | 2016-07-20 |       8.50
 Alex      | 2016-07-19 |       8.50
 Alex      | 2016-07-18 |       8.50
 Alex      | 2016-07-17 |       8.50
 Alex      | 2016-07-15 |       0.50
 Alex      | 2016-07-14 |       8.00
 Alex      | 2016-07-13 |       8.50
 Alex      | 2016-07-12 |       8.50
 Alex      | 2016-07-11 |       8.50
 Alex      | 2016-07-10 |       8.50
(13 rows)

You can see that 7/25/2016 is missing because there is no start_date on or after 7/26/2016, so you'll need to figure out how to account for that special case.

Sign up to request clarification or add additional context in comments.

2 Comments

Perfect, actually I liked the use of union here. I am using like below, case when trunc(start_time) <> trunc(end_time) then cast(to_char(start_time,'yyyy-mm-dd 23:59:59') as timestamp) else cast(to_char(end_time,'yyyy-mm-dd hh24:mi:ss') as timestamp) end as st_t1
union case when trunc(start_time) <> trunc(end_time) then cast(to_char(end_time,'yyyy-mm-dd 00:00:00') as timestamp) else cast(to_char(end_time,'yyyy-mm-dd hh24:mi:ss') as timestamp) end as st_t2
1

here is how I have done the calc and it works perfectly

select user, trunc(start_time) as date1, 
       SUM(case when id = 1 then round(cast(datediff(seconds, start_time, st_t1) as decimal)/3600,2) end) as SCHEDULE

from
(
select user, start_time,
       case when trunc(start_time) <> trunc(end_time) then cast(to_char(start_time,'yyyy-mm-dd 23:59:59') as timestamp) else cast(to_char(end_time,'yyyy-mm-dd hh24:mi:ss') as timestamp) end as st_t1
from   table1 a
where id = 1
group by user_name, trunc(start_time)

union

select user_name, trunc(end_time) as date1,
       SUM(case when id = 1 then round(cast(datediff(seconds, st_t2, end_time) as decimal)/3600,2) end) as SCHEDULE

from
(
select user_name, end_time, 
       case when trunc(start_time) <> trunc(end_time) then cast(to_char(end_time,'yyyy-mm-dd 00:00:00') as timestamp) else cast(to_char(end_time,'yyyy-mm-dd hh24:mi:ss') as timestamp) end as st_t2
from   table1 a
where  id = 1    
)
group by user, trunc(end_time)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.