10

我有一个查询,并非所有条件都是必需的。以下是使用所有条件时的外观示例:

select num
from (select distinct q.num
       from cqqv q
       where q.bcode = '1234567' --this is variable
             and q.lb = 'AXCT' --this is variable
             and q.type = 'privt' --this is variable
             and q.edate > sysdate - 30 --this is variable
       order by dbms_random.value()) subq
where rownum <= 10; --this is variable

标记为--this is variable的部分是不同的部分!如果未指定条件,则没有默认值。例如,如果输入为 q.type 指定“*”(但保持其他所有内容相同),则查询应匹配所有类型,并执行如下:

select num
from (select distinct q.num
       from cqqv q
       where q.bcode = '1234567' --this is variable
             and q.lb = 'AXCT' --this is variable
             --and q.type = 'privt' --this condition ignored because of "type=*" in input
             and q.edate > sysdate - 30 --this is variable
       order by dbms_random.value()) subq
where rownum <= 10; --this is variable

我知道可以使用动态 sql 即时构建此查询,但我想知道这可能会导致什么样的性能问题,以及是否有更好的方法来做到这一点。

4

5 回答 5

12

虽然你可以这样做...

select num
from (select distinct q.num
       from cqqv q
       where 1=1
             and (:bcode is null or q.bcode = :bcode)
             and (:lb is null or q.lb = :lb)
             and (:type is null or q.type = :type)
             and (:edate is null or q.edate > :edate - 30)
       order by dbms_random.value()) subq
where rownum <= :numrows

...使用动态 SQL 的性能通常会更好,因为它会生成更有针对性的查询计划。在上面的查询中,Oracle 无法判断是使用 bcode 还是 lb 或 type 还是 edate 的索引,并且可能每次都会执行全表扫描。

当然,您必须在动态查询中使用绑定变量,而不是将文字值连接到字符串中,否则性能(以及可伸缩性和安全性)将非常糟糕

需要明确的是,我想到的动态版本将像这样工作:

declare
    rc sys_refcursor;
    q long;
