1

I have following table with ID and DATE

ID   DATE
123   7/1/2015
123   6/1/2015
123   5/1/2015
123   4/1/2015
123   9/1/2014
123   8/1/2014
123   7/1/2014
123   6/1/2014
456   11/1/2014
456   10/1/2014
456   9/1/2014
456   8/1/2014
456   5/1/2014
456   4/1/2014
456   3/1/2014
789   9/1/2014
789   8/1/2014
789   7/1/2014
789   6/1/2014
789   5/1/2014
789   4/1/2014
789   3/1/2014

In this table, I have three customer ids, 123, 456, 789 and date column which shows which month they worked.

I want to find out which of the customers have gap in their work. Our customers work record is kept per month...so, dates are monthly.. and each customer have different start and end dates.

Expected results:

ID     First_Absent_date

123    10/01/2014
456    06/01/2014
3
  • I am thinking that one approach which could work is to compare the number of months between the max and min date, with the count of records (assuming there is only 1 record per month per customer) docs.oracle.com/cd/B19306_01/server.102/b14200/functions089.htm and docs.oracle.com/cd/B19306_01/server.102/b14200/functions032.htm Commented Apr 25, 2016 at 17:08
  • You only want to see the IDs which have any missing months; you don't want to see which months are missing? And you don't have a date range to check? Commented Apr 25, 2016 at 17:13
  • If you had a table of dates it would be easy. I don't have time to write a proper answer, but the term to search for is "sql numbers table". Commented Apr 25, 2016 at 17:14

4 Answers 4

1

To get a simple list of the IDs with gaps, with no further details, you need to look at each ID separately, and as @mikey suggested you can count the number of months and look at the first and last date to see if how many months that spans.

If your table has a column called month (since date isn't allowed unless it's a quoted identifier) you could start with:

select id, count(month), min(month), max(month),
  months_between(max(month), min(month)) + 1 as diff
from your_table
group by id
order by id;

        ID COUNT(MONTH) MIN(MONTH) MAX(MONTH)       DIFF
---------- ------------ ---------- ---------- ----------
       123            8 01-JUN-14  01-JUL-15          14
       456            7 01-MAR-14  01-NOV-14           9
       789            7 01-MAR-14  01-SEP-14           7

Then compare the count with the month span, in a having clause:

select id
from your_table
group by id
having count(month) != months_between(max(month), min(month)) + 1
order by id;

        ID
----------
       123
       456

If you can actually have multiple records in a month for an ID, and/or the date recorded might not be the start of the month, you can do a bit more work to normalise the dates:

select id,
  count(distinct trunc(month, 'MM')),
  min(trunc(month, 'MM')),
  max(trunc(month, 'MM')),
  months_between(max(trunc(month, 'MM')), min(trunc(month, 'MM'))) + 1 as diff
from your_table
group by id
order by id;

select id
from your_table
group by id
having count(distinct trunc(month, 'MM')) !=
  months_between(max(trunc(month, 'MM')), min(trunc(month, 'MM'))) + 1
order by id;
Sign up to request clarification or add additional context in comments.

1 Comment

I have one more request to add into it. I want to get very first date when customer become absent.
1

You could use a Lag() function to see if records have been skipped for a particular date or not.Lag() basically helps in comparing the data in current row with previous row. So if we order by DATE, we could easily compare and find any gaps.

select * from 
   (
    select ID,DATE_, case when DATE_DIFF>1 then 1  else 0 end comparison from
        (
          select ID, DATE_ ,DATE_-LAG(DATE_, 1) OVER (PARTITION BY ID ORDER BY DATE_)  date_diff from trial
        )
    )
    where comparison=1 order by ID,DATE_;

This groups all the entries by id, and then arranges the records by date. If a customer is always present, there would not be a gap in his date. So anyone who has a date difference greater than 1 had a gap. You could tweak this as per your requirement.

