Параметры в запросе с предложением in?

Я хочу использовать параметр для запроса следующим образом:

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

поэтому я думаю об этом

SELECT * FROM MATABLE
WHERE MT_ID IN (:MYPARAM)

но это не работает...

Есть ли способ сделать это ?

На самом деле я использую IBX и Firebird 2.1.

Я не знаю, сколько параметров в предложении IN.


person Hugues Van Landeghem    schedule 17.11.2009    source источник
comment
Сам AFAIK SQL не допускает параметров в предложениях IN. Некоторые обходные пути работают, смотрите другие ответы, но помните о рисках SQL-инъекций.   -  person    schedule 17.11.2009
comment
Недавно я пытался сделать то же самое с MS SQL Server, и это тоже не сработало.   -  person J__    schedule 17.11.2009


Ответы (11)


В итоге я использовал глобальную временную таблицу в Firebird, сначала вставив значения параметров, а для получения результатов я использую обычное предложение JOIN вместо WHERE ... IN. Временная таблица зависит от транзакции и очищается при фиксации (ON COMMIT DELETE ROWS).

person Ondrej Kelle    schedule 17.11.2009

Кому еще интересно. Я сделал это в Firebird 2.5, используя другую хранимую процедуру, вдохновленную этим постом.

Как разделить строку, разделенную запятыми, внутри хранимой процедуры?

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

При прохождении SP следующий список

Список разделенных запятыми = 1,2,3,4

и позвони

SELECT * FROM SPLIT_STRING(:CommaSeperatedList)

результат будет:

RESULT
1
2
3
4

А можно использовать следующим образом:

SELECT * FROM MyTable where MyKeyField in ( SELECT * FROM SPLIT_STRING(:CommaSeperatedList) )
person Plofstoffel    schedule 24.04.2012
comment
Если вам нужен результат в виде целого числа, вы меняете тип вывода с varchar(255) на целое число и заменяете RESULT = :TEMPSTR; этим RESULT = cast(:TEMPSTR as integer);. - person Petr Voborník; 06.01.2015

Возможно, вам следует написать это так:

SELECT * FROM MATABLE
WHERE MT_ID IN (:MYPARAM1 , :MYPARAM2)
person Yurish    schedule 17.11.2009
comment
Это должно работать, однако, если его список фильтров не фиксирован, ему придется создавать каждое условие отдельно. - person yozey; 17.11.2009
comment
Я уже сделал это, но мне нужно создать SQL вручную, что зависит от того, сколько параметров мне нужно. Я хочу просто SQL - person Hugues Van Landeghem; 17.11.2009

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

Я использовал этот метод пару раз, хотя он не использует параметры. Он использует список строк и свойство DelimitedText. Вы создаете IDList и заполняете его своими идентификаторами.

Query.SQL.Add(Format('MT_ID IN (%s)', [IDList.DelimitedText]));
person johnny    schedule 17.11.2009
comment
Что с этим не так? См. stackoverflow.com/questions/332365/ - person mghie; 17.11.2009
comment
@mghie, да, это ужасно неправильно, если мы говорим о пользовательском вводе, но я сделал предположение, что, поскольку это список идентификаторов, пользователь не предоставит его. В этом случае я ожидал коды продуктов, номера счетов-фактур и т. д. Возможно, это была моя ошибка, и я благодарю вас за улучшение моего ответа. - person johnny; 17.11.2009
comment
Возможно, вы правы в том, что в данном конкретном случае SQL-инъекция может быть невозможна, но это реальная угроза, и людям кажется, что ее настолько сложно понять/запомнить, что я думаю, что нужно быть последовательным и вообще не делать подобных вещей. - person mghie; 18.11.2009
comment
+1. Если список IN относительно короткий, это ИМХО самый простой и быстрый подход. (Внедрения SQL можно избежать, если вы обратите внимание на то, что делаете) - person kobik; 29.10.2012

Вам также может быть интересно прочитать следующее:
http://www.sommarskog.se/dynamic_sql.html
и
http://www.sommarskog.se/arrays-in-sql-2005.html

Охватывает динамический sql с предложениями «in» и всеми видами. Очень интересно.

person shunty    schedule 17.11.2009
comment
Совершенно верно: очень интересно. - person Hugues Van Landeghem; 17.11.2009

Параметры являются заполнителями для отдельных значений, это означает, что предложение IN, которое принимает список значений, разделенных запятыми, не может использоваться с параметрами.

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

Итак, в таком предложении: IN (:param)

Я могу привязать переменную к значению, но только к 1 значению, например: IN (4)

Теперь, если вы рассмотрите «выражение значения предложения IN», вы получите строку значений: IN (1, 4, 6) -> это 3 значения с запятыми между ними. Это часть строки SQL, а не часть значения, поэтому ее нельзя связать с параметром.

