如何在SQL查询中处理可选参数?

时间:2010-09-06 04:39:56

标签: sql oracle plsql

说我有一个样本表:

 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);

但如果我遇到这种类型的多个参数,会变得太复杂。

你会给我什么建议?

3 个答案:

答案 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使用一些复杂的查询时遇到了问题,但对于像这样的简单查询,它是可靠的。