我有一个查询,其中并非所有条件都是必需的。以下是使用所有条件时的示例:
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来构建这个查询,但我想知道这会导致什么样的性能问题,以及是否有更好的方法来实现这一点。
答案 0 :(得分: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上的索引还是键入或编辑,并且每次都可能执行全表扫描。
当然,必须在动态查询中使用绑定变量,而不是将字面值连接到字符串中,否则性能(和可伸缩性和安全性)将非常糟糕强>
要清楚,我想到的动态版本的工作原理如下:
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为null”子句是必需的,但可以使用DBMS_SQL来避免。
注意:在Michal Pravda的评论中建议在绑定变量为null时使用(1=1 or :bindvar is null)
,因为它允许优化器删除该子句。
答案 1 :(得分:5)
虽然我同意Tony的观点,即使用动态SQL的性能更好,但是上下文变量比使用绑定变量更好。
使用IN_VARIABLE IS NULL OR table.fieldx = IN_VARIABLE
不适合处理可选值。每次提交查询时,Oracle首先检查其共享池以查看之前是否已提交该语句。如果有,则检索查询的执行计划并执行SQL。如果在共享池中找不到该语句,Oracle必须完成解析语句的过程,制定各种执行路径并在执行之前提出最佳访问计划(AKA“最佳路径”)。此过程称为“硬解析”,并且可能比查询本身花费更长的时间。详细了解hard/soft parse in Oracle here和AskTom here。
简而言之 - 这:
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参数如下:
绑定变量意味着Oracle期望填充变量引用 - 否则就是ORA错误。例如:
... L_QUERY USING IN_EXAMPLE_VALUE
...期望有一个单独的绑定变量引用被填充。如果IN_EXAMPLE_VALUE
为空,则 在查询中为:variable
。 IE:AND :variable IS NULL
使用上下文变量意味着不必包含无关/冗余逻辑,检查值是否为空。
重要:绑定变量按发生顺序(称为序数)处理, NOT 按名称处理。您会注意到USING
子句中没有数据类型声明。普通是不理想的 - 如果你在查询中更改它们而不更新USING
子句,它将打破查询直到它被修复。
答案 2 :(得分: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和类型是)
我认为这与Michal Pravda的建议是(或非常相似),而我们的DBA在此更喜欢这种解决方案而不是上下文变量解决方案。感谢所有帮助并提供建议!
我们的DBA发现的链接详细说明了此解决方案:
答案 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 = 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过滤时,只需要保证变量类型为空。
答案 4 :(得分:0)
其中(columnA = passedValue或passedValue = -1)
当传递给sql的值为-1时,columnA可以是任何东西..