Подтвердить что ты не робот

Delphi: как передать список в качестве параметра SQL-запроса?

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

Вот пример. 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.

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

4b9b3361

Ответ 1

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']);

Ответ 2

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

Это создаст запрос с параметрами, оптимизированный для инъекций.

Ответ 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).

Вместо распаковки с циклом while вы можете использовать функцию разделения

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';

Ответ 4

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

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

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

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

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

Ответ 5

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

Запрос → "select * from myTable where intKey in (&listParam)"

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

Ответ 6

Я использую некоторую замену "IN". Вот запрос, который я использую:

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

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

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

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

Ответ 7

Почему бы не сделать динамический 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;