PL/SQL - where 子句中的可选条件 - 没有动态 sql?

发布于 2024-08-11 08:17:47 字数 1040 浏览 3 评论 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 = '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 来构建此查询苍蝇,但我想知道这可能会导致什么样的性能问题,以及是否有更好的方法来做到这一点。

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.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

The parts marked as --this is variable are the parts that, well, vary! If a condition is NOT specified, then there is no default value. For example, if the input specifies "*" for q.type (but leaves everything else the same), then the query should match everything for type, and execute as:

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

I know it is possible to use dynamic sql to build this query on the fly, but I am wondering what sort of performance problems this could cause, and if there is a better way to do this.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

人间☆小暴躁 2024-08-18 08:17:47

虽然您可以这样做...

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;

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

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 来避免。

注意:Michal Pravda 在评论中建议当绑定变量为 null 时使用 (1=1 or :bindvar is null),因为它允许优化器消除该子句。

While you could do this...

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

... the performance using dynamic SQL will usually be better, as it will generate a more targeted query plan. In the above query, Oracle cannot tell whether to use an index on bcode or lb or type or edate, and will probably perform a full table scan every time.

Of course, you must use bind variables in your dynamic query, not concatenate the literal values into the string, otherwise performance (and scalability, and security) will be very bad.

To be clear, the dynamic version I have in mind would work like this:

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;

This means that the result query will be "sargable" (a new word to me I must admit!) since the resulting query run will be (for example):

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

However, I accept that this could require up to 16 hard parses in this example. The "and :bv is null" clauses are required when using native dynamic SQL, but could be avoided by using DBMS_SQL.

Note: the use of (1=1 or :bindvar is null) when the bind variable is null was suggested in a comment by Michal Pravda, as it allows the optimizer to eliminate the clause.

旧情勿念 2024-08-18 08:17:47

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

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

简而言之 - 这:

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 为 null,则查询中必须:variable。 IE:AND :variable IS NULL

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

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

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.

年少掌心 2024-08-18 08:17:47

我选择的解决方案是生成一个动态 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 找到了该解决方案的详细信息的链接:

问汤姆:关于流行和自然选择

The solution I've settled on is one that generates an dynamic SQL query that may look like this:

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

(in this example, the bcode and edate conditions were NOT optional, but the lb and type were)

I think this is (or is very similar to) what Michal Pravda was suggesting, and our DBA here prefers this solution over the context variable solution. Thanks for all that helped and offered advice!

A link our DBA found which details this solution is here:

Ask Tom: On Popularity and Natural Selection

嘿哥们儿 2024-08-18 08:17:47

我只想这样做

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 过滤时,只需保证变量类型为 null 即可。

I would just do this

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

One only has to guarantee that the variable-type is null when the q.TYPE filtering is to be ignored.

九歌凝 2024-08-18 08:17:47

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

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

where ( columnA = passedValue or passedValue = -1 )

when passed value into sql is -1, columnA can be anything..

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文