2012-04-10 1 views
12

У меня есть список целых чисел или строк и нужно передать его в качестве параметра для Delphi DataSet. Как это сделать?Delphi: как передать список в качестве параметра SQL-запроса?

Вот пример. MyQuery что-то вроде:

select * from myTable where intKey in :listParam 

Я бы установить параметр в виде списка или массива или что-то другое:

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

и это привело бы в этом запросе, посланном серверу SQL:

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

было бы еще лучше, если решение будет также работать со строками, что делает этот запрос:

select * from myTable where stringKey in :listParam 

стали:

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

Я считаю, что это простой вопрос, но «IN» не является хорошим ключевое слово для поиска в Интернете.

Пожалуйста, ответьте, как я должен настроить параметр в IDE, запрос и как передать параметры.

Я использую Delphi 7.

Отредактировано: Я рассматриваю, что ответ «это невозможно сделать непосредственно». Если кто-то даст мне не-хакерский ответ, принятый ответ будет изменен.

+3

Вы можете К сожалению. Это недостаток в языке SQL: у него нет понятия «тип списка». –

+0

Возможно, в зависимости от используемой СУБД могут быть некоторые варианты. Что вы используете? SQL Server, Oracle, ....? –

+0

@MikaelEriksson: Я использую Sql Server, но я считаю, что это проблема языка Delphi. – neves

ответ

11

AFAIK, невозможно напрямую.

Вам нужно будет преобразовать список в список SQL в виде простого текста.

Например:

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; 

Для использования в качестве таковых:

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

Конечно, это мое текущее решение, но я хочу передать его как параметр вместо изменения свойства SQL. Конкатенация не позволит СУБД подготовить запрос и разрешить атаки на SQL-инъекции. – neves

+1

Параметризация по-прежнему возможна (хотя вам необходимо обновить свойство SQL). См. Мой длинный ответ ниже. –

+3

@neves Две функции, описанные выше, не позволят атакам SQL-инъекций, поскольку первый будет «указывать» предоставленный текст, а второй будет создавать значения из целых чисел. С современными базами данных (по крайней мере, с Oracle, тот, который я знаю), он не будет медленным - просто убедитесь, что у вас есть правильный индекс для ключа. Внутренне БД подготовит оператор SQL и повторно использует выражение «in()» как один параметр в плане выполнения. Это SQL: код, который вы хотите, а не как вы хотите, чтобы DB его извлекала. –

1

Создать временную таблицу и вставить свои значения в нем. Затем используйте эту таблицу как часть подзапроса.

Например, создайте MyListTable в своей базе данных. Вставьте свои значения в MyListTable. Затем выполните

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

Это позволяет избежать атак SQL-инъекций. Но это не изящно, это не удобно, потому что вам нужно вставлять записи перед запуском вашего запроса и приводить к проблемам параллелизма.

Не мой первый выбор для решения вашей ситуации, но он касается вашей озабоченности по поводу внедрения sql.

0

Я использую замену «IN».Вот запрос я использую:

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

код до отправки параметров:

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

Массив значение элемента может частично совпадать некоторые другие значения. Например, «1» может быть частью «100». Чтобы защитить его, я использую запятую как разделитель

4

SQL принимает только одиночные значения в качестве параметров, поэтому вы не можете создать оператор с одним параметром, который может отображать переменное количество значений, например, приведенный вами пример.

Однако вы все равно можете использовать параметризованный SQL в этой ситуации. Решение состоит в том, чтобы перебирать список имеющихся значений, добавляя маркер параметра к SQL и параметр в список параметров для каждого значения.

Это проще всего сделать с позиционными, а не с именованными параметрами, но может быть адаптировано и для именованных параметров (вам может понадобиться отредактировать этот код, поскольку я не имею Delphi и не помню синтаксис создания параметров) :

//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 + ')'; 

Это приведет к параметризированному запросу с инъекцией.

+0

Это первый раз, когда я увидел кого-то, использующего 'Parameters.Add'. Не назначит ли CommandText их переопределение? – nurettin

3

Существует несколько вариантов для вас, но в основном вам нужно, чтобы ваши ценности были в таблице. Я бы предложил для этого переменную таблицы.

Вот версия, которая распаковывает список 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) 

Возможно иметь многозадачные запросы. Параметр :listParam должен быть строкой:

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

Вы можете использовать тот же метод для строк. Вам просто нужно изменить тип данных ID, например, varchar(10).

Вместо распаковки с помощью цикла в то время как вы могли бы использовать в 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) 

Строка пары могут выглядеть следующим образом:

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

... и это не сработает, если одна из ваших строк будет иметь символ «,» в пределах своего содержимого (что, вероятно, ...) –

+0

@ArnaudBouchez - запятая не является обязательным условием для этого решения. Это может быть любой символ * вашего * выбора. Вы можете даже добавить разделитель как собственный параметр. В любом случае, если у вас очень разнообразные данные, в которых вы не можете определить разделитель, вы можете прибегнуть к настройке XML-строки вместо того, что вы клонируете в таблицу в TSQL-коде. –

+0

@ArnaudBouchez - Будет ли это интересно для вас, если я добавлю также версию XML? –

0

Почему бы не сделать динамический SQL:

Быстрые и грязные, но все еще используемые параметры. проверить 10 элементов. Я не знаю, насколько это хорошо.

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

Если кто-то еще с той же проблемой, если вы используете firedac вы можете использовать макросы, как это:

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

Настройка макроса ->MyQuery.MacroByName('listParam').AsRaw := '1, 2, 3';

Смежные вопросы