2010-09-06 2 views
7

che ho una tabella di esempio:Come gestire i parametri opzionali nella query SQL?

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

E ho un/blocco SQL campione PL, che ha una query che attualmente seleziona una singola riga in un array:

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; 

Cosa mi serve è necessario implementare la possibilità di selezionare una singola riga in un array, come mostrato nel blocco sopra, O per selezionare tutte le righe in un array, se n_RequiredID, che in realtà è un parametro di input dell'utente, è impostato su null .

E, la domanda è: qual è la migliore pratica per gestire tale situazione?

mi viene in mente di modificare where clausola della mia query per qualcosa di simile:

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

ma suppongo che sta per rallentare la query se il parametro non sarà nullo, e mi ricordo Kyte ha detto qualcosa di veramente brutto per questo approccio.

posso anche pensare di attuare la seguente logica 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; 

Ma sarebbe diventato troppo complesso se io incontro più di un parametro di questo tipo.

Cosa mi consiglieresti?

risposta

14

Sì, utilizzando uno dei seguenti:

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

... sono not sargable. Funzioneranno, ma eseguiranno la peggiore delle opzioni disponibili.

Se si dispone di un solo parametro, l'istruzione IF/ELSE e le istruzioni separate e personalizzate rappresentano un'alternativa migliore.

L'opzione successiva è dynamic SQL. Ma la codifica di SQL dinamico è inutile se si portano avanti i predicati non sargable nel primo esempio. SQL dinamico consente di personalizzare la query mentre si adattano numerosi percorsi. Ma rischia anche di SQL injection, quindi dovrebbe essere eseguita dietro query con parametri (preferibilmente entro procedure/funzioni memorizzate in pacchetti.

+0

Oh fantastico! Questo salva la mia giornata. Tuttavia, il primo non ha funzionato coz se la colonna è nullable, questa clausola filtrerà i valori nulli mentre nella domanda non filtreresti i valori nulli nel secondo caso. –

4

OMG_Ponies' e le risposte di Rob van Wijk è del tutto corretto, questo è solo un supplemento.

C'è un bel trucco per rendere più semplice l'uso delle variabili di bind e usare ancora l'SQL dinamico. Se metti tutti i bind in una clausola with all'inizio, puoi sempre associare lo stesso insieme di variabili, indipendentemente dal fatto che tu stia andando o meno usali

Ad esempio, supponiamo di avere tre parametri, che rappresentano un intervallo di date e un ID.Se si desidera effettuare la ricerca solo sul ID, si potrebbe mettere la query insieme in questo modo:

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; 

D'altra parte, se avete bisogno per cercare l'ID e la data di gamma, potrebbe assomigliare a questo:

Questo può sembrare un modo approssimativo di gestirlo, ma il risultato finale è che, indipendentemente dal modo in cui è complicato il tuo SQL dinamico, a patto che richieda solo quei tre parametri, la chiamata PL/SQL è sempre qualcosa del tipo:

execute immediate v_SQL using v_start_date, v_end_date, v_search_id; 

Nella mia esperienza è meglio rendere la costruzione SQL leggermente più complicata per garantire che ci sia solo una linea in cui viene effettivamente eseguita.

+0

Questa soluzione sembra più chiara di quella suggerita da Rob (articolo di Tom Kyte) ma dopo aver testato su Oracle 10.2.0.5 ho scoperto che l'ottimizzatore funziona meglio (ad esempio: sceglie indici più selettivi) con l'approccio di Tom. – Ochoto

2

L'approccio NVL di solito funziona correttamente. L'ottimizzatore riconosce questo modello e costruirà un piano dinamico. Il piano utilizza un indice per un singolo valore e una scansione completa della tabella per un valore NULL.

tavolo del campione e dei dati

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; 

Esegui con diversi predicati

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

esecuzione Get piani

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

piani Bad per COALESCE e è nullo 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 | 
------------------------------------- 

buon piano per NVL

I FILTER operazioni consentono l'ottimizzatore di scegliere un piano diverso in fase di esecuzione, a seconda dei valori di input.

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

Avvertenze

FILTER operazioni e questo NVL trucco non sono ben documentati. Non sono sicuro di quale versione abbia introdotto queste funzionalità ma funziona con 11g. Ho avuto problemi a far funzionare locorrettamente con alcune query complicate, ma per query semplici come queste è affidabile.