PL/SQL - Optional conditions in where-clause - without dynamic sql?

后端 未结 5 1810
隐瞒了意图╮
隐瞒了意图╮ 2021-02-08 13:51

I have a query where not all conditions are necessary. Here\'s an example of what it looks like when all conditions are used:

select num
from (select distinct q.         


        
5条回答
  •  灰色年华
    2021-02-08 14:21

    While I agree with Tony that performance of using dynamic SQL is better, context variables is a better approach than using bind variables.

    Using IN_VARIABLE IS NULL OR table.fieldx = IN_VARIABLE is not ideal for handling optional values. Each time a query is submitted, Oracle first checks in its shared pool to see if the statement has been submitted before. If it has, the execution plan for the query is retrieved and the SQL is executed. If the statement can not be found in the shared pool, Oracle has to go through the process of parsing the statement, working out various execution paths and coming up with the optimal access plan (AKA “best path”) before it can be executed. This process is known as a “hard parse”, and can take longer than the query itself. Read more about the hard/soft parse in Oracle here, and AskTom here.

    In short - this:

    and (:bcode is null or q.bcode = :bcode)
    

    ...will execute the same, dynamic or otherwise. There's no benefit to using bind variables in dynamic SQL for optional parameters. The setup still destroys SARGability...

    Context parameters are a feature that was introduced in Oracle 9i. They are tied to a package, and can be used to set attribute values (only for users with EXECUTE permission on the package, and you'll have to grant CREATE CONTEXT to the schema). Context variables can be used to tailor dynamic SQL so it includes only what is necessary for the query based on the filter/search criteria. In comparison, Bind variables (also supported in dynamic SQL) require that a value is specified which can result in IN_VARIABLE IS NULL OR table.fieldx = IN_VARIABLE tests in the search query. In practice, a separate context variable should be used for each procedure or function to eliminate the risk of value contamination.

    Here's your query using context variables:

    L_CURSOR SYS_REFCURSOR;
    L_QUERY  VARCHAR2(5000) DEFAULT 'SELECT num
                                       FROM (SELECT DISTINCT q.num
                                               FROM CQQV q
                                              WHERE 1 = 1 ';
    BEGIN
    
        IF IN_BCODE IS NOT NULL THEN
          DBMS_SESSION.SET_CONTEXT('THE_CTX',
                                   'BCODE',
                                   IN_BCODE);
          L_QUERY := L_QUERY || ' AND q.bcode = SYS_CONTEXT(''THE_CTX'', ''BCODE'') ';
        END IF;
    
        IF IN_LB IS NOT NULL THEN
          DBMS_SESSION.SET_CONTEXT('THE_CTX',
                                   'LB',
                                   IN_LB);
          L_QUERY := L_QUERY || ' AND q.lb = SYS_CONTEXT(''THE_CTX'', ''LB'') ';
        END IF;
    
        IF IN_TYPE IS NOT NULL THEN
          DBMS_SESSION.SET_CONTEXT('THE_CTX',
                                   'TYPE',
                                   IN_TYPE);
          L_QUERY := L_QUERY || ' AND q.type = SYS_CONTEXT(''THE_CTX'', ''TYPE'') ';
        END IF;
    
        IF IN_EDATE IS NOT NULL THEN
          DBMS_SESSION.SET_CONTEXT('THE_CTX',
                                   'EDATE',
                                   IN_EDATE);
          L_QUERY := L_QUERY || ' AND q.edate = SYS_CONTEXT(''THE_CTX'', ''EDATE'') - 30 ';
        END IF;
    
        L_QUERY := L_QUERY || ' ORDER BY dbms_random.value()) subq
               WHERE rownum <= :numrows ';
    
        FOR I IN 0 .. (TRUNC(LENGTH(L_QUERY) / 255)) LOOP
          DBMS_OUTPUT.PUT_LINE(SUBSTR(L_QUERY, I * 255 + 1, 255));
        END LOOP;
    
        OPEN L_CURSOR FOR L_QUERY USING IN_ROWNUM;
        RETURN L_CURSOR;
    
    END;
    

    The example still uses a bind variable for the rownum, because the value is not optional.

    DBMS_SESSION.SET_CONTEXT('THE_CTX', 'LB', IN_LB);
    

    The SET_CONTEXT parameters are as follows:

    1. The context variable name. There's no instance creation involved
    2. The variable within the context variable. A context variable is like a session variable, assuming familiarity with web applications & session objects.
    3. The value for the variable defined in parameter #2.

    Bind vs Context

    Bind variables means Oracle expects a variable reference to populate - it's an ORA error otherwise. For example:

    ... L_QUERY USING IN_EXAMPLE_VALUE
    

    ...expects that there is a single bind variable reference to be populated. If IN_EXAMPLE_VALUE is null, there has to be :variable in the query. IE: AND :variable IS NULL

    Using a context variable means not having to include the extraneous/redundant logic, checking if a value is null.

    IMPORTANT: Bind variables are processed in order of occurrence (known as ordinal), NOT by name. You'll notice there's no datatype declaration in the USING clause. Ordinals aren't ideal - if you change them in the query without updating the USING clause, it will break the query until it's fixed.

提交回复
热议问题