BriteSponge
BriteSponge

Reputation: 1054

Find consecutive dates spanning a weekend

I have a job list that indicates the work performed on any particular job. When work is done during the day then just one record is added and a work_type is included. Work is not performed on a weekend. Jobs can have work done over a long period of time with the odd day here and there but at some point in its lifecycle it should have a period of work where it is being worked on consistently. Our management would like to be able to highlight on a report any jobs where this longer period of work hasn't happened. There are some other conditions around type of work and the team name but the main sticking point is the time issue.

So ... how do I find jobs that have not had a period of at least two consecutive weeks (10 working days) consistent work performed ?

In the following, job 164353 will not be included as it has the necessary 10 consecutive days (ignoring weekends), while job 214325 will be flagged as there is a gap on the 9th that broke the sequence of consecutive days.

JOB_ID     W ACTION_DATE
---------- - -----------
164354     H 10-FEB-17
164354     H 13-FEB-17
164354     H 14-FEB-17
164354     H 15-FEB-17
164354     H 16-FEB-17
164354     H 17-FEB-17
164354     H 20-FEB-17
164354     H 21-FEB-17
164354     H 22-FEB-17
164354     H 23-FEB-17
164354     H 24-FEB-17

214325     H 01-MAR-17
214325     H 02-MAR-17
214325     H 03-MAR-17
214325     H 06-MAR-17
214325     H 07-MAR-17
214325     H 08-MAR-17
214325     H 10-MAR-17
214325     H 13-MAR-17
214325     H 14-MAR-17
214325     H 15-MAR-17

I have this query where I can produce consecutive groups with a number of days against each group but I am struggling to adapt it to span over the weekends. In other words the results below would ideally show a number of consecutive days of 10.

WITH  
  groups AS (
    SELECT
      ROW_NUMBER() OVER (ORDER BY action_date) AS rn,
      action_date -ROW_NUMBER() OVER (ORDER BY action_date) AS grp,
      action_date
    FROM test_job_list
    WHERE job_id = 164354
  )
SELECT count(*) AS num_consec_dates,
       min(action_date) AS earliest,
       max(action_date) AS latest
FROM groups
group by grp
ORDER BY num_consec_dates desc, earliest desc


NUM_CONSEC
DATES      EARLIEST  LATEST
---------- --------- ---------
         5 20-FEB-17 24-FEB-17
         5 13-FEB-17 17-FEB-17
         1 10-FEB-17 10-FEB-17

Upvotes: 4

Views: 1146

Answers (4)

Gordon Linoff
Gordon Linoff

Reputation: 1269773

10 days = 2 full weeks. For 11 days, you can look at the date 14 days ago and see if it is exactly two weeks ago:

select tjl.*,
       lag(action_date, 10) over (partition by job id order by action_date) as minad_2weeks
from test_job_list;

A simple trick works for 10 days:

Then you can get jobs with no such period by using aggregation:

select job_id
from (select tjl.*,
             lag(action_date, 9) over (partition by job_id order by action_date) as lag9_ad
      from test_job_list tjl
     ) tjl
group by job_id
having max(action_date - lag9_ad) > action_date - 14;

That is, if the 9th date back is within the past two weeks, then there are two full weeks of dates.

Upvotes: 0

MT0
MT0

Reputation: 167982

You can determine which day of the week it is using (monday = 0, sunday = 6):

TRUNC( action_date ) - TRUNC( action_date, 'IW' )

And, using the LAG analytic function you can then compare whether the previous entry is the previous working day and use this to determine the group:

Oracle Setup:

