2012-04-10 19 views
12

Ho una lista di interi o di stringhe e ho bisogno di passarlo come parametro per un DataSet Delphi. Come farlo?Delphi: come passare un elenco come parametro a una query SQL?

Ecco un esempio. MyQuery è qualcosa di simile:

select * from myTable where intKey in :listParam 

mi piacerebbe impostare un parametro come una lista o un array o qualcosa d'altro:

MyQuery.ParamByName('listParam').AsSomething := [1,2,3]; 

e si tradurrebbe in questa query inviata al server sql:

select * from myTable where intKey in (1, 2, 3) 

sarebbe ancora meglio se la soluzione sarebbe anche lavorare con le stringhe, rendendo questa query:

select * from myTable where stringKey in :listParam 

diventano:

select * from myTable where stringKey in ('a', 'b', 'c') 

Credo che questa sia una domanda semplice, ma "IN" non è una buona parola chiave per la ricerca sul Web.

Si prega di rispondere come dovrei configurare il parametro nell'IDE, la query e come passare i parametri.

sto usando Delphi 7.

A cura: Sto valutando la risposta è "non è possibile fare direttamente". Se qualcuno mi dà una risposta non hacker, la risposta accettata verrà cambiata.

+3

Puoi purtroppo. È una deficienza nel linguaggio SQL: non ha alcun concetto di "un tipo di lista". –

+0

Potrebbero esserci alcune opzioni a seconda del DBMS che si utilizza. Cosa stai usando? SQL Server, Oracle, ....? –

+0

@ MikaelEriksson: sto utilizzando SQL Server, ma credo che sia un problema di lingua Delphi. – neves

risposta

11

AFAIK, non è possibile direttamente.

Dovrai convertire l'elenco in un elenco SQL in testo normale.

Per esempio:

function ListToText(const Args: array of string): string; overload; 
var i: integer; 
begin 
    result := '('; 
    for i := 0 to high(Args) do 
    result := result+QuotedStr(Args[i])+','; 
    result[length(result)] := ')'; 
end; 


function ListToText(const Args: array of integer): string; overload; 
var i: integer; 
begin 
    result := '('; 
    for i := 0 to high(Args) do 
    result := result+IntToStr(Args[i])+','; 
    result[length(result)] := ')'; 
end; 

per essere usato come tale:

SQL.Text := 'select * from myTable where intKey in '+ListToText([1,2,3]); 
SQL.Text := 'select * from myTable where stringKey in '+ListToText(['a','b','c']); 
+0

Certo, questa è la mia soluzione attuale, ma voglio passarlo come parametro invece di modificare la proprietà SQL. La concatenazione non consente al DBMS di preparare la query e consentire attacchi di SQL injection. – neves

+1

La parametrizzazione è ancora possibile (sebbene sia necessario aggiornare la proprietà SQL). Guarda la mia risposta prolissa di seguito. –

+3

@neves Le due funzioni sopra non permetteranno attacchi SQL injection, dal momento che il primo "citerà" il testo fornito, e il secondo creerà valori da interi. Con i moderni database (almeno con Oracle, quello che conosco), non sarà lento, basta assicurarsi di avere un indice adeguato sulla chiave. Internamente, il DB preparerà l'istruzione SQL e riutilizzerà l'intera espressione "in()" come un parametro nel suo piano di esecuzione. È SQL: codifica ciò che vuoi, non come vuoi che il DB lo recuperi. –

1

creare una tabella temporanea e inserire i valori in esso. Quindi usa quella tabella come parte di una sottoquery.

Ad esempio, creare MyListTable nel database. Inserisci i tuoi valori in MyListTable. Quindi fare

select * from myTable where keyvalue in (select keyvalue from MyListTable) 

Ciò evita attacchi di SQL injection. Ma non è elegante, non è performante perché devi inserire i record prima di eseguire la query e può portare a problemi di concorrenza.

Non è la mia prima scelta per gestire la situazione, ma risolve il problema relativo all'iniezione sql.

0

Uso alcune sostituzioni "IN".L'interrogazione che uso:

SELECT * FROM MyTable WHERE CHARINDEX(','+cast(intKey as varchar(10))+',', :listParam) > 0 

il codice per inviare parametro: valore dell'articolo

MyQuery.ParamByName('listParam').AsString := ',1,2,3,'; 

La matrice può corrispondere parzialmente alcuni altri valori. Ad esempio, "1" può essere parte di "100". Per proteggerlo, utilizzo la virgola come delimitatore