begin
    q := 'select num
    from (select distinct q.num
           from cqqv q
           where 1=1';

    if p_bcode is not null then
        q := q || 'and q.bcode = :bcode';
    else
        q := q || 'and (1=1 or :bcode is null)';
    end if;

    if p_lb is not null then
        q := q || 'and q.lb = :lb';
    else
        q := q || 'and (1=1 or :lb is null)';
    end if;

    if p_type is not null then
        q := q || 'and q.type = :type';
    else
        q := q || 'and (1=1 or :type is null)';
    end if;

    if p_edate is not null then
        q := q || 'and q.edate = :edate';
    else
        q := q || 'and (1=1 or :edate is null)';
    end if;

    q := q || ' order by dbms_random.value()) subq
    where rownum <= :numrows';

    open rc for q using p_bcode, p_lb, p_type, p_edate, p_numrows;
    return rc;
end;

这意味着结果查询是“sargable”(我必须承认这是一个新词!),因为结果查询运行将是(例如):

select num
from (select distinct q.num
       from cqqv q
       where 1=1
             and q.bcode = :bcode
             and q.lb = :lb
             and (1=1 or :type is null)
             and (1=1 or :edate is null)
       order by dbms_random.value()) subq
where rownum <= :numrows

但是,我接受在此示例中这可能需要多达 16 次硬解析。使用本机动态 SQL 时需要“and :bv is null”子句,但可以通过使用 DBMS_SQL 来避免。

注意:在(1=1 or :bindvar is null)Michal Pravda 的评论中建议使用 when bind variable is null,因为它允许优化器消除该子句。

于 2009-11-11T17:12:35.020 回答
5

虽然我同意 Tony 的观点,即使用动态 SQL 的性能更好,但上下文变量是比使用绑定变量更好的方法。

使用IN_VARIABLE IS NULL OR table.fieldx = IN_VARIABLE不适合处理可选值。每次提交查询时,Oracle 首先在其共享池中检查该语句之前是否已提交过。如果有,则检索查询的执行计划并执行 SQL。如果在共享池中找不到该语句,Oracle 必须经过解析语句、计算出各种执行路径并提出最佳访问计划(也称为“最佳路径”)的过程才能执行。这个过程被称为“硬解析”,它可能比查询本身花费更长的时间。在此处阅读有关Oracle 中的硬/软解析的更多信息,并在此处阅读 AskTom

简而言之 - 这个:

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

...将执行相同的,动态的或其他的。在动态 SQL 中为可选参数使用绑定变量没有任何好处。该设置仍然会破坏 SARGability ......

上下文参数是Oracle 9i中引入的一个特性。它们与包绑定,可用于设置属性值(仅适用于对包具有 EXECUTE 权限的用户,您必须将 CREATE CONTEXT 授予模式)。上下文变量可用于定制动态 SQL,因此它仅包含基于过滤器/搜索条件的查询所需的内容。相比之下,绑定变量(在动态 SQL 中也支持)要求指定一个值,这可能会导致IN_VARIABLE IS NULL OR table.fieldx = IN_VARIABLE搜索查询中的测试。在实践中,应为每个程序或功能使用单独的上下文变量,以消除价值污染的风险。

这是您使用上下文变量的查询:

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;

该示例仍然为 rownum 使用绑定变量,因为该值不是可选的。

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

SET_CONTEXT 参数如下:

  1. 上下文变量名称。不涉及实例创建
  2. 上下文变量中的变量。假设熟悉 Web 应用程序和会话对象,上下文变量就像会话变量。
  3. 参数 #2 中定义的变量的值。

绑定与上下文

绑定变量意味着 Oracle 期望填充变量引用 - 否则会出现 ORA 错误。例如:

... L_QUERY USING IN_EXAMPLE_VALUE

...期望有一个要填充的绑定变量引用。如果IN_EXAMPLE_VALUE为空,则必须:variable查询中。IE:AND :variable IS NULL

使用上下文变量意味着不必包含无关/冗余逻辑,检查值是否为空。

重要提示:绑定变量按出现顺序(称为序数)处理,而不是按名称处理。USING您会注意到子句中没有数据类型声明。序数并不理想 - 如果您在查询中更改它们而不更新USING子句,它将破坏查询,直到它被修复。

于 2009-11-11T18:16:33.380 回答
3

我选择的解决方案是生成一个动态 SQL 查询,如下所示:

select num
from (select distinct q.NUM
       from cqqv q 
       where  (q.bcode = :bcode) 
                  and  (1=1 or :lb is null) 
                  and  (1=1 or :type is null) 
                  and  (q.edate> :edate) 
                order by dbms_random.value()) subq 
where rownum <= :numrows

(在这个例子中,bcode 和 edate 条件不是可选的,但是 lb 和 type 是可选的)

我认为这是(或非常类似于)Michal Pravda 的建议,我们的 DBA 更喜欢这个解决方案而不是上下文变量解决方案。感谢您提供的所有帮助和建议!

我们的 DBA 找到了详细说明此解决方案的链接:

问汤姆:关于受欢迎程度和自然选择

于 2009-11-13T15:06:30.813 回答
0

我会这样做

select num
from (select distinct q.num
       from cqqv q
       where q.bcode = '1234567' --this is variable
             and q.lb = 'AXCT' --this is variable
             and q.type = nvl(<variable-type>, q.type)  --this condition ignored because of "type=*" in input
             and q.edate > sysdate - 30 --this is variable
       order by dbms_random.value()) subq
where rownum <= 10; --this is variable

仅当要忽略 q.TYPE 过滤时,必须保证变量类型为空。

于 2011-03-15T23:35:13.740 回答
0

其中(columnA = passValue 或passedValue = -1)

当传递给 sql 的值为 -1 时,columnA 可以是任何东西..

于 2016-03-23T13:31:45.827 回答