2009-11-17 23 views
10

voglio utilizzare il parametro per la query come questa:Parametri nella query con clausola?

SELECT * FROM MATABLE 
WHERE MT_ID IN (368134, 181956) 

quindi penso che su questo

SELECT * FROM MATABLE 
WHERE MT_ID IN (:MYPARAM) 

ma non funziona ...

C'è un modo per farlo Questo ?

Io in realtà uso IBX e Firebird 2.1

Non so quanti parametri nella clausola.

+3

per quanto ne so SQL per sé non consente parametri di clausole. Alcuni workaround funzionano, vedi altre risposte, ma sii consapevole dei rischi di SQL injection. –

+1

Ho provato di recente a fare la stessa cosa con MS SQL Server e anche questo non ha funzionato. –

risposta

5

Ho finito per utilizzare una tabella temporanea globale in Firebird, inserendo prima i valori dei parametri e per recuperare i risultati, utilizzo una regolare JOIN anziché una clausola WHERE ... IN. La tabella temporanea è specifica della transazione e deselezionata per il commit (ON COMMIT DELETE ROWS).

4

Forse si dovrebbe wite in questo modo:

SELECT * FROM MATABLE 
WHERE MT_ID IN (:MYPARAM1 , :MYPARAM2) 
+0

Questo dovrebbe funzionare, tuttavia se il suo elenco di filtri non è fisso dovrà costruire singolarmente ogni condizione. – yozey

+0

Lo faccio già, ma devo creare manualmente SQL che dipende da quanti parametri ho bisogno. Voglio solo da SQL –

0

Se si utilizza Oracle, allora si dovrebbe verificare post sul blog di Tom Kyte esattamente su questo argomento (link).

Seguendo le orme di Mr Kyte, ecco un esempio:

SELECT * 
    FROM MATABLE 
WHERE MT_ID IN 
     (SELECT TRIM(substr(text, instr(text, sep, 1, LEVEL) + 1, 
          instr(text, sep, 1, LEVEL + 1) - 
          instr(text, sep, 1, LEVEL) - 1)) AS token 
      FROM (SELECT sep, sep || :myparam || sep AS text 
        FROM (SELECT ',' AS sep 
          FROM dual)) 
     CONNECT BY LEVEL <= length(text) - length(REPLACE(text, sep, '')) - 1) 

Dove si legano :MYPARAM-'368134,181956' nel tuo caso.

+0

La modalità Oracle non funziona per Firebird ma questo sembra essere quello che mi serve –

1

La risposta da yurish è una soluzione in due dei tre casi:

  • se si dispone di un numero limitato di elementi da aggiungere al tuo nella clausola
  • o, se si è disposti a creare parametri al volo per ogni elemento necessario (non si conosce il numero di elementi in fase di progettazione)

Ma se si desidera avere un numero arbitrario di elementi, e talvolta nessun elemento, è possibile generare Dichiarazione SLQ al volo. L'utilizzo del formato aiuta.

+0

Sì, voglio un numero arbitrario di elementi e non voglio generare istruzioni SQL. –

+0

Non credo sia possibile, ma vorrei che mi sbaglio. Volevo fare proprio questo e ho deciso di scrivere io stesso l'SQL. Le query preparate hanno senso solo nel motore DB sa cosa aspettarsi (quanti parametri e i loro rispettivi tipi). Anche se ci fosse un modo per parametrizzare un numero arbitrario di input, penso che non ci sarebbe alcun guadagno in termini di prestazioni rispetto al solo "sorprendente" motore DB con la nuova query ogni volta. – Mihaela

+0

L'utilizzo dei parametri non è solo per migliorare le prestazioni, è necessario proteggersi dagli attacchi di SQL injection. O si impiegano molte energie per sanificare le stringhe dei parametri di input o gestirle come parametri. – mghie

3

Non penso che sia qualcosa che può essere fatto. C'è qualche ragione particolare per cui non vuoi costruire la query tu stesso?

