【问题标题】:Parameters in query with in clause?带有in子句的查询参数?
【发布时间】:2010-12-17 09:00:13
【问题描述】:

我想像这样使用参数进行查询:

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

所以我想到了这个

SELECT * FROM MATABLE
WHERE MT_ID IN (:MYPARAM)

但它不起作用......

有没有办法做到这一点?

我实际上使用的是 IBX 和 Firebird 2.1

不知道IN子句有多少个参数。

【问题讨论】:

  • AFAIK SQL 本身不允许在 IN 子句中使用参数。一些解决方法有效,请参阅其他答案,但请注意 SQL 注入风险。
  • 我最近尝试用 MS SQL Server 做同样的事情,但也没有用。

标签: sql delphi firebird


【解决方案1】:

也许你应该这样写:

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

【讨论】:

  • 这应该可以,但是如果他的过滤器列表不固定,他将不得不单独构建每个条件。
  • 我已经做了这个,但我必须手动创建 SQL,这取决于我需要多少参数。我只想要 SQL
【解决方案2】:

如果您使用的是 Oracle,那么您绝对应该查看 Tom Kyte 关于这个主题的博文 (link)。

在 Kyte 先生的带领下,举个例子:

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'

【讨论】:

  • Oracle 模式不适用于 Firebird,但这似乎是我需要的
【解决方案3】:

Yurish 给出的答案是三分之二的解决方案:

  • 如果要添加到 in 子句的项目数量有限
  • 或者,如果您愿意为每个需要的元素动态创建参数(您不知道设计时元素的数量)

但是,如果您想拥有任意数量的元素,有时甚至根本没有元素,那么您可以即时生成 SLQ 语句。使用格式会有所帮助。

【讨论】:

  • 是的,我想要任意数量的元素,我不想生成 SQL 语句。
  • 我不认为这是可能的,但我希望我错了。我想这样做并决定自己编写 SQL。准备好的查询只有在数据库引擎知道会发生什么(有多少参数以及它们各自的类型)时才有意义。即使有一种方法可以对任意数量的输入进行参数化,我认为与每次使用新查询“令人惊讶”的数据库引擎相比,不会有任何性能提升。
  • 使用参数不仅是为了提高性能,还需要防止 SQL 注入攻击。要么花费大量精力清理输入参数字符串,要么将它们作为参数处理。
  • “必要”仅当所讨论的 SQL 案例易于受到注入攻击时。如果 SQL 是通过代码将内存中的整数值从某些对象的属性 (.ID) 转换为嵌入到一些文字 SQL 语句中来格式化的,那么注入攻击的可能性可以忽略不计。仅仅因为一个问题涉及 SQL 和参数并不意味着所有涉及 SQL 和参数的问题都必须适用。在这种情况下,afaic 性能是唯一重要的问题,恕我直言,我会谨慎假设未经测试格式化的 SQL 在该分数上是不可接受的。
  • @Deltics:我认为最好还是谨慎行事。我不认为开发人员每次构建这样的 SQL 语句时都会进行彻底的风险分析。这就是恕我直言,最好完全放弃这种做法的原因。
【解决方案4】:

我最终在 Firebird 中使用了一个全局临时表,首先插入参数值并检索结果,我使用常规 JOIN 而不是 WHERE ... IN 子句。临时表是特定于事务的,并在提交时清除 (ON COMMIT DELETE ROWS)。

【讨论】:

    【解决方案5】:

    我不认为这是可以做到的。您是否有什么特别的原因不想自己构建查询?

    这个方法我用过几次,不过它不使用参数。它使用一个字符串列表和它的属性 DelimitedText。您创建一个 IDList 并使用您的 ID 填充它。

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

    【讨论】:

    • 这有什么问题?见stackoverflow.com/questions/332365/…
    • @mghie,是的,如果我们谈论的是用户输入,那就大错特错了,但我假设因为它是一个 ID 列表,所以用户不会提供它。在那种情况下,我期望的是产品代码、发票号码等。这可能是我的错误,我感谢您改进我的回答。
    • 你可能是对的,在这种特殊情况下 SQL 注入可能是不可能的,但它是一个真正的威胁,人们似乎很难理解/记住我认为应该是结果而不是完全做这样的事情。
    • +1。如果IN 列表相对较短,恕我直言,这是最简单和最快的方法。 (注意自己在做什么,可以避免SQL注入)
    【解决方案6】:

    您可能也有兴趣阅读以下内容:
    http://www.sommarskog.se/dynamic_sql.html

    http://www.sommarskog.se/arrays-in-sql-2005.html

    涵盖带有“in”子句和各种类型的动态 sql。很有趣。

    【讨论】:

    • 非常正确:非常有趣。
    【解决方案7】:

    这是我过去用来解决“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;
    

    希望这会有所帮助。

    【讨论】:

    • 已经在一个项目中做到了,但我的目标是没有帕斯卡代码
    • 那将是非常困难的。我为此奋斗了多年。如果您有支持宏的组件,您可以完成此操作,但使用参数我对此表示怀疑,并且在服务器端,我无法完成此操作。
    【解决方案8】:

    参数是单个值的占位符,这意味着接受逗号分隔的值列表的 IN 子句不能与参数一起使用。

    这样想:无论我在哪里放置一个值,我都可以使用一个参数。

    所以,在这样的子句中:IN (:param)

    我可以将变量绑定到一个值,但只能绑定一个值,例如:IN (4)

    现在,如果您考虑“IN 子句值表达式”,您会得到一串值:IN (1, 4, 6) -> 那是 3 个值,它们之间有逗号。这是 SQL 字符串的一部分,而不是值的一部分,这就是它不能被参数绑定的原因。

    显然,这不是你想要的,但它是唯一可能的参数。

    【讨论】:

      【解决方案9】:

      对于仍然感兴趣的人。我使用受这篇文章启发的另一个存储过程在 Firebird 2.5 中完成了它。

      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
      

      当你通过SP下面的列表

      CommaSeperatedList = 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) )
      

      【讨论】:

      • 如果您需要作为整数的结果,请将输出类型从 varchar(255) 更改为整数,并将 RESULT = :TEMPSTR; 替换为此 RESULT = cast(:TEMPSTR as integer);
      【解决方案10】:

      使用反向SQL LIKE 条件有一个技巧。

      您将列表作为字符串 (VARCHAR) 参数传递,例如 '~12~23~46~567~'

      然后你有类似的查询 where ... :List_Param LIKE ('%~' || CAST( NumField AS VARCHAR(20)) || '~%')

      【讨论】:

        【解决方案11】:
        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)
        

        【讨论】:

          【解决方案12】:

          从 MATABLE 中选择 * WHERE MT_ID IN (:MYPARAM) 不要使用带有 : 的 MYPARAM,而是使用参数名称。

          喜欢 SELECT * FROM MATABLE WHERE MT_ID IN (SELECT REGEXP_SUBSTR(**MYPARAM,'[^,]+', 1, LEVEL) 从双 CONNECT BY REGEXP_SUBSTR(MYPARAM, '[^,]+', 1, LEVEL) IS NOT NULL))**

          MYPARAM-'368134,181956'

          【讨论】:

            猜你喜欢
            • 2023-03-19
            • 2010-10-13
            • 1970-01-01
            • 1970-01-01
            • 2014-05-02
            • 2018-07-07
            • 1970-01-01
            • 2016-04-02
            • 1970-01-01
            相关资源
            最近更新 更多