CREATE TABLE test_job_list ( JOB_ID,     W, ACTION_DATE ) AS
SELECT 164354, 'H', DATE '2017-02-10' FROM DUAL UNION ALL
SELECT 164354, 'H', DATE '2017-02-13' FROM DUAL UNION ALL
SELECT 164354, 'H', DATE '2017-02-14' FROM DUAL UNION ALL
SELECT 164354, 'H', DATE '2017-02-15' FROM DUAL UNION ALL
SELECT 164354, 'H', DATE '2017-02-16' FROM DUAL UNION ALL
SELECT 164354, 'H', DATE '2017-02-17' FROM DUAL UNION ALL
SELECT 164354, 'H', DATE '2017-02-20' FROM DUAL UNION ALL
SELECT 164354, 'H', DATE '2017-02-21' FROM DUAL UNION ALL
SELECT 164354, 'H', DATE '2017-02-22' FROM DUAL UNION ALL
SELECT 164354, 'H', DATE '2017-02-23' FROM DUAL UNION ALL
SELECT 164354, 'H', DATE '2017-02-24' FROM DUAL UNION ALL
SELECT 214325, 'H', DATE '2017-03-01' FROM DUAL UNION ALL
SELECT 214325, 'H', DATE '2017-03-02' FROM DUAL UNION ALL
SELECT 214325, 'H', DATE '2017-03-03' FROM DUAL UNION ALL
SELECT 214325, 'H', DATE '2017-03-06' FROM DUAL UNION ALL
SELECT 214325, 'H', DATE '2017-03-07' FROM DUAL UNION ALL
SELECT 214325, 'H', DATE '2017-03-08' FROM DUAL UNION ALL
SELECT 214325, 'H', DATE '2017-03-10' FROM DUAL UNION ALL
SELECT 214325, 'H', DATE '2017-03-13' FROM DUAL UNION ALL
SELECT 214325, 'H', DATE '2017-03-14' FROM DUAL UNION ALL
SELECT 214325, 'H', DATE '2017-03-15' FROM DUAL;

Query:

SELECT job_id,
       MIN( action_date ) AS start_date,
       MAX( action_date ) AS end_date,
       COUNT( 1 ) AS num_days
FROM   (
  SELECT job_id,
         action_date,
         SUM( has_changed_group ) OVER ( PARTITION BY job_id ORDER BY action_date )
           AS group_id
  FROM   (
    SELECT job_id,
           action_date,
           CASE WHEN
             LAG( action_date ) OVER ( PARTITION BY job_id ORDER BY action_date )
             = action_date - CASE TRUNC( action_date ) - TRUNC( action_date, 'IW' )
                             WHEN 0 THEN 3 ELSE 1 END
             THEN 0
             ELSE 1
           END AS has_changed_group
    FROM   test_job_list
  )
)
GROUP BY job_id, group_id
-- HAVING COUNT(1) >= 10;

Output:

    JOB_ID START_DATE          END_DATE              NUM_DAYS
---------- ------------------- ------------------- ----------
    164354 2017-02-10 00:00:00 2017-02-24 00:00:00         11
    214325 2017-03-10 00:00:00 2017-03-15 00:00:00          4
    214325 2017-03-01 00:00:00 2017-03-08 00:00:00          6

Alternative:

If you just want the jobs where there has never been a period of 10 consecutive working days then you can use the COUNT() analytic function and specify a RANGE window:

SELECT job_id
FROM   (
  SELECT job_id,
         COUNT( 1 ) OVER ( PARTITION BY job_id
                           ORDER BY action_date
                           RANGE BETWEEN INTERVAL '13' DAY PRECEDING
                                 AND     INTERVAL  '0' DAY FOLLOWING )
           AS num_days
  FROM   test_job_list
)
GROUP BY job_id
HAVING MAX( num_days ) < 10;

Output:

    JOB_ID
----------
    214325

Upvotes: 2

Sedat.Turan
Sedat.Turan

Reputation: 1

I know this solution too long , but you can see all details on query by executing step by step

create table calendar1 as
select day_id,WEEK_DAY_SHORT,day_num_of_week  from  VITDWH.DW_MIS_TAKVIM as calendar order by day_id;

CREATE TABLE JOB_LIST (JOB_ID NUMBER,ACTION_DATE DATE);

