2012-06-12 19 views
6

Mam procedurę przechowywaną, która przyjmuje wielu parametrów (tj PNAME, pHeight, pTeam)wiążące parametry do Oracle Dynamiczny SQL

Mam zapytanie zabudowany tak:

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; 

Jeśli I wykonanie procedura przechodząca wszystkie parametry, działa poprawnie.

Ale jeśli tylko przeszły jednego lub dwóch parametrów, wówczas procedura się błędy:

ORA-01006: bind variable does not exist 

Jak selektywnie wiążą zmienną z parametrami oparciu o wartość parametru gdzie użyto? Na przykład, jeśli tylko PNAME została uchwalona, ​​to bym wykonać tylko zapytania:

OPEN TestCursor FOR SQLQuery USING pName; 

Albo wtedy, gdy oba PNAME i pTeam została przyjęta, a następnie:

OPEN TestCursor FOR SQLQuery USING pName, pTeam; 

nadzieję, że ktoś może rzucić więcej sposobów, aby rozwiązać to. Dzięki.

Edit: I faktycznie mogła użyć następujących:

- Budowanie zapytania w oparciu o parametry przekazywane. JEŚLI pName NIE JEST NULL SQLQuery: = SQLQuery || "ORAZ NAZWĘ 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; 

Ale to byłoby bardzo podatne na SQL Injection ...

Odpowiedz

6

To nie jest niezwykle elegancki, ale oznaczałoby to, że zawsze można dostarczyć wszystkie trzy zmienne powiązań nawet jeśli niektóre z nich są nieważne. W razie potrzeby dodaje się tylko dodatkowe klauzule WHERE.

(Próbowałem sformatować dynamiczny SQL, aby był bardziej czytelny, można go podać jako jeden długi ciąg).

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; 

Nie jestem naprzeciwko stacji roboczej z dostępem do DB, więc nie mogę sprawdzić działanie tej funkcji, ale powinna być zbliżona (proszę wybaczyć ewentualne błędy składniowe, to był długi dzień!)

Mam nadzieję, że pomoże ...

+0

Gdzie zadeklarowano t_binds? Czy muszę go gdzieś zadeklarować? – Batuta

+0

Jest zadeklarowany w klauzuli 'WITH' w instrukcji SQL, jest to fikcyjna tabela do przechowywania zmiennych wiązania. Zobacz tutaj: http://www.orafaq.com/node/1879 – Ollie

+0

Próbowałem tego, ale wciąż mówiłem ORA-01008: nie wszystkie zmienne związane – Batuta

7

Możesz użyć pakietu DBMS_SQL. Zapewnia to alternatywny sposób uruchamiania dynamicznego SQL. Być może jest to trochę bardziej kłopotliwe w użyciu, ale może być bardziej elastyczne, szczególnie przy zmiennej liczbie parametrów wiązania.

Oto jak można go używać (UWAGA: nie testowałem tego):

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; 

(Uwaga:. DBMS_SQL.TO_REFCURSOR nowego w Oracle 11g)

+0

+1, lubię używać DBMS_SQL, ponieważ jest bardziej "obsługiwany", ale nieco bardziej zaangażowany niż sugerowana przeze mnie metoda. – Ollie

0

Jak o

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

podobnie jak "%" nie wpływa na wartość null. więc jeśli niektóre z tych pól mają wartość null, otrzymasz fałsz –

1

Podejście, którego używam, to uwzględnienie w dynamicznym zapytaniu SQL i ELSE, stwierdza stan odwrotny od IF. Twój kod testuje, że pName nie ma wartości null, więc dodałbym do wygenerowanego zapytania zapytanie, że pName IS Null. W ten sposób można za każdym razem przekazać te same parametry bez wpływu na wyniki zapytania.

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;