2012-06-12 17 views
6

Ho una stored procedure che accetta parametri multipli (cioè pName, pHeight, PTeam)i parametri di rilegatura a Oracle SQL dinamico

Ho query costruita in questo modo:

SQLQuery VARCHAR2(6000); 
TestCursor T_CURSOR; 

SQLQuery := 'SELECT ID, Name, Height, Team FROM MyTable WHERE ID IS NOT NULL '; 


-- Build the query based on the parameters passed. 
IF pName IS NOT NULL 
    SQLQuery := SQLQuery || 'AND Name LIKE :pName '; 
END IF; 

IF pHeight IS > 0 
    SQLQuery := SQLQuery || 'AND Height = :pHeight '; 
END IF; 

IF pTeam IS NOT NULL 
    SQLQuery := SQLQuery || 'AND Team LIKE :pTeam '; 
END IF; 


OPEN TestCursor FOR SQLQuery USING pName, pHeight, pTeam; 

Se eseguire la procedura che passa tutti i parametri, funziona correttamente.

Ma se ho superato solo uno o due dei parametri, quindi gli errori di procedura out:

ORA-01006: bind variable does not exist 

Come faccio selettivamente lego la variabile con i parametri in base a dove è stato utilizzato il valore del parametro? Ad esempio, se non altro è stata approvata pName, quindi vorrei solo eseguire la query:

OPEN TestCursor FOR SQLQuery USING pName; 

O se sia pName e PTeam è stata approvata, quindi:

OPEN TestCursor FOR SQLQuery USING pName, pTeam; 

speranza che qualcuno possa fare più modi per risolvere Questo. Grazie.

Edit: ho potuto effettivamente utilizzare il seguente:

- Costruire la query in base ai parametri passati. SE pName NON È NULL SQLQuery: = SQLQuery || 'AND Nome LIKE' '' || pName || '' ''; FINE IF;

IF pHeight IS > 0 
    SQLQuery := SQLQuery || 'AND Height = pHeight '; 
END IF; 

