2012-06-12 15 views
6

Tengo un procedimiento almacenado que acepta múltiples parámetros (es decir Pname, pHeight, PTeam)parámetros de unión a Oracle SQL dinámico

tengo la consulta construida de esta manera:

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; 

Si ejecutar la procedimiento que pasa todos los parámetros, se ejecuta correctamente.

Pero si yo sólo pasaba una o dos de los parámetros, a continuación, los errores de procedimiento cabo:

ORA-01006: bind variable does not exist 

¿Cómo se unen selectivamente la variable con los parámetros basados ​​en donde se utilizó el valor del parámetro? Por ejemplo, aunque sólo fue aprobada pName, entonces yo solamente ejecutar la consulta:

OPEN TestCursor FOR SQLQuery USING pName; 

O si tanto pName y PTeam fue aprobada, entonces:

OPEN TestCursor FOR SQLQuery USING pName, pTeam; 

espero que alguien pueda arrojar más formas de resolver esta. Gracias.

Editar: que podría utilizar realmente el siguiente:

- Construir la consulta sobre la base de los parámetros pasados. SI pName NO ES NULO SQLQuery: = SQLQuery || 'Y NOMBRE COMO' '' || 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; 

Pero esto sería muy vulnerable a la inyección de SQL ...

Respuesta

6

Esto no es enormemente elegante pero significaría que siempre podríamos suministrar todas las tres variables se unen, aunque algunos de ellos son nulos. Solo agrega las cláusulas adicionales WHERE si es necesario.

(Intenté formatear el SQL dinámico para que sea más legible, solo podría proporcionarlo como una cadena larga).

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; 

No estoy frente a una estación de trabajo con acceso a base de datos por lo que no puede probar la función, sino que debe estar cerca (por favor, perdona los errores de sintaxis, que ha sido un largo día!)

Espero que ayude ...

+0

¿Dónde se declaró t_binds? ¿O debo declararlo en alguna parte? – Batuta

+0

Se declara en la cláusula 'WITH' en la declaración SQL, es una tabla ficticia para contener las variables de vinculación. Vea aquí: http://www.orafaq.com/node/1879 – Ollie

+0

Probé esto pero todavía dice ORA-01008: no todas las variables están vinculadas – Batuta

7

Puede usar el paquete DBMS_SQL. Esto proporciona una forma alternativa de ejecutar SQL dinámico. Es quizás un poco más engorroso de usar, pero puede ser más flexible, especialmente con números variables de parámetros de enlace.

Así es como se podría utilizar (advertencia: no he probado esto):

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 es nuevo en Oracle 11g)

+0

+1, me gusta usar DBMS_SQL ya que es más "compatible" pero un poco más complicado que el método que sugerí. – Ollie

0

¿Qué tal

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

como '%' no afecta null. entonces si alguno de estos campos es nulo, obtendrás falsa –

1

El enfoque que uso es incluir en el SQL dinámico un caso ELSE en los estados al revés del IF. Su código comprueba que pName no es nulo, por lo que agregaría una cláusula a la prueba de consulta generada que pName ES nulo. De esta forma, puede pasar los mismos parámetros cada vez sin afectar los resultados de la consulta.

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; 
Cuestiones relacionadas