Find missing values in the student table Find missing values in the student table oracle oracle

Find missing values in the student table


Create a CTE that returns the years that you search for and another one that returns the distinct student ids.
Cross join them and left join your table and then filter out all the matching rows.
The remaining rows contain all the years each student has not paid.
Then aggregate and use LISTAGG() to collect all the years for each student:

WITH   years(year) AS (    SELECT 2018 FROM dual UNION ALL    SELECT 2019 FROM dual UNION ALL    SELECT 2020 FROM dual UNION ALL    SELECT 2021 FROM dual  ),  students(Student_ID) AS (SELECT DISTINCT Student_ID FROM tablename)SELECT s.Student_ID,       LISTAGG(y.year, ',') WITHIN GROUP (ORDER BY y.year) Year_of_DefaultFROM years y CROSS JOIN students sLEFT JOIN tablename tON t.Year_of_paid_fee = y.year AND t.Student_ID = s.Student_IDWHERE t.Student_ID IS NULLGROUP BY s.Student_ID

See the demo.


If your Oracle version is 19c or later then use Query#1 or for older version use Query#2.

 CREATE TABLE mytable (Student_ID int, Year_of_paid_fee int);  Insert INTO mytable VALUES (101, 2018); Insert INTO mytable VALUES (101, 2019); Insert INTO mytable VALUES (101, 2021); Insert INTO mytable VALUES (102, 2018); Insert INTO mytable VALUES (102, 2021);

Query#1 (For Oracle 19c and later)

 with allyears as (     select 2018 yr from dual     union all     select 2019 from dual     union all     select 2020 from dual     union all     select 2021 from dual )  select  mt.student_id , LISTAGG(distinct yr, ',') WITHIN GROUP (ORDER BY yr)  Year_of_Default from mytable mt cross join allyears  where not exists(select * from mytable m WHERE  mt.student_id=m.student_id and yr=m.YEAR_OF_PAID_FEE)  group by mt.student_id 

Query#2 (For older version of oracle than 19c)

 with allyears as (     select 2018 yr from dual     union all     select 2019 from dual     union all     select 2020 from dual     union all     select 2021 from dual ) ,cte as  (    select distinct  mt.student_id ,  yr    from mytable mt cross join allyears     where not exists(select * from mytable m WHERE  mt.student_id=m.student_id and yr=m.YEAR_OF_PAID_FEE)  ) select student_id, LISTAGG(yr,',') WITHIN GROUP (ORDER BY yr)  Year_of_Default from cte  group by student_id 

Output:

STUDENT_IDYEAR_OF_DEFAULT
1012020
1022019,2020

db<fiddle here


One option would be using MINUS operator in order to subtract the year values of the table from the set of years (2018..2021) for each student such as

SELECT Student_ID,        LISTAGG(Year,',') WITHIN GROUP (ORDER BY Student_ID) AS Year_of_Default  FROM (         SELECT DISTINCT Student_ID, level + 2017 AS Year           FROM t        CONNECT BY level <= 4           MINUS         SELECT Student_ID,Year           FROM t        )  GROUP BY Student_ID 

Demo