说我有一个样本表:
id_pk value
------------
1 a
2 b
3 c
我有一个示例PL / SQL块,它有一个查询,当前在数组中选择一行:
declare
type t_table is table of myTable%rowtype;
n_RequiredId myTable.id_pk%type := 1;
t_Output t_table := t_table();
begin
select m.id_pk, m.value
bulk collect into t_Output
from myTable m
where m.id_pk = n_RequiredId;
end;
我需要做的是实现一个选择单个行到数组的能力,如上面的方框所示, OR 选择所有行到数组中,如果{{1} },实际上是用户输入参数,设置为n_RequiredID
。
问题是,处理这种情况的最佳做法是什么?
我可以考虑将查询的null
子句修改为:
where
但我认为如果参数不为空,那将会减慢查询速度,我记得Kyte说这个方法真的很糟糕。
我还可以考虑实现以下PL / SQL逻辑:
where m.id_pk = nvl(n_RequiredId, m.id_pk);
但如果我遇到这种类型的多个参数,会变得太复杂。
你会给我什么建议?
答案 0 :(得分:13)
是,请使用以下任何一项:
WHERE m.id_pk = NVL(n_RequiredId, m.id_pk);
WHERE m.id_pk = COALESCE(n_RequiredId, m.id_pk);
WHERE (n_RequiredId IS NULL OR m.id_pk = n_RequiredId);
...是not sargable。他们会工作,但执行最糟糕的选择。
如果您只有一个参数,则IF / ELSE和单独的,定制的语句是更好的选择。
之后的下一个选项是dynamic SQL。但是如果你在第一个例子中继承了不可思考的谓词,那么编写动态SQL是没用的。动态SQL允许您在容纳多个路径的同时定制查询。但它也存在SQL注入的风险,因此它应该在参数化查询后面执行(最好在包中的存储过程/函数中执行。
答案 1 :(得分:4)
OMG_Ponies'和Rob van Wijk的答案是完全正确的,这只是补充。
有一个很好的技巧,可以很容易地使用绑定变量并仍然使用动态SQL。如果你把所有的绑定放在一个开头的with子句中,你总是可以绑定同一组变量,无论你是否要使用它们。
例如,假设您有三个参数,表示日期范围和ID。如果您只想搜索ID,可以将查询放在一起,如下所示:
with parameters as (
select :start_date as start_date,
:end_date as end_date,
:search_id as search_id
from dual)
select *
from your_table
inner join parameters
on parameters.search_id = your_table.id;
另一方面,如果您需要搜索ID和日期范围,它可能如下所示:
with parameters as (
select :start_date as start_date,
:end_date as end_date,
:search_id as search_id
from dual)
select *
from your_table
inner join parameters
on parameters.search_id = your_table.id
and your_table.create_date between (parameters.start_date
and parameters.end_date);
这看起来似乎是一种处理这种问题的方法,但最终的结果是,无论你的动态SQL如何复杂化,只要它只需要这三个参数,PL / SQL调用总是一样的像:
execute immediate v_SQL using v_start_date, v_end_date, v_search_id;
根据我的经验,最好使SQL构造稍微复杂一点,以确保只有一行实际执行它。
答案 2 :(得分:2)
NVL方法通常可以正常工作。优化器识别此模式并构建动态计划。该计划使用单个值的索引和NULL的全表扫描。
示例表和数据
drop table myTable;
create table myTable(
id_pk number,
value varchar2(100),
constraint myTable_pk primary key (id_pk)
);
insert into myTable select level, level from dual connect by level <= 100000;
commit;
使用不同的谓词执行
--Execute predicates that return one row if the ID is set, or all rows if ID is null.
declare
type t_table is table of myTable%rowtype;
n_RequiredId myTable.id_pk%type := 1;
t_Output t_table := t_table();
begin
select /*+ SO_QUERY_1 */ m.id_pk, m.value
bulk collect into t_Output
from myTable m
where m.id_pk = nvl(n_RequiredId, m.id_pk);
select /*+ SO_QUERY_2 */ m.id_pk, m.value
bulk collect into t_Output
from myTable m
where m.id_pk = COALESCE(n_RequiredId, m.id_pk);
select /*+ SO_QUERY_3 */ m.id_pk, m.value
bulk collect into t_Output
from myTable m
where (n_RequiredId IS NULL OR m.id_pk = n_RequiredId);
end;
/
获取执行计划
select sql_id, child_number
from gv$sql
where lower(sql_text) like '%so_query_%'
and sql_text not like '%QUINE%'
and sql_text not like 'declare%';
select * from table(dbms_xplan.display_cursor(sql_id => '76ucq3bkgt0qa', cursor_child_no => 1, format => 'basic'));
select * from table(dbms_xplan.display_cursor(sql_id => '4vxf8yy5xd6qv', cursor_child_no => 1, format => 'basic'));
select * from table(dbms_xplan.display_cursor(sql_id => '457ypz0jpk3np', cursor_child_no => 1, format => 'basic'));
错误的COALESCE计划和IS NULL或
EXPLAINED SQL STATEMENT:
------------------------
SELECT /*+ SO_QUERY_2 */ M.ID_PK, M.VALUE FROM MYTABLE M WHERE M.ID_PK
= COALESCE(:B1 , M.ID_PK)
Plan hash value: 1229213413
-------------------------------------
| Id | Operation | Name |
-------------------------------------
| 0 | SELECT STATEMENT | |
| 1 | TABLE ACCESS FULL| MYTABLE |
-------------------------------------
EXPLAINED SQL STATEMENT:
------------------------
SELECT /*+ SO_QUERY_3 */ M.ID_PK, M.VALUE FROM MYTABLE M WHERE (:B1 IS
NULL OR M.ID_PK = :B1 )
Plan hash value: 1229213413
-------------------------------------
| Id | Operation | Name |
-------------------------------------
| 0 | SELECT STATEMENT | |
| 1 | TABLE ACCESS FULL| MYTABLE |
-------------------------------------
NVL的好计划
FILTER
操作允许优化器在运行时选择不同的计划,具体取决于输入值。
EXPLAINED SQL STATEMENT:
------------------------
SELECT /*+ SO_QUERY_1 */ M.ID_PK, M.VALUE FROM MYTABLE M WHERE M.ID_PK
= NVL(:B1 , M.ID_PK)
Plan hash value: 730481884
----------------------------------------------------
| Id | Operation | Name |
----------------------------------------------------
| 0 | SELECT STATEMENT | |
| 1 | CONCATENATION | |
| 2 | FILTER | |
| 3 | TABLE ACCESS FULL | MYTABLE |
| 4 | FILTER | |
| 5 | TABLE ACCESS BY INDEX ROWID| MYTABLE |
| 6 | INDEX UNIQUE SCAN | MYTABLE_PK |
----------------------------------------------------
<强>警告强>
FILTER
操作和此NVL
技巧没有详细记录。我不确定哪个版本引入了这些功能,但它适用于11g。我在使FILTER
使用一些复杂的查询时遇到了问题,但对于像这样的简单查询,它是可靠的。