Search code examples
sqloracle-databaselag

Oracle Function LAG and order by


*Edit Question to reflect true output, see comments.

I have the below data, I need previous program.

TableA

StartDate    EndDate     Program     Id 
  1/26/15     2/23/15      Red       1    
  2/24/15     3/31/17      Yellow    1
  5/3/16       6/1/17      Silver    1
  4/1/17       1/31/18     Orange    1
  2/1/18                   Blue      1

MyOutput(incorrect)

StartDate    EndDate     Program      Prev_program   
  1/26/15     2/23/15      Red                    
  2/24/15     3/31/17      Yellow       Red
  5/3/16       6/1/17      Silver       Yellow
  4/1/17       1/31/18     Orange       Silver
  2/1/18                   Blue         Orange

ExpectedOutput:

StartDate    EndDate     Program      Prev_program   
  1/26/15     2/23/15      Red                    
  2/24/15     3/31/17      Yellow       Red
  5/3/16       6/1/17      Silver       Red
  4/1/17       1/31/18     Orange       Yellow
  2/1/18                   Blue         Orange

I would like to take the previous program when previous program end date is not greater than current startdate.

I used Lag which is producing results that I do not want. Lag is not taking into account "program end date is not greater than current startdate."

SELECT *
,LAG (PROGRAM, 1) OVER (PARTITION BY ID ORDER BY STARTDATE) AS PREV_PROGRAM
FROM TABLEA

Solution

  • Here is one way to do this. Not the most elegant or efficient, but it does the job. Two indexes, one on (id, startdate) and one on (id, enddate) may help with performance (worth testing, anyway). You are missing the id column in the output, but I assume it plays a role (and you want the processing to be done separately for each id). I wrote the query to work separately for each id, even though the test data has only one id.

    The with clause is not part of the query - I included it at the top instead of creating an actual table. You don't need it - start from SELECT a1.startdate...

    with
      table_a ( startdate, enddate, program, id ) as (
        select date '2015-01-26', date '2015-02-23', 'Red'   , 1 from dual union all    
        select date '2015-02-24', date '2017-03-31', 'Yellow', 1 from dual union all
        select date '2016-03-05', date '2017-06-01', 'Silver', 1 from dual union all
        select date '2017-04-01', date '2018-01-31', 'Orange', 1 from dual union all
        select date '2018-02-01', null             , 'Blue'  , 1 from dual
      )
    select   a1.startdate, a1.enddate, a1.program, a1.id,
             min(a2.program) keep (dense_rank last order by a2.startdate) as prev_program
    from     table_a a1 left outer join table_a a2 
                        on a1.id = a2.id and a1.startdate > a2.enddate
    group by a1.startdate, a1.enddate, a1.program, a1.id
    ;
    
    STARTDATE   ENDDATE     PROGRAM   ID  PREV_PROGRAM
    ----------  ----------  --------  --  ------------
    1/26/2015   2/23/2015   Red        1       
    2/24/2015   3/31/2017   Yellow     1  Red   
    3/5/2016    6/1/2017    Silver     1  Red   
    4/1/2017    1/31/2018   Orange     1  Yellow
    2/1/2018                Blue       1  Orange