INSERT INTO JOB_LIST VALUES(164354,TO_DATE('10-FEB-17','DD-MON-YY'));
INSERT INTO JOB_LIST VALUES(164354,TO_DATE('13-FEB-17','DD-MON-YY'));
INSERT INTO JOB_LIST VALUES(164354,TO_DATE('14-FEB-17','DD-MON-YY'));
INSERT INTO JOB_LIST VALUES(164354,TO_DATE('15-FEB-17','DD-MON-YY'));
INSERT INTO JOB_LIST VALUES(164354,TO_DATE('16-FEB-17','DD-MON-YY'));
INSERT INTO JOB_LIST VALUES(164354,TO_DATE('17-FEB-17','DD-MON-YY'));
INSERT INTO JOB_LIST VALUES(164354,TO_DATE('20-FEB-17','DD-MON-YY'));
INSERT INTO JOB_LIST VALUES(164354,TO_DATE('21-FEB-17','DD-MON-YY'));
INSERT INTO JOB_LIST VALUES(164354,TO_DATE('22-FEB-17','DD-MON-YY'));
INSERT INTO JOB_LIST VALUES(164354,TO_DATE('23-FEB-17','DD-MON-YY'));
INSERT INTO JOB_LIST VALUES(164354,TO_DATE('24-FEB-17','DD-MON-YY'));
INSERT INTO JOB_LIST VALUES(214325,TO_DATE('01-MAR-17','DD-MON-YY'));
INSERT INTO JOB_LIST VALUES(214325,TO_DATE('02-MAR-17','DD-MON-YY'));
INSERT INTO JOB_LIST VALUES(214325,TO_DATE('03-MAR-17','DD-MON-YY'));
INSERT INTO JOB_LIST VALUES(214325,TO_DATE('06-MAR-17','DD-MON-YY'));
INSERT INTO JOB_LIST VALUES(214325,TO_DATE('07-MAR-17','DD-MON-YY'));
INSERT INTO JOB_LIST VALUES(214325,TO_DATE('08-MAR-17','DD-MON-YY'));
INSERT INTO JOB_LIST VALUES(214325,TO_DATE('10-MAR-17','DD-MON-YY'));
INSERT INTO JOB_LIST VALUES(214325,TO_DATE('13-MAR-17','DD-MON-YY'));
INSERT INTO JOB_LIST VALUES(214325,TO_DATE('14-MAR-17','DD-MON-YY'));
INSERT INTO JOB_LIST VALUES(214325,TO_DATE('15-MAR-17','DD-MON-YY'));

COMMIT;

with a1 as 
(
select A.JOB_ID,A.ACTION_DATE,B.DAY_ID,
       (case when action_date is not null and lag(action_date) over(partition by job_id order by day_id) is null then action_date else null end) start_date,
       (case when action_date is not null and lead(action_date) over(partition by job_id order by day_id) is  null then action_date else null end) max_date 
  from
(
select * from  calendar1 
 WHERE DAY_ID >=(select MIN(ACTION_DATE) from  JOB_LIST) 
   AND DAY_ID <= (select MAX(ACTION_DATE) from  JOB_LIST)
ORDER BY DAY_ID   
)
B  LEFT OUTER JOIN
 JOB_LIST A 
 PARTITION BY (A.JOB_ID) ON (A.ACTION_DATE= B.DAY_ID) 
ORDER BY A.JOB_ID,DAY_ID
)
,a2 as 
(
select * from  a1 where start_date is not null or max_date is not null
)
,a3 as 
(
select a2.*,lead(max_date) over(partition by job_id order by day_id)  end_date
 from  a2
)
select a.job_id,a.start_date,nvl(a.maX_date,a.end_date) end_date, (nvl(a.maX_date,a.end_date) -a.start_date) +1 date_count 
 from a3 a where start_date is not null;

Upvotes: 0

Stefano Zanini
Stefano Zanini

Reputation: 5916

Edit 2

First version had many issues, this one should work.

An option is to join the table with itself on the job_id, and filtering on the right side only the rows of the two weeks preceding the date on the left side. Then you can count the reimaining dates.

select  JOB_ID
from    (
            select  g1.JOB_ID, count(g2.ACTION_DATE) CNT
            from    GROUPS g1
            join    GROUPS g2
            on      g1.JOB_ID = g2.JOB_ID
            where   g2.ACTION_DATE between g1.ACTION_DATE - 13 and g1.ACTION_DATE
            group by g1.JOB_ID, g1.ACTION_DATE
        ) t1
group by JOB_ID
having   max(CNT) < 10

Upvotes: 1

Related Questions