Home > Software engineering >  Oracle query to find the first or second row
Oracle query to find the first or second row

Time:11-19

I have a set of data with records of people and their terms. I am only concerned with terms 201030, 201040, & 201110. All others will be ignored. Some people will have several records, and some will only have one. I am writing a query to grab the first term for each person. If that 1st term is 201030, it should look at the next row. I need to return the term of the 2nd row if it is either 201040 or 201110, otherwise just return 201030. Return the 1st row term if it is either 201040 or 201110. This is a bit confusing so I tried to come with an image with the different scenarios. The green boxes indicate the record I should keep.

Here is the query I have so far. It runs but does not apply the case logic. It just returns the 1st term. How should I change this query?

select person_id, 
       min(term) as min_term,  -- including this field to verify the 1st term before changes
       case 
          when term = '201030' 
          then lead(min(term), 1, min(term)) over (order by min(term))
          else term
       end as term
from my_table 
group by person_id

enter image description here

CodePudding user response:

Here's one option. Read comments within code.

SQL> with test (person_id, term) as
  2    -- sample data
  3    (select 53303, 201030 from dual union all
  4     select 53303, 201040 from dual union all
  5     select 53303, 201110 from dual union all
  6     select 53303, 201140 from dual union all
  7     --
  8     select 14627, 201030 from dual union all
  9     select 14627, 201110 from dual union all
 10     select 14627, 201510 from dual union all
 11     --
 12     select 14702, 201030 from dual union all
 13     --
 14     select 28103, 201030 from dual union all
 15     select 28103, 201230 from dual union all
 16     select 28103, 201240 from dual union all
 17     select 28103, 201310 from dual union all
 18     --
 19     select 33634, 201040 from dual union all
 20     select 33634, 201110 from dual union all
 21     select 33634, 201130 from dual union all
 22     --
 23     select 32356, 201510 from dual
 24    ),

 25  rns as
 26    -- select next term and row numbers
 27    (select person_id,
 28       term,
 29       lead(term) over (partition by person_id order by term) next_term,
 30       row_number() over (partition by person_id order by term) rn
 31     from test
 32    )
 33  select person_id,
 34    max(case when rn = 1 and term = 201030 then
 35               case when next_term in (201040, 201110) then next_term
 36                    else term
 37               end
 38             when rn = 1 and term in (201040, 201110) then term
 39        end) result
 40  from rns
 41  group by person_id;

 PERSON_ID     RESULT
---------- ----------
     14702     201030
     32356
     33634     201040
     14627     201110
     28103     201030
     53303     201040

6 rows selected.

SQL>

CodePudding user response:

From Oracle 12, your can use MATCH_RECOGNIZE:

SELECT *
FROM   (
  SELECT *
  FROM   my_table
  WHERE  term IN (201030, 201040, 201110)
)
MATCH_RECOGNIZE (
  PARTITION BY person_id
  ORDER     BY term_order
  ALL ROWS PER MATCH
  PATTERN ( ^ {- IS30? -} ANY_ROW )
  DEFINE
    IS30 AS term = 201030
)

Which, for the sample data:

CREATE TABLE my_table (person_id, term, term_order) As
  SELECT 14627, 201030, 1 FROM DUAL UNION ALL -- Not this one
  SELECT 14627, 201110, 2 FROM DUAL UNION ALL -- This one
  SELECT 14627, 201510, 3 FROM DUAL UNION ALL -- Ignore this
  SELECT 14702, 201030, 1 FROM DUAL UNION ALL -- This one
  SELECT 28103, 201030, 1 FROM DUAL UNION ALL -- This one
  SELECT 28103, 201230, 2 FROM DUAL UNION ALL -- Ignore this
  SELECT 28103, 201240, 3 FROM DUAL UNION ALL -- Ignore this
  SELECT 28103, 201310, 4 FROM DUAL UNION ALL -- Ignore this
  SELECT 33634, 201040, 1 FROM DUAL UNION ALL -- This one
  SELECT 33634, 201110, 2 FROM DUAL UNION ALL -- Not this one
  SELECT 33634, 201130, 3 FROM DUAL UNION ALL -- Ignore this
  SELECT 32356, 201510, 1 FROM DUAL UNION ALL -- Ignore this
  SELECT 53303, 201030, 1 FROM DUAL UNION ALL -- Not this one
  SELECT 53303, 201040, 2 FROM DUAL UNION ALL -- This one.
  SELECT 53303, 201110, 3 FROM DUAL UNION ALL -- Not this one
  SELECT 53303, 201140, 4 FROM DUAL;          -- Ignore this

Note: In SQL, tables are unordered. If you want them to be in a specific order then you need to have something with which to apply that order (such as the task_order column) and to use an ORDER BY clause.

Outputs:

PERSON_ID TERM_ORDER TERM
14627 2 201110
14702 1 201030
28103 1 201030
33634 1 201040
53303 2 201040

db<>fiddle here

  • Related