2010-09-06 83 views
7

decir que tengo una tabla de ejemplo:¿Cómo manejar los parámetros opcionales en la consulta SQL?

id_pk value 
------------ 
1  a 
2  b 
3  c 

Y tengo un/bloque de muestras de PL SQL, que tiene una consulta que actualmente selecciona una sola fila en una matriz:

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; 

Lo que necesito hacer es implementar la capacidad de seleccionar una sola fila en una matriz, como se muestra en el bloque anterior, O para seleccionar todas las filas en una matriz, si n_RequiredID, que es realmente un parámetro de entrada de usuario, se establece en null .

Y, la pregunta es, ¿cuál es la mejor práctica para manejar esa situación?

puedo pensar en modificar where cláusula de mi consulta a algo como esto:

where m.id_pk = nvl(n_RequiredId, m.id_pk); 

pero supongo que eso va a retrasar la consulta si el parámetro no será nulo, y recuerdo Kyte dijo algo realmente malo acerca de este enfoque.

También se puede pensar en la aplicación de la siguiente lógica PL/SQL:

if n_RequiredId is null then 

    select m.id_pk, m.value bulk collect into t_Output from myTable m; 

else 

    select m.id_pk, m.value bulk collect 
    into t_Output 
    from myTable m 
    where m.id_pk = n_RequiredId; 

end if; 

Pero sería demasiado compleja si me encuentro con más de un parámetro de este tipo.

¿Qué me recomendarías?

Respuesta

14

Sí, utilizando cualquiera de los siguientes:

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

... son not sargable. Funcionarán, pero realizarán la peor de las opciones disponibles.

Si solo tiene un parámetro, el IF/ELSE y las declaraciones por separado y personalizadas son una mejor alternativa.

La siguiente opción después de eso es dynamic SQL. Pero la codificación de SQL dinámico es inútil si se transfieren los predicados no sargables en el primer ejemplo. Dynamic SQL le permite adaptar la consulta a la vez que acomoda numerosas rutas. Pero también corre el riesgo de inyección de SQL, por lo que debe realizarse tras consultas con parámetros (preferiblemente dentro de los procedimientos/funciones almacenadas en paquetes.

+0

¡Oh, genial! Esto me salva el día. Sin embargo, la primera no funcionó porque si la columna es nulable, esta cláusula filtrará los valores nulos, mientras que en la pregunta no se filtrarán los valores nulos en el segundo caso. –

4

OMG_Ponies' y las respuestas de Rob van Wijk son del todo correcto, esto es sólo suplementario.

Hay un buen truco para facilitar el uso de variables de vinculación y aún usar SQL dinámico. Si pone todos los enlaces en una cláusula with al principio, siempre puede vincular el mismo conjunto de variables, ya sea que vaya o no a úselos.

Por ejemplo, supongamos que tiene tres parámetros, que representan un intervalo de fechas y una ID.Si desea buscar sólo en el ID, se puede poner la consulta juntos de esta manera:

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; 

Por otro lado, si es necesario buscar en el rango de identificación y la fecha, que podría tener este aspecto:

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

esto puede parecer una manera rotonda de manejar esto, pero el resultado final es que no importa cómo complicado su SQL dinámico se pone, con tal de que sólo necesita esos tres parámetros, la llamada PL/SQL es siempre algo así como:

execute immediate v_SQL using v_start_date, v_end_date, v_search_id; 

En mi experiencia, es mejor hacer la construcción de SQL un poco más complicada para garantizar que solo haya una línea donde se ejecute realmente.

+0

Esta solución parece más clara que la sugerida por Rob (artículo de Tom Kyte) pero después de probar en Oracle 10.2.0.5 descubrí que el optimizador funciona mejor (es decir, elige índices más selectivos) con el enfoque de Tom. – Ochoto

2

El enfoque NVL generalmente funciona bien. El optimizador reconoce este patrón y construirá un plan dinámico. El plan usa un índice para un valor único y un análisis de tabla completo para un NULL.

Tabla de muestras y datos

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; 

Ejecutar con diferentes predicados

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

Get planes de ejecución

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

planes malo para unirse y es nulo o

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

buen plan para NVL

Los FILTER operaciones permiten al optimizador para elegir un plan diferente en tiempo de ejecución, en función de los valores de entrada.

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

Advertencias

FILTER operaciones y este truco NVL no están bien documentados. No estoy seguro de qué versión presentó estas características, pero funciona con 11g. He tenido problemas para que el FILTER funcione correctamente con algunas consultas complicadas, pero para consultas simples como estas es confiable.

Cuestiones relacionadas