Очевидно, это не то, что вам нужно, но это единственное, что возможно с параметрами.

person Martijn Tonies    schedule 16.03.2010

Ответ от Юриша — решение в двух случаях из трёх:

  • если у вас есть ограниченное количество элементов, которые нужно добавить в предложение in
  • или, если вы хотите создать параметры на лету для каждого необходимого элемента (вы не знаете количество элементов во время разработки)

Но если вы хотите иметь произвольное количество элементов, а иногда вообще не иметь элементов, то вы можете сгенерировать оператор SLQ на лету. Использование формата помогает.

person Mihaela    schedule 17.11.2009
comment
Да, мне нужно произвольное количество элементов, и я не хочу генерировать оператор SQL. - person Hugues Van Landeghem; 17.11.2009
comment
Я не думаю, что это возможно, но я хочу, чтобы я ошибался. Я хотел сделать именно это и решил написать SQL самостоятельно. Подготовленные запросы имеют смысл только в том случае, если механизм БД знает, чего ожидать (сколько параметров, а также их соответствующие типы). Даже если бы был способ параметризовать произвольное количество входных данных, я думаю, что не было бы прироста производительности по сравнению с тем, чтобы каждый раз удивлять механизм БД новым запросом. - person Mihaela; 17.11.2009
comment
Использование параметров необходимо не только для повышения производительности, но и для защиты от атак путем внедрения кода SQL. Либо вы тратите много усилий на очистку строк входных параметров, либо обрабатываете их как параметры, которыми они являются. - person mghie; 17.11.2009
comment
Необходимо только в том случае, если рассматриваемый случай SQL подвержен атаке путем внедрения. Если SQL отформатирован кодом, преобразующим целочисленные значения в памяти из свойств (.ID) некоторых объектов для встраивания в какой-либо литеральный оператор SQL, то вероятность атаки путем внедрения незначительна. Тот факт, что вопрос касается SQL и параметров, не означает, что все вопросы, связанные с SQL и параметрами, обязательно применимы. производительность afaic - единственная серьезная проблема в этом случае, imho, и я бы с осторожностью предполагал, что форматированный SQL будет неприемлем в этом отношении без тестирования. - person Deltics; 17.11.2009
comment
@Deltics: я думаю, что лучше перестраховаться. Я не думаю, что разработчики проводят тщательный анализ рисков каждый раз, когда создают подобные операторы SQL. Поэтому ИМХО лучше вообще отказаться от практики. - person mghie; 18.11.2009

SELECT * FROM MATABLE WHERE MT_ID IN (:MYPARAM) вместо использования MYPARAM с : используйте имя параметра.

Например, SELECT * FROM MATABLE WHERE MT_ID IN (SELECT REGEXP_SUBSTR(**MYPARAM,'[^,]+', 1, LEVEL) FROM DUAL CONNECT BY REGEXP_SUBSTR(MYPARAM, '[^,]+', 1, УРОВЕНЬ) НЕ НУЛЬ))**

МАЙПАРАМ-'368134,181956'

person Rajesh D    schedule 30.12.2015

Если вы используете Oracle, вам обязательно следует прочитать запись в блоге Тома Кайта именно на эту тему (ссылка).

Следуя примеру г-на Кайта, вот пример:

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)

Где вы бы привязали :MYPARAM к '368134,181956' в вашем случае.

person Tom    schedule 17.11.2009
comment
Режим Oracle не работает для Firebird, но мне кажется, что это то, что мне нужно. - person Hugues Van Landeghem; 17.11.2009

Вот метод, который я использовал в прошлом, чтобы обойти эту проблему с оператором IN. Он строит список «ИЛИ» на основе количества значений, указанных в параметрах (уникальных). Затем все, что мне нужно было сделать, это добавить параметры в порядке их появления в предоставленном списке значений.

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;

Надеюсь это поможет.

person yozey    schedule 17.11.2009
comment
уже сделал это в одном проекте, но моя цель была без кода паскаля - person Hugues Van Landeghem; 17.11.2009
comment
Это было бы очень трудно. Я много лет боролся с этим. Если у вас есть компоненты, поддерживающие макросы, вы можете это сделать, но с использованием параметров я сомневаюсь в этом, а на стороне сервера я не смог этого сделать. - person yozey; 17.11.2009

Есть один прием, позволяющий использовать обратное условие SQL LIKE.

Вы передаете список как параметр строки (VARCHAR), например '~12~23~46~567~'

Тогда у вас есть запрос типа where ... :List_Param LIKE ('%~' || CAST( NumField AS VARCHAR(20)) || '~%')

person Arioch 'The    schedule 29.10.2012