IF pTeam IS NOT NULL 
    SQLQuery := SQLQuery || 'AND Team LIKE ''' || pTeam || ''' '; 
END IF; 


OPEN TestCursor FOR SQLQuery; 

Ma questo sarebbe molto vulnerabili a SQL Injection ...

risposta

6

Questo non è estremamente elegante, ma vorrebbe dire che si può sempre fornire tutti i tre variabili di bind, anche se alcuni di loro sono nulli. Aggiungi le clausole WHERE in eccesso, se necessario.

(Ho provato a formattare l'SQL dinamico per renderlo più leggibile, è possibile fornire solo una stringa lunga).

FUNCTION myFunc (
    pName IN VARCHAR2, 
    pHeight IN VARCHAR2, 
    pTeam IN VARCHAR2 
) 
    RETURN T_CURSOR 
IS 
    -- Local Variables 
    SQLQuery VARCHAR2(6000); 
    TestCursor T_CURSOR; 
BEGIN 
    -- Build SQL query 
    SQLQuery := 'WITH t_binds '|| 
       ' AS (SELECT :v_name AS bv_name, '|| 
          ' :v_height AS bv_height, '|| 
          ' :v_team AS bv_team '|| 
         ' FROM dual) '|| 
       ' SELECT id, '|| 
         ' name, '|| 
         ' height, '|| 
         ' team '|| 
       ' FROM MyTable, '|| 
         ' t_binds '|| 
       ' WHERE id IS NOT NULL'; 

    -- Build the query WHERE clause based on the parameters passed. 
    IF pName IS NOT NULL 
    THEN 
    SQLQuery := SQLQuery || ' AND Name LIKE bv_name '; 
    END IF; 

    IF pHeight > 0 
    THEN 
    SQLQuery := SQLQuery || ' AND Height = bv_height '; 
    END IF; 

    IF pTeam IS NOT NULL 
    THEN 
    SQLQuery := SQLQuery || ' AND Team LIKE bv_team '; 
    END IF; 

    OPEN TestCursor 
    FOR SQLQuery 
    USING pName, 
     pHeight, 
     pTeam; 

    -- Return the cursor 
    RETURN TestCursor; 
END myFunc; 

Io non sono di fronte a una stazione di lavoro con accesso DB, quindi non posso testare la funzione ma dovrebbe essere vicino (si prega di perdonare eventuali errori di sintassi, è stata una lunga giornata!)

Spero che aiuti ...

+0

Dove è stato dichiarato t_binds? O devo dichiararlo da qualche parte? – Batuta

+0

È dichiarato nella clausola 'WITH' nell'istruzione SQL, è una tabella fittizia per contenere le variabili di binding. Vedi qui: http://www.orafaq.com/node/1879 – Ollie

+0

Provato questo, ma continua a dire ORA-01008: non tutte le variabili rilevano – Batuta

7

È possibile utilizzare il pacchetto DBMS_SQL. Questo fornisce un modo alternativo per eseguire SQL dinamico. È forse un po 'più ingombrante da usare, ma può essere più flessibile, soprattutto con un numero variabile di parametri di bind.

Ecco come si potrebbe usare (attenzione: non ho ancora testato questo):

FUNCTION player_search (
    pName  IN VARCHAR2, 
    pHeight  IN NUMBER, 
    pTeam  IN VARCHAR2 
) RETURN SYS_REFCURSOR 
IS 
    cursor_name INTEGER; 
    ignore  INTEGER; 
    id_var  MyTable.ID%TYPE; 
    name_var  MyTable.Name%TYPE; 
    height_var MyTable.Height%TYPE; 
    team_var  MyTable.Team%TYPE; 
BEGIN 
    -- Put together SQLQuery here... 

    -- Open the cursor and parse the query   
    cursor_name := DBMS_SQL.OPEN_CURSOR; 
    DBMS_SQL.PARSE(cursor_name, SQLQuery, DBMS_SQL.NATIVE); 

    -- Define the columns that the query returns. 
    -- (The last number for columns 2 and 4 is the size of the 
    -- VARCHAR2 columns. Feel free to change them.) 
    DBMS_SQL.DEFINE_COLUMN(cursor_name, 1, id_var); 
    DBMS_SQL.DEFINE_COLUMN(cursor_name, 2, name_var, 30); 
    DBMS_SQL.DEFINE_COLUMN(cursor_name, 3, height_var); 
    DBMS_SQL.DEFINE_COLUMN(cursor_name, 4, team_var, 30); 

    -- Add bind variables depending on whether they were added to 
    -- the query. 
    IF pName IS NOT NULL THEN 
    DBMS_SQL.BIND_VARIABLE(cursor_name, ':pName', pName); 
    END IF; 

    IF pHeight > 0 THEN 
    DBMS_SQL.BIND_VARIABLE(cursor_name, ':pHeight', pHeight); 
    END IF; 

    IF pTeam IS NOT NULL THEN 
    DBMS_SQL.BIND_VARIABLE(cursor_name, ':pTeam', pTeam); 
    END IF; 

    -- Run the query. 
    -- (The return value of DBMS_SQL.EXECUTE for SELECT queries is undefined, 
    -- so we must ignore it.) 
    ignore := DBMS_SQL.EXECUTE(cursor_name); 

    -- Convert the DBMS_SQL cursor into a PL/SQL REF CURSOR. 
    RETURN DBMS_SQL.TO_REFCURSOR(cursor_name); 

EXCEPTION 
    WHEN OTHERS THEN 
    -- Ensure that the cursor is closed. 
    IF DBMS_SQL.IS_OPEN(cursor_name) THEN 
     DBMS_SQL.CLOSE_CURSOR(cursor_name); 
    END IF; 
    RAISE; 
END; 

(Nota:. DBMS_SQL.TO_REFCURSOR è nuovo in Oracle 11g)

+0

+1, mi piace usare DBMS_SQL perché è più "supportato" ma un po 'più complicato del metodo che ho suggerito. – Ollie

0

Come su

SQLQuery := 'SELECT ID, Name, Height, Team FROM MyTable WHERE ID IS NOT NULL '; 

SQLQuery := SQLQuery || 'AND Name LIKE :pName '; 
SQLQuery := SQLQuery || 'AND Team LIKE :pTeam '; 
SQLQuery := SQLQuery || 'AND (Height = :pHeight OR :pHeight = 0)'; 

OPEN TestCursor FOR SQLQuery USING nvl(pName, '%'), nvl(pTeam, '%'), nvl(pHeight, 0), nvl(pHeight, 0); 

?

+0

come '%' non ha effetto su null. quindi se alcuni di questi campi sono nulli, si otterrà falso –

1

L'approccio che utilizzo è quello di includere nell'SQL dinamico un caso ELSE che indica il retro dell'IF. Il tuo codice verifica che pName non sia nullo, quindi aggiungerei una clausola alla query generata che verifica che pName IS Null. In questo modo è possibile passare gli stessi parametri ogni volta senza influire sui risultati della query.

SQLQuery VARCHAR2(6000); 
TestCursor T_CURSOR; 

SQLQuery := 'SELECT ID, Name, Height, Team FROM MyTable WHERE ID IS NOT NULL '; 


-- Build the query based on the parameters passed. 
IF pName IS NOT NULL 
    SQLQuery := SQLQuery || 'AND Name LIKE :pName '; 
ELSE 
    SQLQuery := SQLQuery || 'AND :pName IS NULL'; 
END IF; 

IF pHeight IS > 0 
    SQLQuery := SQLQuery || 'AND Height = :pHeight '; 
ELSE 
    SQLQuery := SQLQuery || 'AND :pHeight <=0 '; 
END IF; 

IF pTeam IS NOT NULL 
    SQLQuery := SQLQuery || 'AND Team LIKE :pTeam '; 
ELSE 
    SQLQuery := SQLQuery || 'AND :pTeam IS NULL'; 
END IF; 


OPEN TestCursor FOR SQLQuery USING pName, pHeight, pTeam;