Oracle. Select all if parameter is null else return specific item issue

后端 未结 3 1057
自闭症患者
自闭症患者 2021-01-26 05:43

I need to return all records if input parameter is null.

I\'ve written a simple query

declare  
   l_sql varchar2(100);
   i number := 1;
 begin
   l_sq         


        
相关标签:
3条回答
  • 2021-01-26 06:15

    Simply use coalesce. It is the most readable and understandable way to write this. Since the logic is contained in one predicate, it's easier to maintain and remove:

    select * from job where id = coalesce(:i, id)
    

    As requested, a 'proof' this actually uses the index:

    create table x ( id number(15) null );
    
    create unique index x_pk on x( id );
    
    select id
    from   x
    where  id = coalesce(:x, id)
    ; -- Uses index
    
    select id
    from   x
    where  id = :x or :x is null
    ; -- Full table scan
    

    Plan:

    SELECT STATEMENT ALL_ROWS Cost: 1 Bytes: 13 Cardinality: 1

    1 INDEX FULL SCAN INDEX (UNIQUE) X_PK Cost: 1 Bytes: 13 Cardinality: 1

    0 讨论(0)
  • 2021-01-26 06:23

    Please let me show the full story behind the Val's question.

    We need to produce the report from the Job table which has following fields :

    • Id not null,
    • JobNumber not null,
    • JobDate not null,
    • MissedReasonId null,
    • FollowUpReasonId null ....

    The report filter form produces the input parameters for the report stored procedure.

    Among other parameters there are two check list boxes on that filter form to check the required Missed Reason items or/and FolowUp Reason items.

    As you can see MissedReasonId and FollowUpReasonId are nullable therefore if user doesn't check any item in those check list boxes we need to show all the jobs.

    If some of the items are checked we send them to the stored procedure as a delimited ID list.

    We are trying to get the data using one single query.

    The assumption is :

    • If Id list input parameter is empty I don't want Oracle to check if the value of (lets say) MissedReasonId is in the provided delimited list (MissedReasonIdList).
    • If Id list is not empty lets select only those records which have MissedReasonId value in the provided list.

    There's no problem with the ID list itself. We can convert it into table or an array and make selection working fast enough. There's no problem if the ID list is mandatory on the filter form (we don't need to check the parameter for null). The problem arises then it is not mandatory (parameter can be null) and there are multiple filter conditions like that on the filter form.

    The query we are using looks like this :

    Select j.Id , j.JobNumber  .....
      From Job j
      left join MissedReason mr on mr.ID = j.MissedReasonId 
      left join FollowUpReason fr on fr.ID = j.FollowUpReasonId
      ....
      Where
        (j.JobDate between P_StartDate and P_EndDate)
        and ( P_MissedReasonIdList is null or (j.MissedReasonId "IS IN THAT P_MissedReasonIdList"))
        and ( P_FollowUpReasonIdList is null or (j.FollowUpReasonId "IS IN THAT MissedReasonIdList"))
        and ......
    

    We expected that if an input parameter is null in the first part of the WHERE condition :

    "and ( P_MissedReasonIdList is null or (j.MissedReasonId "IS IN THAT P_MissedReasonIdList"))"

    Oracle shouldn't bother to check the second part of the condition. (This is what Val mentioned in his question. Right Val ?)

    I really don't want to build the dynamic query on the fly including only those conditions which should be checked into query.

    But is there any other solution to our problem ?

    0 讨论(0)
  • 2021-01-26 06:27

    IS NULL suppresses the index usage. Because NULL values are not indexed.

    There are two ways to make the use of index with IS NULL :

    1.BITMAP index. However, more applicable in OLTP systems.

    2.My favourite way, and nice to demonstrate. We could make the leaves of the b-tree index a constant. Thus, making use of index while querying for NULL. Basically, the NULLs are all together, at the top/bottom of the index. Oracle can use the index forwards or backwards, so doesn't really matter. And it does a full scan of the index.

    I have answered a similar question here http://www.orafaq.com/forum/mv/msg/194746/625371/#msg_625371

    The first scenario won't use the index due to the OR is null condition :

    SQL> SELECT * FROM PROD_NEW;
    
    PROFILE_TYPE
    ---------------
    
    Prod
    Prodparallel
    
    Prod
    
    SQL> CREATE INDEX PROD_NEW_I1 ON PROD_NEW
      2    (PROFILE_TYPE
      3    );
    
    Index created.
    
    SQL> EXPLAIN PLAN FOR SELECT * FROM PROD_NEW WHERE PROFILE_TYPE = 'Prod' OR PROFILE_TYPE IS NULL;
    
    Explained.
    
    SQL> SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);
    
    PLAN_TABLE_OUTPUT
    --------------------------------------------------------------------------------
    Plan hash value: 2121244107
    
    ------------------------------------------------------------------------------
    | Id  | Operation         | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
    ------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT  |          |     3 |    15 |     3   (0)| 00:00:01 |
    |*  1 |  TABLE ACCESS FULL| PROD_NEW |     3 |    15 |     3   (0)| 00:00:01 |
    ------------------------------------------------------------------------------
    
    Predicate Information (identified by operation id):
    ---------------------------------------------------
    
    PLAN_TABLE_OUTPUT
    --------------------------------------------------------------------------------
    
       1 - filter("PROFILE_TYPE" IS NULL OR "PROFILE_TYPE"='Prod')
    
    13 rows selected
    

    Let's make the leaves constant :

    SQL> DROP INDEX PROD_NEW_I1;
    
    Index dropped.
    
    SQL> CREATE INDEX PROD_NEW_I1 ON PROD_NEW
      2    (PROFILE_TYPE,1
      3    );
    
    Index created.
    
    SQL> EXPLAIN PLAN FOR SELECT * FROM PROD_NEW WHERE PROFILE_TYPE = 'Prod' OR PROFILE_TYPE IS NULL;
    
    Explained.
    
    SQL> SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);
    
    PLAN_TABLE_OUTPUT
    --------------------------------------------------------------------------------
    Plan hash value: 1272076902
    
    --------------------------------------------------------------------------------
    | Id  | Operation        | Name        | Rows  | Bytes | Cost (%CPU)| Time     |
    --------------------------------------------------------------------------------
    |   0 | SELECT STATEMENT |             |     3 |    15 |     1   (0)| 00:00:01 |
    |*  1 |  INDEX FULL SCAN | PROD_NEW_I1 |     3 |    15 |     1   (0)| 00:00:01 |
    --------------------------------------------------------------------------------
    
    Predicate Information (identified by operation id):
    ---------------------------------------------------
    
    PLAN_TABLE_OUTPUT
    --------------------------------------------------------------------------------
    
       1 - filter("PROFILE_TYPE" IS NULL OR "PROFILE_TYPE"='Prod')
    
    13 rows selected.
    
    SQL>
    
    0 讨论(0)
提交回复
热议问题