Ho usato questo metodo un paio di volte, tuttavia non utilizza i parametri. Usa una lista di stringhe e la sua proprietà DelimitedText. Si crea una lista ID e la si popola con i propri ID.

Query.SQL.Add(Format('MT_ID IN (%s)', [IDList.DelimitedText])); 
+0

Cosa c'è di sbagliato in questo? Vedi http://stackoverflow.com/questions/332365/xkcd-sql-injection-please-explain – mghie

+1

@mghie, sì, è orribilmente sbagliato se stiamo parlando dell'input dell'utente, ma ho fatto l'ipotesi che dal momento che si tratta di un elenco di ID non è qualcosa che l'utente avrebbe fornito. In quel caso mi aspettavo codici prodotto, numeri di fattura, ecc. Potrebbe essere stato un errore da parte mia e ti ringrazio per aver migliorato la mia risposta. – johnny

+0

Potresti avere ragione che in questo caso particolare l'iniezione SQL potrebbe non essere possibile, ma è una vera minaccia e sembra essere così difficile per le persone capire/ricordare che penso che uno dovrebbe essere conseguente e non fare cose del genere, per niente . – mghie

0

Ecco una tecnica che ho utilizzato in passato per aggirare il problema dell'istruzione "IN".Crea un elenco "OR" basato sulla quantità di valori specificati con parametri (unici). Quindi tutto ciò che dovevo fare era aggiungere i parametri nell'ordine in cui apparivano nell'elenco dei valori forniti.

var 
    FilterValues: TStringList; 
    i: Integer; 
    FilterList: String; 
    Values: String; 
    FieldName: String; 
begin 
    Query.SQL.Text := 'SELECT * FROM table WHERE '; // set base sql 
    FieldName := 'some_id'; // field to filter on 
    Values := '1,4,97'; // list of supplied values in delimited format 
    FilterList := ''; 
    FilterValues := TStringList.Create; // will get the supplied values so we can loop 
    try 
    FilterValues.CommaText := Values; 

    for i := 0 to FilterValues.Count - 1 do 
    begin 
     if FilterList = '' then 
     FilterList := Format('%s=:param%u', [FieldName, i]) // build the filter list 
     else 
     FilterList := Format('%s OR %s=:param%u', [FilterList, FieldName, i]); // and an OR 
    end; 
    Query.SQL.Text := Query.SQL.Text + FilterList; // append the OR list to the base sql 

    // ShowMessage(FilterList); // see what the list looks like. 
    if Query.ParamCount <> FilterValues.Count then 
     raise Exception.Create('Param count and Value count differs.'); // check to make sure the supplied values have parameters built for them 

    for i := 0 to FilterValues.Count - 1 do 
    begin 
     Query.Params[i].Value := FilterValues[i]; // now add the values 
    end; 

    Query.Open; 
finally 
    FilterValues.Free; 
end; 

Spero che questo aiuti.

+0

l'ho già fatto in un progetto ma il mio obiettivo era senza codice pascal –

+0

Sarebbe molto difficile. Ho lottato per anni con quello. Se si dispone di componenti che supportano le macro, è possibile farlo, ma utilizzando i parametri che dubito e sul lato server, non sono stato in grado di farlo. – yozey

2

I parametri sono segnaposti per valori singoli, ciò significa che una clausola IN, che accetta un elenco di valori delimitati da virgole, non può essere utilizzata con i parametri.

Pensateci in questo modo: ovunque io inserisca un valore, posso usare un parametro.

Così, in una clausola del genere: IN (: param)

posso legare la variabile a un valore, ma solo 1 valore, ad es: IN (4)

Ora, se si considera un "Espressione del valore di clausola IN", ottieni una stringa di valori: IN (1, 4, 6) -> si tratta di 3 valori con virgole tra di loro. Fa parte della stringa SQL, non parte di un valore, motivo per cui non può essere associato a un parametro.

Ovviamente, questo non è quello che vuoi, ma è l'unica cosa possibile con i parametri.