EDIT : Just observed that you are storing data in mm/dd/yyyy format, when I closely observed above answers.You are storing only first date of every month. So, the above query can be tweaked as :

select * from 
   (
    select ID,DATE_,PREV_DATE,last_day(PREV_DATE)+1 ABSENT_DATE, case when DATE_DIFF>31 then 1  else 0 end comparison from
        (
          select ID, DATE_ ,LAG(DATE_,1)  OVER (PARTITION BY ID ORDER BY DATE_)  PREV_DATE,DATE_-LAG(DATE_, 1) OVER (PARTITION BY ID ORDER BY DATE_)  date_diff from trial
        )
    )
    where comparison=1 order by ID,DATE_;

Comments

0

Oracle Setup:

CREATE TABLE your_table ( ID, "DATE" ) AS
SELECT 123, DATE '2015-07-01' FROM DUAL UNION ALL
SELECT 123, DATE '2015-06-01' FROM DUAL UNION ALL
SELECT 123, DATE '2015-05-01' FROM DUAL UNION ALL
SELECT 123, DATE '2015-04-01' FROM DUAL UNION ALL
SELECT 123, DATE '2014-09-01' FROM DUAL UNION ALL
SELECT 123, DATE '2014-08-01' FROM DUAL UNION ALL
SELECT 123, DATE '2014-07-01' FROM DUAL UNION ALL
SELECT 123, DATE '2014-06-01' FROM DUAL UNION ALL
SELECT 456, DATE '2014-11-01' FROM DUAL UNION ALL
SELECT 456, DATE '2014-10-01' FROM DUAL UNION ALL
SELECT 456, DATE '2014-09-01' FROM DUAL UNION ALL
SELECT 456, DATE '2014-08-01' FROM DUAL UNION ALL
SELECT 456, DATE '2014-05-01' FROM DUAL UNION ALL
SELECT 456, DATE '2014-04-01' FROM DUAL UNION ALL
SELECT 456, DATE '2014-03-01' FROM DUAL UNION ALL
SELECT 789, DATE '2014-09-01' FROM DUAL UNION ALL
SELECT 789, DATE '2014-08-01' FROM DUAL UNION ALL
SELECT 789, DATE '2014-07-01' FROM DUAL UNION ALL
SELECT 789, DATE '2014-06-01' FROM DUAL UNION ALL
SELECT 789, DATE '2014-05-01' FROM DUAL UNION ALL
SELECT 789, DATE '2014-04-01' FROM DUAL UNION ALL
SELECT 789, DATE '2014-03-01' FROM DUAL;

Query:

SELECT ID,
       MIN( missing_date )
FROM   (
  SELECT ID,
         CASE WHEN LEAD( "DATE" ) OVER ( PARTITION BY ID ORDER BY "DATE" )
                     = ADD_MONTHS( "DATE", 1 ) THEN NULL
              WHEN LEAD( "DATE" ) OVER ( PARTITION BY ID ORDER BY "DATE" )
                     IS NULL THEN NULL
              ELSE ADD_MONTHS( "DATE", 1 )
              END AS missing_date
  FROM   your_table
)
GROUP BY ID
HAVING COUNT( missing_date ) > 0;

Output:

        ID MIN(MISSING_DATE) 
---------- -------------------
       123 2014-10-01 00:00:00 
       456 2014-06-01 00:00:00 

Comments

0
    with 
    -- Unique dates in the source table
    unique_dates as(
      SELECT 
      distinct event_date
      FROM date_table
    ),
    -- Find the next available date for each date
    dates_lagged as (
      SELECT *
      , LAG(event_date,1) OVER (ORDER BY event_date DESC) as next_day
      FROM unique_dates
    )
    -- Gives you the beginning of each missing date timeframe and the number of days missing
    SELECT event_date
    , next_day
    , date_diff(next_day,event_date,DAY) as missing_days 
    FROM dates_lagged
    WHERE date_diff(next_day,event_date,DAY)!=1

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.