Home > Software design >  Alternative to nested queries in CASE statements which are too slow for large tables
Alternative to nested queries in CASE statements which are too slow for large tables

Time:04-20

So I have a requirement that if a record exists that satisfies both condition A and B then it should be picked up, otherwise default record that satisfies condition B should be picked up.

For e.g., I have data like

Name Marks Year Sub
John 78 2019 Maths
Harry 90 2021 Maths
Mohammed 82 2020 English

My solution is

Select 
(
  CASE WHEN EXISTS
    (select 1 from table_t where Marks>=INPUT_MARKS and Sub=INPUT_SUB)
  THEN
    select Marks from table_t where Marks>=INPUT_MARKS and Sub=INPUT_SUB
  ELSE
    A.Marks
  END
) as Marks,
A.Year,
A.Name
from table_t A
where Sub=INPUT_SUB;

When INPUT_MARKS=90, INPUT_SUB=Maths

I should get 90, 2021, Harry but I get 90, 2019, John which is not a valid data

Clearly I have to apply CASE statements to year and Name as well but if there are thousands of records(as is in my case) the query is too slow.

Simple If Else doesn't work either as this is a sub query to a bigger query so there will be huge redundancy in code.

Please suggest a solution.

CodePudding user response:

You can use the RANK analytic function (then you do not have to query the same table multiple times with EXISTS or UNION ALL):

SELECT t.*,
       RANK() OVER (
         ORDER BY CASE WHEN marks >= 90 THEN 0 ELSE 1 END
       ) AS rnk
FROM   table_t t
WHERE  Sub='Maths'
ORDER BY rnk
FETCH FIRST ROW WITH TIES;

Which, for the sample data:

CREATE TABLE table_t (Name, Marks, Year, Sub) AS
SELECT 'John',     78, 2019, 'Maths' FROM DUAL UNION ALL
SELECT 'Harry',    90, 2021, 'Maths' FROM DUAL UNION ALL
SELECT 'Mohammed', 82, 2020, 'English' FROM DUAL;

Outputs:

NAME MARKS YEAR SUB RNK
Harry 90 2021 Maths 1

Or, for Oracle 11 and earlier:

SELECT name, marks, year, sub
FROM   (
  SELECT t.*,
         RANK() OVER (
           ORDER BY CASE WHEN marks >= 90 THEN 0 ELSE 1 END
         ) AS rnk
  FROM   table_t t
  WHERE  Sub='Maths'
  ORDER BY rnk
)
WHERE  rnk = 1;

db<>fiddle here

CodePudding user response:

A UNION could work. Test out this one:

WITH dat AS
(
  SELECT 'John'  AS Name
       , 78      AS Marks
       , 2019    AS Year
       , 'Maths' AS Sub
    FROM dual UNION ALL
  SELECT 'Harry'   ,  90, 2021, 'Maths'   FROM dual UNION ALL
  SELECT 'Mohammed',  82, 2020, 'English' FROM dual
)
SELECT Marks
     , YEAR
     , NAME
  FROM dat mrks
 WHERE sub   =  '&INPUT_SUB'
   AND marks >= &INPUT_MARKS
 UNION ALL
SELECT marks
     , YEAR
     , NAME 
  FROM dat mrks
 WHERE sub   =  '&INPUT_SUB'
   AND NOT EXISTS (SELECT 1
                     FROM dat 
                    WHERE marks >= &INPUT_MARKS
                      AND sub   =  '&INPUT_SUB');
  • Related