9

Per chi è ancora interessato. L'ho fatto in Firebird 2.5 usando un'altra stored procedure ispirata a questo post.

How to split comma separated string inside stored procedure?

CREATE OR ALTER PROCEDURE SPLIT_STRING (
    ainput varchar(8192)) 
RETURNS (
    result varchar(255)) 
AS 
DECLARE variable lastpos integer; 
DECLARE variable nextpos integer; 
DECLARE variable tempstr varchar(8192); 
BEGIN 
    AINPUT = :AINPUT || ','; 
    LASTPOS = 1; 
    NEXTPOS = position(',', :AINPUT, LASTPOS); 
    WHILE (:NEXTPOS > 1) do 
    BEGIN 
    TEMPSTR = substring(:AINPUT from :LASTPOS for :NEXTPOS - :LASTPOS); 

    RESULT = :TEMPSTR; 
    LASTPOS = :NEXTPOS + 1; 
    NEXTPOS = position(',', :AINPUT, LASTPOS); 
    suspend; 
    END 

END 

Quando si passa la SP seguente elenco

CommaSeperatedList = 1,2,3,4

e chiamare

SELECT * FROM SPLIT_STRING(:CommaSeperatedList) 

il risultato sarà :

RESULT 
1 
2 
3 
4 

e può essere utilizzato come segue:

SELECT * FROM MyTable where MyKeyField in (SELECT * FROM SPLIT_STRING(:CommaSeperatedList)) 
+0

Se hai bisogno di reasult come numero intero, cambi il tipo di output da varchar (255) a intero e sostituisci 'RESULT =: TEMPSTR;' di questo 'RESULT = cast (: TEMPSTR come intero);'. –

0

C'è un trucco per usare invertito SQL LIKE condizione.

si passa la lista come stringa (VARCHAR) parametro come '~12~23~46~567~'

Poi u ha interrogazione come where ... :List_Param LIKE ('%~' || CAST(NumField AS VARCHAR(20)) || '~%')

0
CREATE PROCEDURE TRY_LIST (PARAM_LIST VARCHAR(255)) RETURNS (FIELD1....) 
AS 
BEGIN 
/* Check if :PARAM_LIST begins with colon "," and ands with colon "," 
    the list should look like this --> eg. **",1,3,4,66,778,33,"**   
    if the format of list is right then GO if not just add then colons 
*/ 
IF (NOT SUBSTRING(:PARAM_LIST FROM 1 FOR 1)=',') THEN PARAM_LIST=','||PARAM_LIST; 
IF (NOT SUBSTRING(:PARAM_LIST FROM CHAR_LENGTH(:PARAM_LIST) FOR 1)=',') THEN PARAM_LIST=PARAM_LIST||','; 

/* Now you are shure thet :PARAM_LIST format is correct */ 
/* NOW ! */ 
FOR SELECT * FROM MY_TABLE WHERE POSITION(','||MY_FIELD||',' in :PARAM_LIST)>0 
INTO :FIELD1, :FIELD2 etc... DO 
BEGIN 
    SUSPEND; 
END 

END 

How to use it. 

SELECT * FROM TRY_LIST('3,4,544,87,66,23') 
or SELECT * FROM TRY_LIST(',3,4,544,87,66,23,') 
if the list have to be longer then 255 characters then just change the part of header f.eg. like PARAM_LIST VARCHAR(4000) 
1

SELECT * FROM accoppiabili DOVE MT_ID IN (: MyParam) invece di utilizzare MyParam con :, utilizzare il nome del parametro.

come SELECT * FROM accoppiabili DOVE MT_ID IN (SELECT REGEXP_SUBSTR (** MyParam, '[^,] +', 1, LIVELLO) FROM DUAL CONNECT BY REGEXP_SUBSTR (MyParam, '[^ ,] +', 1, LEVEL) non è NULL)) **

MYPARAM- '368134,181956'

Problemi correlati