4

SQL accetta solo valori singoli come parametri, quindi non è possibile creare un'istruzione con un parametro che può eseguire il mapping su un numero variabile di valori, come nell'esempio fornito.

Tuttavia, è possibile utilizzare SQL parametrizzato in questa situazione. La soluzione è quella di scorrere l'elenco dei valori che hai, aggiungendo un marcatore di parametro all'SQL e un parametro all'elenco dei parametri per ogni valore.

Questo è più facile da fare con i parametri posizionali piuttosto che con i nomi, ma può essere adattato anche per i parametri denominati (potrebbe essere necessario regolare questo codice poiché non ho Delphi disponibile e non ricordo la sintassi di creazione dei parametri) :

//AValues is an array of variant values 
//SQLCommand is some TDataSet component with Parameters. 
for I := Low(AValues) to High(AValues) do 
begin 

    if ParamString = '' then 
     ParamString = '?' 
    else 
     ParamString = ParamString + ', ?'; 

    SQLCommand.Parameters.Add(AValues[I]); 

    end 

    SQLCommand.CommandText = 
    'SELECT * FROM MyTable WHERE KeyValue IN (' + ParamString + ')'; 

Ciò produrrà una query con parametri di sicurezza per l'iniezione.

+0

Questa è la prima volta che vedo qualcuno usare 'Parameters.Add'. L'assegnazione di CommandText non li sovrascriverà? – nurettin

3

Ci sono diverse opzioni per te, ma in pratica devi inserire i tuoi valori in una tabella. Suggerirei una variabile di tabella per quello.

Ecco una versione che decomprime un elenco int.

declare @IDs varchar(max) 
set @IDs = :listParam 

set @IDs = @IDs+',' 

declare @T table(ID int primary key) 

while len(@IDs) > 1 
begin 
    insert into @T(ID) values (left(@IDs, charindex(',', @IDs)-1)) 
    set @IDs = stuff(@IDs, 1, charindex(',', @IDs), '') 
end 

select * 
from myTable 
where intKey in (select ID from @T) 

È possibile avere query multi-statement. Il parametro :listParam dovrebbe essere una stringa:

MyQuery.ParamByName('listParam').AsString := '1,2,3'; 

È possibile utilizzare la stessa tecnica per le stringhe. Hai solo bisogno di cambiare il tipo di dati di ID ad esempio varchar(10).

Invece di disimballaggio con un ciclo while si potrebbe fare uso di un split function

declare @T table(ID varchar(10)) 

insert into @T 
select s 
from dbo.Split(',', :listParam) 

select * 
from myTable 
where charKey in (select ID from @T) 

Un param stringa potrebbe essere la seguente:

MyQuery.ParamByName('listParam').AsString := 'Adam,Bertil,Caesar'; 
+0

... e non funzionerà se una delle tue stringhe avrà un carattere "," all'interno del suo contenuto (che è probabile) ... –

+0

@ArnaudBouchez - La virgola non è un requisito per questa soluzione. Può essere qualsiasi carattere della * tua * scelta. È anche possibile aggiungere il delimitatore come parametro a sé stante. In ogni caso, se hai dati molto diversi in cui non riesci a trovare un delimitatore, puoi ricorrere a una stringa XML invece di distruggerti in una tabella in codice TSQL. –

+0

@ArnaudBouchez - Ti interesserebbe se aggiungo anche la versione XML? –

0

Perché non fare una SQL dinamico:

Veloce e sporco, ma utilizza ancora i parametri. controlla 10 elementi. Non so quanto bene questo scale.

MyQuerySQL.Text:='SELECT * FROM myTable WHERE intKey in (:listParam0' 
    for i := 1 to 9 do begin 
     MyQuerySQL.Text := MyQuerySQL.Text + ',:listParam'+IntToStr(i) 
    end; 
    MyQuerySQL.Text := MyQuerySQL.Text+')'; 
    for i:=0 to 9 do begin 
     MyQuery.ParamByName('listParam'+IntToStr(i)).AsInteger := ArrayofInt[0]; 
    end; 
1

Se qualcuno continui ad avere lo stesso problema, se si utilizza firedac è possibile utilizzare le macro come questo:

Query ->"select * from myTable where intKey in (&listParam)"

Impostazione della macro ->MyQuery.MacroByName('listParam').AsRaw := '1, 2, 3';