关于sql server:参数化SQL IN子句

Parameterize an SQL IN clause

如何参数化包含带有可变数量参数的IN子句的查询,比如这个?

1
2
3
SELECT * FROM Tags
WHERE Name IN ('ruby','rails','scruffy','rubyonrails')
ORDER BY COUNT DESC

在此查询中,参数的数量可以是1到5之间的任何值。

我不希望为此(或XML)使用专用存储过程,但如果有一些特定于SQL Server 2008的优雅方式,我对此持开放态度。


您可以参数化每个值,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
string[] tags = NEW string[] {"ruby","rails","scruffy","rubyonrails" };
string cmdText ="SELECT * FROM Tags WHERE Name IN ({0})";

string[] paramNames = tags.Select(
    (s, i) =>"@tag" + i.ToString()
).ToArray();

string inClause = string.Join(",", paramNames);
USING (SqlCommand cmd = NEW SqlCommand(string.Format(cmdText, inClause))) {
    FOR(INT i = 0; i < paramNames.Length; i++) {
       cmd.Parameters.AddWithValue(paramNames[i], tags[i]);
    }
}

哪个会给你:

1
2
3
4
5
cmd.CommandText ="SELECT * FROM Tags WHERE Name IN (@tag0, @tag1, @tag2, @tag3)"
cmd.Parameters["@tag0"] ="ruby"
cmd.Parameters["@tag1"] ="rails"
cmd.Parameters["@tag2"] ="scruffy"
cmd.Parameters["@tag3"] ="rubyonrails"

不,这不适用于SQL注入。 CommandText中唯一注入的文本不是基于用户输入。它完全基于硬编码的"@tag"前缀和数组的索引。索引将始终为整数,不是用户生成的,并且是安全的。

用户输入的值仍然填充到参数中,因此没有漏洞。

编辑:

Injection concerns aside, take care to note that constructing the command text to accomodate a variable number of parameters (as above) impede's SQL server's ability to take advantage of cached queries. The net result is that you almost certainly lose the value of using parameters in the first place (as opposed to merely inserting the predicate strings into the SQL itself).

并不是说缓存的查询计划没有价值,但IMO这个查询并不复杂,足以从中看到很多好处。虽然编译成本可能接近(甚至超过)执行成本,但您仍然在谈论毫秒。

如果你有足够的RAM,我希望SQLServer可能会缓存一个常见的参数计数的计划。我想你总是可以添加五个参数,让未指定的标签为NULL - 查询计划应该是相同的,但对我来说似乎很难看,我不确定它是否值得进行微优化(尽管如此,在StackOverflow上 - 它可能非常值得)。

此外,SQLServer 7及更高版本将自动参数化查询,因此从性能角度来看,使用参数并不是必需的 - 但从安全角度来看,这是至关重要的 - 特别是对于这样的用户输入数据。


这是我使用的一种快速而肮脏的技术:

1
2
3
SELECT * FROM Tags
WHERE '|ruby|rails|scruffy|rubyonrails|'
LIKE '%|' + Name + '|%'

所以这是C#代码:

1
2
3
4
5
6
string[] tags = NEW string[] {"ruby","rails","scruffy","rubyonrails" };
const string cmdText ="select * from tags where '|' + @tags + '|' like '%|' + Name + '|%'";

USING (SqlCommand cmd = NEW SqlCommand(cmdText)) {
   cmd.Parameters.AddWithValue("@tags", string.Join("|", tags);
}

两个警告:

  • 表现很可怕。 LIKE"%...%"查询未编入索引。
  • 确保您没有任何|,空白或空标记,否则这将无效

还有其他方法可以实现这一点,有些人可能认为更干净,所以请继续阅读。


对于SQL Server 2008,您可以使用表值参数。这有点工作,但它可以说比我的其他方法更清晰。

首先,您必须创建一个类型

1
CREATE TYPE dbo.TagNamesTableType AS TABLE ( Name nvarchar(50) )

然后,您的ADO.NET代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
string[] tags = NEW string[] {"ruby","rails","scruffy","rubyonrails" };
cmd.CommandText ="SELECT Tags.* FROM Tags JOIN @tagNames as P ON Tags.Name = P.Name";

// VALUE must be IEnumerable<SqlDataRecord>
cmd.Parameters.AddWithValue("@tagNames", tags.AsSqlDataRecord("Name")).SqlDbType = SqlDbType.Structured;
cmd.Parameters["@tagNames"].TypeName ="dbo.TagNamesTableType";

// Extension method FOR converting IEnumerable<string> TO IEnumerable<SqlDataRecord>
public static IEnumerable<SqlDataRecord> AsSqlDataRecord(this IEnumerable<string> VALUES, string columnName) {
    IF (VALUES == NULL || !VALUES.Any()) RETURN NULL; // Annoying, but SqlClient wants NULL instead OF 0 ROWS
    var firstRecord = VALUES.First();
    var metadata = SqlMetaData.InferFromValue(firstRecord, columnName);
    RETURN VALUES.Select(v =>
    {
       var r = NEW SqlDataRecord(metadata);
       r.SetValues(v);
       RETURN r;
    });
}


最初的问题是"我如何参数化查询......"

让我在这里说,这不是原始问题的答案。在其他好的答案中已经有一些示范。

话虽如此,请继续并标记这个答案,将其归类,将其标记为不是答案......做任何你认为正确的事情。

请参阅Mark Brackett的答案,了解我(和其他231人)投票的首选答案。他的回答中给出的方法允许1)有效使用绑定变量,2)用于可搜索的谓词。

选择答案

我想在这里讨论的是Joel Spolsky的答案中给出的方法,答案"选择"作为正确的答案。

Joel Spolsky的方法很聪明。并且它合理地工作,它将展示可预测的行为和可预测的性能,给定"正常"值,以及规范边缘情况,例如NULL和空字符串。对于特定应用来说可能就足够了。

但是在概括这种方法的术语中,让我们考虑更隐蔽的极端情况,例如Name列包含通配符(由LIKE谓词识别)。我看到最常用的通配符是%(a百分号。)。现在让我们来处理这个问题,然后继续讨论其他案例。

%字符的一些问题

考虑名称值'pe%ter'。 (对于这里的示例,我使用文字字符串值代替列名。)名称值为"'pe%ter"的行将由以下形式的查询返回:

1
2
SELECT ...
 WHERE '|peanut|butter|' LIKE '%|' + 'pe%ter' + '|%'

但是,如果搜索项的顺序颠倒,则不会返回相同的行:

1
2
SELECT ...
 WHERE '|butter|peanut|' LIKE '%|' + 'pe%ter' + '|%'

我们观察到的行为有点奇怪。更改列表中搜索词的顺序会更改结果集。

毫无疑问,我们可能不希望pe%ter匹配花生酱,无论他多么喜欢它。

隐秘的角落案例

(是的,我会同意这是一个模糊的案例。可能是一个不太可能被测试的案例。我们不希望列值中出现通配符。我们可以假设应用程序阻止存储这样的值。但是根据我的经验,我很少看到数据库约束明确禁止在LIKE比较运算符右侧被视为通配符的字符或模式。

修补一个洞

修补此漏洞的一种方法是转义%通配符。 (对于不熟悉运算符的escape子句的人,这里是SQL Server文档的链接。

1
2
3
SELECT ...
 WHERE '|peanut|butter|'
  LIKE '%|' + 'pe\%ter' + '|%' escape '\'

现在我们可以匹配文字%。当然,当我们有一个列名时,我们需要动态转义通配符。我们可以使用REPLACE函数查找%字符的出现位置,并在每个字符前插入一个反斜杠字符,如下所示:

1
2
3
SELECT ...
 WHERE '|pe%ter|'
  LIKE '%|' + REPLACE( 'pe%ter' ,'%','\%') + '|%' escape '\'

这样就解决了%wildcard的问题。几乎。

逃离逃生

我们认识到我们的解决方案引入了另一个问题逃脱角色。我们看到我们还需要逃避任何出现的转义字符本身。这次,我们用了!作为转义字符:

1
2
3
SELECT ...
 WHERE '|pe%t!r|'
  LIKE '%|' + REPLACE(REPLACE( 'pe%t!r' ,'!','!!'),'%','!%') + '|%' escape '!'

下划线也是

现在我们已经开始了,我们可以添加另一个REPLACE处理下划线通配符。而且只是为了好玩,这一次,我们将使用$作为转义字符。

1
2
3
SELECT ...
 WHERE '|p_%t!r|'
  LIKE '%|' + REPLACE(REPLACE(REPLACE( 'p_%t!r' ,'$','$$'),'%','$%'),'_','$_') + '|%' escape '$'

我更喜欢这种方法来逃避,因为它适用于Oracle和MySQL以及SQL Server。 (我通常使用反斜杠作为转义字符,因为这是我们在正则表达式中使用的字符。但为什么会被约定约束!

那些讨厌的括号

SQL Server还允许将通配符作为文字处理,方法是将它们括在括号[]中。所以我们还没有完成修复,至少对于SQL Server来说。由于括号对具有特殊含义,我们也需要逃避它们。如果我们设法正确地逃避括号,那么至少我们不必打扰括号中的连字符-和克拉^。我们可以在括号中保留任何%_字符,因为我们基本上已经禁用了括号的特殊含义。

找到匹配的括号对不应该那么难。这比处理单例%和_的出现要困难一些。 (注意,仅仅转义所有出现的括号是不够的,因为单个括号被认为是文字,并且不需要进行转义。逻辑变得比我能处理的更模糊而不运行更多的测试用例。)

内联表达式变得混乱

SQL中的内联表达式越来越长,越来越丑陋。我们可能会让它发挥作用,但是天堂帮助了落后的穷人,并且必须破译它。作为内联表达的粉丝,我倾向于不在这里使用,主要是因为我不想留下评论解释混乱的原因,并为此道歉。

一个功能在哪里?

好的,所以,如果我们不将它作为SQL中的内联表达式处理,那么我们最接近的替代方法是用户定义的函数。而且我们知道不会加速任何事情(除非我们可以像在Oracle上一样定义索引。)如果我们必须创建一个函数,我们可能最好在调用SQL的代码中这样做声明。

并且该功能可能在行为上有一些差异,这取决于DBMS和版本。 (向所有Java开发人员致敬,他们热衷于能够交替使用任何数据库引擎。)

领域知识

我们可能对列的域具有专门知识(即,为列强制执行的允许值集合。我们可能先验地知道列中存储的值永远不会包含百分号,下划线或括号在这种情况下,我们只是简单地包含这些案例的快速评论。

存储在列中的值可以允许%或_字符,但是约束可能要求对这些值进行转义,可能使用已定义的字符,以使值为LIKE比较"安全"。再次,快速评论允许的值集,特别是哪个字符用作转义字符,并与Joel Spolsky的方法一起使用。

但是,如果缺乏专业知识和保证,至少考虑处理那些模糊不清的角落案件,并考虑行为是否合理并且"符合规范"对我们来说非常重要。

其他问题概括

我相信其他人已经充分指出了其他一些常被考虑的关注领域:

  • SQL注入(看似用户提供的信息,包括在SQL文本中的信息,而不是通过绑定变量提供它们。使用绑定变量不是必需的,它只是一种方便的方法来阻止SQL注入。还有其他处理它的方法:

  • 优化器计划使用索引扫描而不是索引搜索,可能需要表达式或函数来转义通配符(可能的表达式或函数索引)

  • 使用文字值代替绑定变量会影响可伸缩性

  • 结论

    我喜欢Joel Spolsky的做法。这很聪明。它有效。

    但是当我看到它时,我立刻就看到了它的一个潜在问题,让它滑动不是我的本性。我并不是要批评别人的努力。我知道很多开发人员非常个人地开展工作,因为他们投入了大量资金,而且他们非常关心它。所以请理解,这不是个人攻击。我在这里发现的是在生产而不是测试中出现的问题类型。

    是的,我离原问题远远不够。但是,还有什么地方可以留下这个关于我认为对于一个问题的"选定"答案的重要问题的说明?

    好。


    您可以将参数作为字符串传递

    所以你有字符串

    1
    2
    3
    4
    5
    6
    7
    DECLARE @tags

    SET @tags = ‘ruby|rails|scruffy|rubyonrails’

    SELECT * FROM Tags
    WHERE Name IN (SELECT item FROM fnSplit(@tags,|))
    ORDER BY COUNT DESC

    然后你要做的就是将字符串作为1参数传递。

    这是我使用的分割功能。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    CREATE FUNCTION [dbo].[fnSplit](
        @sInputList VARCHAR(8000) -- List of delimited items
      , @sDelimiter VARCHAR(8000) = ',' -- delimiter that separates items
    ) RETURNS @List TABLE (item VARCHAR(8000))

    BEGIN
    DECLARE @sItem VARCHAR(8000)
    WHILE CHARINDEX(@sDelimiter,@sInputList,0) <> 0
     BEGIN
     SELECT
      @sItem=RTRIM(LTRIM(SUBSTRING(@sInputList,1,CHARINDEX(@sDelimiter,@sInputList,0)-1))),
      @sInputList=RTRIM(LTRIM(SUBSTRING(@sInputList,CHARINDEX(@sDelimiter,@sInputList,0)+LEN(@sDelimiter),LEN(@sInputList))))

     IF LEN(@sItem) > 0
      INSERT INTO @List SELECT @sItem
     END

    IF LEN(@sInputList) > 0
     INSERT INTO @List SELECT @sInputList -- Put the last item in
    RETURN
    END


    我听说Jeff / Joel今天在播客上谈论这个(第34集,2008-12-16(MP3,31MB),1小时03分38秒 - 1小时06分45秒),我想我回想起StackOverflow正在使用LINQtoSQL,但也许它被抛弃了。这与LINQtoSQL中的内容相同。

    1
    2
    3
    4
    5
    var inValues = NEW [] {"ruby","rails","scruffy","rubyonrails" };

    var results = FROM tag IN Tags
                  WHERE inValues.Contains(tag.Name)
                  SELECT tag;

    而已。而且,是的,LINQ已经足够向后看,但Contains条款对我来说似乎更加倒退。当我不得不对工作中的项目进行类似的查询时,我自然会尝试通过在本地数组和SQL Server表之间进行连接来做错误的方法,认为LINQtoSQL转换器足够智能以某种方式处理翻译。它没有,但确实提供了一个描述性的错误信息,并指出我使用Contains。

    无论如何,如果您在强烈推荐的LINQPad中运行它,并运行此查询,您可以查看SQL LINQ提供程序生成的实际SQL。它将向您展示参数化为IN子句的每个值。


    如果从.NET调用,可以使用Dapper dot net:

    1
    2
    3
    4
    5
    string[] names = NEW string[] {"ruby","rails","scruffy","rubyonrails"};
    var tags = dataContext.Query<Tags>(@"
    select * from Tags
    where Name in @names
    order by Count desc"
    , NEW {names});

    Dapper在这里思考,所以你没必要。当然,LINQtoSQL可能有类似的东西:

    1
    2
    3
    4
    5
    string[] names = NEW string[] {"ruby","rails","scruffy","rubyonrails"};
    var tags = FROM tag IN dataContext.Tags
               WHERE names.Contains(tag.Name)
               orderby tag.Count descending
               SELECT tag;


    这可能是一种令人讨厌的方式,我用了一次,相当有效。

    根据您的目标,它可能有用。

  • 使用一列创建临时表。
  • INSERT该列中的每个查找值。
  • 您可以使用标准的JOIN规则,而不是使用IN。 (灵活性++)
  • 这有一些额外的灵活性,你可以做什么,但它更适合你有一个大表要查询,具有良好的索引,并且你想多次使用参数化列表的情况。节省必须执行两次并手动完成所有卫生。

    我从来没有准确地分析它到底有多快,但在我的情况下它是必要的。


    我们有一个函数可以创建一个可以加入的表变量:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    ALTER FUNCTION [dbo].[Fn_sqllist_to_table](@list  AS VARCHAR(8000),
                                               @delim AS VARCHAR(10))
    RETURNS @listTable TABLE(
      POSITION INT,
      VALUE    VARCHAR(8000))
    AS
      BEGIN
          DECLARE @myPos INT

          SET @myPos = 1

          WHILE Charindex(@delim, @list) > 0
            BEGIN
                INSERT INTO @listTable
                            (POSITION,VALUE)
                VALUES     (@myPos,LEFT(@list, Charindex(@delim, @list) - 1))

                SET @myPos = @myPos + 1

                IF Charindex(@delim, @list) = Len(@list)
                  INSERT INTO @listTable
                              (POSITION,VALUE)
                  VALUES     (@myPos,'')

                SET @list = RIGHT(@list, Len(@list) - Charindex(@delim, @list))
            END

          IF Len(@list) > 0
            INSERT INTO @listTable
                        (POSITION,VALUE)
            VALUES     (@myPos,@list)

          RETURN
      END

    所以:

    1
    2
    3
    4
    5
    @Name VARCHAR(8000) = NULL // parameter FOR SEARCH VALUES    

    SELECT * FROM Tags
    WHERE Name IN (SELECT VALUE FROM fn_sqllist_to_table(@Name,',')))
    ORDER BY COUNT DESC

    SQL Server 2016+中,您可以使用STRING_SPLIT函数:

    1
    2
    3
    4
    5
    6
    DECLARE @names NVARCHAR(MAX) = 'ruby,rails,scruffy,rubyonrails';

    SELECT *
    FROM Tags
    WHERE Name IN (SELECT [VALUE] FROM STRING_SPLIT(@names, ','))
    ORDER BY COUNT DESC;

    要么:

    1
    2
    3
    4
    5
    6
    7
    DECLARE @names NVARCHAR(MAX) = 'ruby,rails,scruffy,rubyonrails';

    SELECT t.*
    FROM Tags t
    JOIN STRING_SPLIT(@names,',')
      ON t.Name = [VALUE]
    ORDER BY COUNT DESC;

    LiveDemo

    接受的答案当然是有效的,这是其中一种方法,但它是反模式的。

    E. Find rows by list of values

    This is replacement for common anti-pattern such as creating a dynamic SQL string in application layer or Transact-SQL, or by using LIKE operator:

    1
    2
    3
    SELECT ProductId, Name, Tags
    FROM Product
    WHERE ',1,2,3,' LIKE '%,' + CAST(ProductId AS VARCHAR(20)) + ',%';

    原始问题有要求SQL Server 2008。因为这个问题经常被用作副本,所以我添加了这个答案作为参考。


    这很糟糕,但如果你保证至少有一个,你可以这样做:

    1
    2
    3
    SELECT ...
           ...
     WHERE tag IN( @tag1, ISNULL( @tag2, @tag1 ), ISNULL( @tag3, @tag1 ), etc. )

    IN('tag1','tag2','tag1','tag1','tag1')将很容易被SQL Server优化。另外,您可以获得直接索引搜索


    在我看来,解决这个问题的最佳来源是在这个网站上发布的内容:

    SYSCOMMENTS。 Dinakar Nethi

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    CREATE FUNCTION dbo.fnParseArray (@Array VARCHAR(1000),@separator CHAR(1))
    RETURNS @T TABLE (col1 VARCHAR(50))
    AS
    BEGIN
     --DECLARE @T Table (col1 varchar(50))  
     -- @Array is the array we wish to parse
     -- @Separator is the separator charactor such as a comma
     DECLARE @separator_position INT -- This is used to locate each separator character
     DECLARE @array_value VARCHAR(1000) -- this holds each array value as it is returned
     -- For my loop to work I need an extra separator at the end. I always look to the
     -- left of the separator character for each array value

     SET @array = @array + @separator

     -- Loop through the string searching for separtor characters
     WHILE PATINDEX('%' + @separator + '%', @array) <> 0
     BEGIN
        -- patindex matches the a pattern against a string
        SELECT @separator_position = PATINDEX('%' + @separator + '%',@array)
        SELECT @array_value = LEFT(@array, @separator_position - 1)
        -- This is where you process the values passed.
        INSERT INTO @T VALUES (@array_value)    
        -- Replace this select statement with your processing
        -- @array_value holds the value of this element of the array
        -- This replaces what we just processed with and empty string
        SELECT @array = STUFF(@array, 1, @separator_position, '')
     END
     RETURN
    END

    使用:

    1
    SELECT * FROM dbo.fnParseArray('a,b,c,d,e,f', ',')

    奖励:Dinakar Nethi


    我会传递一个表类型参数(因为它是SQL Server 2008),并执行where exists或内部联接。您也可以使用XML,使用sp_xml_preparedocument,然后甚至索引该临时表。


    恕我直言的正确方法是将列表存储在字符串中(DBMS支持的长度有限);唯一的技巧是(为了简化处理)我在字符串的开头和结尾有一个分隔符(在我的例子中是一个逗号)。我们的想法是"动态规范化",将列表转换为单列表,每个值包含一行。这允许你转弯

    in (ct1,ct2, ct3 ... ctn)

    变成一个

    in (select ...)

    或者(我可能更喜欢的解决方案)常规连接,如果你只是添加一个"distinct"来避免列表中重复值的问题。

    不幸的是,切片字符串的技术是特定于产品的。
    这是SQL Server版本:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     WITH qry(n, names) AS
           (SELECT len(list.names) - len(REPLACE(list.names, ',', '')) - 1 AS n,
                   SUBSTRING(list.names, 2, len(list.names)) AS names
            FROM (SELECT ',Doc,Grumpy,Happy,Sneezy,Bashful,Sleepy,Dopey,' names) AS list
            UNION ALL
            SELECT (n - 1) AS n,
                   SUBSTRING(names, 1 + charindex(',', names), len(names)) AS names
            FROM qry
            WHERE n > 1)
     SELECT n, SUBSTRING(names, 1, charindex(',', names) - 1) dwarf
     FROM qry;

    Oracle版本:

    1
    2
    3
    4
    5
    6
    7
    8
    9
     SELECT n, substr(name, 1, instr(name, ',') - 1) dwarf
     FROM (SELECT n,
                 substr(val, 1 + instr(val, ',', 1, n)) name
          FROM (SELECT rownum AS n,
                       list.val
                FROM  (SELECT ',Doc,Grumpy,Happy,Sneezy,Bashful,Sleepy,Dopey,' val
                       FROM dual) list
                CONNECT BY level < LENGTH(list.val) -
                                   LENGTH(REPLACE(list.val, ',', ''))));

    和MySQL版本:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    SELECT pivot.n,
          substring_index(substring_index(list.val, ',', 1 + pivot.n), ',', -1) FROM (SELECT 1 AS n
         UNION ALL
         SELECT 2 AS n
         UNION ALL
         SELECT 3 AS n
         UNION ALL
         SELECT 4 AS n
         UNION ALL
         SELECT 5 AS n
         UNION ALL
         SELECT 6 AS n
         UNION ALL
         SELECT 7 AS n
         UNION ALL
         SELECT 8 AS n
         UNION ALL
         SELECT 9 AS n
         UNION ALL
         SELECT 10 AS n) pivot,    (SELECT ',Doc,Grumpy,Happy,Sneezy,Bashful,Sleepy,Dopey,' val) AS list WHERE pivot.n <  LENGTH(list.val) -
                       LENGTH(REPLACE(list.val, ',', ''));

    (当然,"pivot"必须返回与最大数量一样多的行
    我们可以在列表中找到的项目)


    如果您有SQL Server 2008或更高版本,我将使用表值参数。

    如果你不幸被卡在SQL Server 2005上,你可以添加像这样的CLR函数,

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    [SqlFunction(
        DataAccessKind.None,
        IsDeterministic = TRUE,
        SystemDataAccess = SystemDataAccessKind.None,
        IsPrecise = TRUE,
        FillRowMethodName ="SplitFillRow",
        TableDefinintion ="s NVARCHAR(MAX)"]
    public static IEnumerable Split(SqlChars seperator, SqlString s)
    {
        IF (s.IsNull)
            RETURN NEW string[0];

        RETURN s.ToString().Split(seperator.Buffer);
    }

    public static void SplitFillRow(object ROW, OUT SqlString s)
    {
        s = NEW SqlString(ROW.ToString());
    }

    您可以这样使用,

    1
    2
    3
    4
    5
    6
    DECLARE @desiredTags nvarchar(MAX);
    SET @desiredTags = 'ruby,rails,scruffy,rubyonrails';

    SELECT * FROM Tags
    WHERE Name IN [dbo].[Split] (',', @desiredTags)
    ORDER BY COUNT DESC

    我认为这是静态查询不是要走的路。动态构建in子句的列表,转义单引号,并动态构建SQL。在这种情况下,由于列表很小,您可能不会发现任何方法有太大差异,但最有效的方法实际上是发送SQL,就像在帖子中写的一样。我认为以最有效的方式编写它是一个好习惯,而不是做出最漂亮的代码,或者认为动态构建SQL是不好的做法。

    我已经看到,在参数变大的许多情况下,拆分函数的执行时间比查询本身要长。 SQL 2008中具有表值参数的存储过程是我考虑的唯一其他选项,尽管在您的情况下这可能会更慢。如果要搜索TVP的主键,TVP可能只会对大型列表更快,因为SQL无论如何都会为列表构建一个临时表(如果列表很大)。除非你测试它,否则你不会

    我还看到存储过程有500个参数,默认值为null,并且WHERE Column1 IN(@ Param1,@ Param2,@ Param3,...,@ Param500)。这导致SQL构建临时表,执行排序/分离,然后执行表扫描而不是索引搜索。这基本上就是你通过参数化查询来做的事情,虽然规模很小,但不会产生明显的差异。我强烈建议您不要在IN列表中使用NULL,就好像它被更改为NOT IN一样,它不会按预期运行。您可以动态构建参数列表,但唯一明显的事情是对象将为您转义单引号。这种方法在应用程序端也略慢,因为对象必须解析查询以查找参数。它可能会或可能不会更快SQL,因为参数化查询调用sp_prepare,sp_execute执行查询的次数,然后是sp_unprepare。

    为存储过程或参数化查询重用执行计划可能会为您带来性能提升,但它会将您锁定到由执行的第一个查询确定的一个执行计划。在许多情况下,这可能不太适合后续查询。在您的情况下,重用执行计划可能是一个加号,但它可能没有任何区别,因为该示例是一个非常简单的查询。

    悬崖注意到:

    对于您所做的任何事情,无论是在列表中使用固定数量的项进行参数化(如果不使用,则为null),使用或不使用参数动态构建查询,或者使用具有表值参数的存储过程不会产生太大差异。但是,我的一般建议如下:

    您的案例/简单查询参数很少:

    动态SQL,如果测试显示更好的性能,可能带参数。

    具有可重用执行计划的查询,通过简单地更改参数或查询是否复杂来多次调用:

    带动态参数的SQL。

    查询大型列表:

    带有表值参数的存储过程。如果列表变化很大,请在存储过程上使用WITH RECOMPILE,或者只使用不带参数的动态SQL为每个查询生成新的执行计划。


    可能我们可以在这里使用XML:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
        DECLARE @x xml
        SET @x='<items>
        <item myvalue="29790" />
        <item myvalue="31250" />
        </items>
        '
    ;
        WITH CTE AS (
             SELECT
                x.item.value('@myvalue[1]', 'decimal') AS myvalue
            FROM @x.nodes('//items/item') AS x(item) )

        SELECT * FROM YourTable WHERE tableColumnName IN (SELECT myvalue FROM cte)


    默认情况下,我会将表值函数(从字符串返回一个表)传递给IN条件。

    这是UDF的代码(我从Stack Overflow的某个地方得到它,我现在找不到源代码)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    CREATE FUNCTION [dbo].[Split] (@sep CHAR(1), @s VARCHAR(8000))
    RETURNS TABLE
    AS
    RETURN (
        WITH Pieces(pn, START, stop) AS (
          SELECT 1, 1, CHARINDEX(@sep, @s)
          UNION ALL
          SELECT pn + 1, stop + 1, CHARINDEX(@sep, @s, stop + 1)
          FROM Pieces
          WHERE stop > 0
        )
        SELECT
          SUBSTRING(@s, START, CASE WHEN stop > 0 THEN stop-START ELSE 512 END) AS s
        FROM Pieces
      )

    一旦你得到了这个,你的代码就像这样简单:

    1
    2
    3
    SELECT * FROM Tags
    WHERE Name IN (SELECT s FROM dbo.split(';','ruby;rails;scruffy;rubyonrails'))
    ORDER BY COUNT DESC

    除非你有一个可笑的长字符串,否则这应该适用于表索引。

    如果需要,您可以将其插入临时表,索引它,然后运行连接...


    使用以下存储过程。它使用自定义拆分功能,可在此处找到。

    1
    2
    3
    4
    5
    6
    7
    8
     CREATE stored PROCEDURE GetSearchMachingTagNames
        @PipeDelimitedTagNames VARCHAR(MAX),
        @delimiter CHAR(1)
        AS  
        BEGIN
             SELECT * FROM Tags
             WHERE Name IN (SELECT DATA FROM [dbo].[Split](@PipeDelimitedTagNames,@delimiter)
        END

    如果我们在IN子句中存储了以逗号(,)分隔的字符串,我们可以使用charindex函数来获取值。如果使用.NET,则可以使用SqlParameters进行映射。

    DDL脚本:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    CREATE TABLE Tags
        ([ID] INT, [Name] VARCHAR(20))
    ;

    INSERT INTO Tags
        ([ID], [Name])
    VALUES
        (1, 'ruby'),
        (2, 'rails'),
        (3, 'scruffy'),
        (4, 'rubyonrails')
    ;

    T-SQL:

    1
    2
    3
    4
    5
    6
    DECLARE @Param nvarchar(MAX)

    SET @Param = 'ruby,rails,scruffy,rubyonrails'

    SELECT * FROM Tags
    WHERE CharIndex(Name,@Param)>0

    您可以在.NET代码中使用上述语句,并使用SqlParameter映射参数。

    提琴手演示

    编辑:
    使用以下脚本创建名为SelectedTags的表。

    DDL脚本:

    1
    2
    3
    4
    CREATE TABLE SelectedTags
    (Name nvarchar(20));

    INSERT INTO SelectedTags VALUES ('ruby'),('rails')

    T-SQL:

    1
    2
    3
    4
    5
    DECLARE @list nvarchar(MAX)
    SELECT @list=COALESCE(@list+',','')+st.Name FROM SelectedTags st

    SELECT * FROM Tags
    WHERE CharIndex(Name,@Param)>0


    另一种可能的解决方案是,不是将可变数量的参数传递给存储过程,而是传递包含您所追求的名称的单个字符串,但通过用"<>"包围它们使它们成为唯一的。然后使用PATINDEX查找名称:

    1
    2
    3
    SELECT *
    FROM Tags
    WHERE PATINDEX('%<' + Name + '>%','<jo>,<john>,<scruffy>,<rubyonrails>') > 0

    对于像这样的可变数量的参数,我所知道的唯一方法是显式生成SQL或执行涉及使用所需项目填充临时表并加入临时表的操作。


    这是另一种选择。只需将逗号分隔的列表作为字符串参数传递给存储过程,然后:

    1
    2
    3
    4
    5
    CREATE PROCEDURE [dbo].[sp_myproc]
        @UnitList VARCHAR(MAX) = '1,2,3'
    AS
    SELECT COLUMN FROM TABLE
    WHERE ph.UnitID IN (SELECT * FROM CsvToInt(@UnitList))

    功能:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    CREATE FUNCTION [dbo].[CsvToInt] ( @Array VARCHAR(MAX))
    RETURNS @IntTable TABLE
    (IntValue INT)
    AS
    BEGIN
        DECLARE @separator CHAR(1)
        SET @separator = ','
        DECLARE @separator_position INT
        DECLARE @array_value VARCHAR(MAX)

        SET @array = @array + ','

        while patindex('%,%' , @array) <> 0
        BEGIN

            SELECT @separator_position = patindex('%,%' , @array)
            SELECT @array_value = LEFT(@array, @separator_position - 1)

            INSERT @IntTable
            VALUES (CAST(@array_value AS INT))
            SELECT @array = stuff(@array, 1, @separator_position, '')
        END
        RETURN
    END

    在SQL Server 2016+中,另一种可能性是使用OPENJSON函数。

    这种方法是在OPENJSON中发布的博客 - 这是通过ID列表选择行的最佳方法之一。

    下面是一个完整的例子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    CREATE TABLE dbo.Tags
      (
         Name  VARCHAR(50),
         COUNT INT
      )

    INSERT INTO dbo.Tags
    VALUES      ('VB',982), ('ruby',1306), ('rails',1478), ('scruffy',1), ('C#',1784)

    GO

    CREATE PROC dbo.SomeProc
    @Tags VARCHAR(MAX)
    AS
    SELECT T.*
    FROM   dbo.Tags T
    WHERE  T.Name IN (SELECT J.Value COLLATE Latin1_General_CI_AS
                      FROM   OPENJSON(CONCAT('[', @Tags, ']')) J)
    ORDER  BY T.Count DESC

    GO

    EXEC dbo.SomeProc @Tags = '"ruby","rails","scruffy","rubyonrails"'

    DROP TABLE dbo.Tags


    在ColdFusion中我们只做:

    1
    2
    3
    4
    <cfset myvalues ="ruby|rails|scruffy|rubyonrails">
        <cfquery name="q">
            SELECT * FROM sometable WHERE VALUES IN <cfqueryparam VALUE="#myvalues#" list="true">
        </cfquery>

    这是一种重新创建要在查询字符串中使用的本地表的技术。这样做可以消除所有解析问题。

    该字符串可以使用任何语言构建。在这个例子中,我使用SQL,因为那是我试图解决的原始问题。我需要一种干净的方法来动态传递表格数据,以便稍后执行。

    使用用户定义的类型是可选的。创建类型只创建一次,可以提前完成。否则只需在字符串中的声明中添加完整的表类型。

    一般模式易于扩展,可用于传递更复杂的表。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    -- Create a user defined type for the list.
    CREATE TYPE [dbo].[StringList] AS TABLE(
        [StringValue] [nvarchar](MAX) NOT NULL
    )

    -- Create a sample list using the list table type.
    DECLARE @list [dbo].[StringList];
    INSERT INTO @list VALUES ('one'), ('two'), ('three'), ('four')

    -- Build a string in which we recreate the list so we can pass it to exec
    -- This can be done in any language since we're just building a string.
    DECLARE @str nvarchar(MAX);
    SET @str = 'DECLARE @list [dbo].[StringList]; INSERT INTO @list VALUES '

    -- Add all the values we want to the string. This would be a loop in C++.
    SELECT @str = @str + '(''' + StringValue + '''),' FROM @list

    -- Remove the trailing comma so the query is valid sql.
    SET @str = SUBSTRING(@str, 1, len(@str)-1)

    -- Add a select to test the string.
    SET @str = @str + '; SELECT * FROM @list;'

    -- Execute the string and see we've pass the table correctly.
    EXEC(@str)

    我使用顶级投票答案的更简洁版本:

    1
    2
    3
    List<SqlParameter> parameters = tags.Select((s, i) => NEW SqlParameter("@tag" + i.ToString(), SqlDbType.NVarChar(50)) { VALUE = s}).ToList();

    var whereCondition = string.Format("tags in ({0})", String.Join(",",parameters.Select(s => s.ParameterName)));

    它会循环标记参数两次;但这在大多数情况下并不重要(它不会是你的瓶颈;如果是,则展开循环)。

    如果你真的对性能感兴趣并且不想迭代循环两次,那么这是一个不太漂亮的版本:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    var parameters = NEW List<SqlParameter>();
    var paramNames = NEW List<string>();
    FOR (var i = 0; i < tags.Length; i++)  
    {
        var paramName ="@tag" + i;

        //Include SIZE AND SET VALUE explicitly (NOT AddWithValue)
        //Because SQL Server may USE an implicit conversion IF it doesn't know
        //the actual size.
        var p = new SqlParameter(paramName, SqlDbType.NVarChar(50) { Value = tags[i]; }
        paramNames.Add(paramName);
        parameters.Add(p);
    }

    var inClause = string.Join(",", paramNames);

    我有一个不需要UDF,XML的答案
    因为IN接受select语句
    例如SELECT * FROM测试数据输入(SELECT值FROM TABLE)

    你真的只需要一种方法将字符串转换成表格。

    这可以通过递归CTE或带有数字表(或Master..spt_value)的查询来完成

    这是CTE版本。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    DECLARE @InputString VARCHAR(8000) = 'ruby,rails,scruffy,rubyonrails'

    SELECT @InputString = @InputString + ','

    ;WITH RecursiveCSV(x,y)
    AS
    (
        SELECT
            x = SUBSTRING(@InputString,0,CHARINDEX(',',@InputString,0)),
            y = SUBSTRING(@InputString,CHARINDEX(',',@InputString,0)+1,LEN(@InputString))
        UNION ALL
        SELECT
            x = SUBSTRING(y,0,CHARINDEX(',',y,0)),
            y = SUBSTRING(y,CHARINDEX(',',y,0)+1,LEN(y))
        FROM
            RecursiveCSV
        WHERE
            SUBSTRING(y,CHARINDEX(',',y,0)+1,LEN(y)) <> '' OR
            SUBSTRING(y,0,CHARINDEX(',',y,0)) <> ''
    )
    SELECT
        *
    FROM
        Tags
    WHERE
        Name IN (SELECT x FROM RecursiveCSV)
    OPTION (MAXRECURSION 32767);

    这是这个问题的另一个答案。

    (新版本发布于2013年6月4日)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
        private static DataSet GetDataSet(SqlConnectionStringBuilder scsb, string strSql, params object[] pars)
        {
            var ds = NEW DataSet();
            USING (var sqlConn = NEW SqlConnection(scsb.ConnectionString))
            {
                var sqlParameters = NEW List<SqlParameter>();
                var replacementStrings = NEW Dictionary<string, string>();
                IF (pars != NULL)
                {
                    FOR (INT i = 0; i < pars.Length; i++)
                    {
                        IF (pars[i] IS IEnumerable<object>)
                        {
                            List<object> enumerable = (pars[i] AS IEnumerable<object>).ToList();
                            replacementStrings.Add("@" + i, String.Join(",", enumerable.Select((VALUE, pos) => String.Format("@_{0}_{1}", i, pos))));
                            sqlParameters.AddRange(enumerable.Select((VALUE, pos) => NEW SqlParameter(String.Format("@_{0}_{1}", i, pos), VALUE ?? DBNull.Value)).ToArray());
                        }
                        ELSE
                        {
                            sqlParameters.Add(NEW SqlParameter(String.Format("@{0}", i), pars[i] ?? DBNull.Value));
                        }
                    }
                }
                strSql = replacementStrings.Aggregate(strSql, (CURRENT, replacementString) => CURRENT.Replace(replacementString.Key, replacementString.Value));
                USING (var sqlCommand = NEW SqlCommand(strSql, sqlConn))
                {
                    IF (pars != NULL)
                    {
                        sqlCommand.Parameters.AddRange(sqlParameters.ToArray());
                    }
                    ELSE
                    {
                        //Fail-safe, just IN CASE a USER intends TO pass a single NULL parameter
                        sqlCommand.Parameters.Add(NEW SqlParameter("@0", DBNull.Value));
                    }
                    USING (var sqlDataAdapter = NEW SqlDataAdapter(sqlCommand))
                    {
                        sqlDataAdapter.Fill(ds);
                    }
                }
            }
            RETURN ds;
        }

    干杯。


    您可以通过执行以下操作以可重用的方式执行此操作 -

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    public static class SqlWhereInParamBuilder
    {
        public static string BuildWhereInClause< T >(string partialClause, string paramPrefix, IEnumerable< T > parameters)
        {
            string[] parameterNames = parameters.Select(
                (paramText, paramNumber) =>"@" + paramPrefix + paramNumber.ToString())
                .ToArray();

            string inClause = string.Join(",", parameterNames);
            string whereInClause = string.Format(partialClause.Trim(), inClause);

            RETURN whereInClause;
        }

        public static void AddParamsToCommand< T >(this SqlCommand cmd, string paramPrefix, IEnumerable< T > parameters)
        {
            string[] parameterValues = parameters.Select((paramText) => paramText.ToString()).ToArray();

            string[] parameterNames = parameterValues.Select(
                (paramText, paramNumber) =>"@" + paramPrefix + paramNumber.ToString()
                ).ToArray();

            FOR (INT i = 0; i < parameterNames.Length; i++)
            {
                cmd.Parameters.AddWithValue(parameterNames[i], parameterValues[i]);
            }
        }
    }

    有关更多详细信息,请查看此博客文章 - 参数化SQL WHERE IN子句c#


    (编辑:如果表值参数不可用)
    Best似乎是将大量IN参数拆分为多个具有固定长度的查询,因此您有许多已知的SQL语句具有固定的参数计数且没有虚拟/重复值,也没有解析字符串,XML等。

    这是C#中关于这个主题的一些代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    public static T[][] SplitSqlValues< T >(IEnumerable< T > VALUES)
    {
        var sizes = NEW INT[] { 1000, 500, 250, 125, 63, 32, 16, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 };
        INT processed = 0;
        INT currSizeIdx = sizes.Length - 1; /* start with last (smallest) */
        var splitLists = NEW List<T[]>();

        var valuesDistSort = VALUES.Distinct().ToList(); /* remove redundant */
        valuesDistSort.Sort();
        INT totalValues = valuesDistSort.Count;

        while (totalValues > sizes[currSizeIdx] && currSizeIdx > 0)
        currSizeIdx--; /* bigger size, by array pos. */

        while (processed < totalValues)
        {
            while (totalValues - processed < sizes[currSizeIdx])
                currSizeIdx++; /* smaller size, by array pos. */
            var partList = NEW T[sizes[currSizeIdx]];
            valuesDistSort.CopyTo(processed, partList, 0, sizes[currSizeIdx]);
            splitLists.Add(partList);
            processed += sizes[currSizeIdx];
        }
        RETURN splitLists.ToArray();
    }

    (您可能有进一步的想法,省略排序,使用valuesDistSort.Skip(已处理).Take(size [...])而不是list / array CopyTo)。

    插入参数变量时,您可以创建以下内容:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    foreach(INT[] partList IN splitLists)
    {
        /* here: question mark for param variable, use named/numbered params if required */
        string SQL ="select * from Items where Id in("
            + string.Join(",", partList.Select(p =>"?"))
            +")"; /* comma separated ?, one for each partList entry */

        /* create command with sql string, set parameters, execute, merge results */
    }

    我已经看过NHibernate对象 - 关系映射器生成的SQL(当查询数据以创建对象时),并且在多个查询中看起来最好。在NHibernate中,可以指定批量大小;如果必须提取许多对象数据行,它会尝试检索等于批量大小的行数

    1
    SELECT * FROM MyTable WHERE Id IN (@p1, @p2, @p3, ... , @p[batch-SIZE])

    而不是发送数百或数千

    1
    SELECT * FROM MyTable WHERE Id=@id

    当剩余的ID小于批量大小但仍然多于一个时,它会分成较小的语句,但仍然具有一定的长度。

    如果您的批处理大小为100,并且查询包含118个参数,则会创建3个查询:

    • 一个有100个参数(批量大小),
    • 然后一个12
    • 和另一个6,

    但是没有118或18.这样,它将可能的SQL语句限制为可能已知的语句,从而防止了太多不同的,因此太多的查询计划,这些查询计划填满了缓存,而且很多部分永远不会被重用。上面的代码也是如此,但长度分别为1000,500,250,125,63,32,16,10到-1。还会拆分包含1000个以上元素的参数列表,以防止因大小限制而导致数据库错误。

    无论如何,最好有一个直接发送参数化SQL的数据库接口,而不需要单独的Prepare语句和句柄来调用。像SQL Server和Oracle这样的数据库通过字符串相等性来记住SQL(值更改,SQL中没有绑定参数!)并重用查询计划(如果可用)。不需要单独的准备语句,以及代码中查询句柄的繁琐维护! ADO.NET的工作方式与此类似,但似乎Java仍然使用handle(不确定)执行prepare / execute。

    我对这个主题有我自己的问题,最初建议用重复项填充IN子句,但后来更喜欢NHibernate样式语句拆分:
    参数化SQL - 使用固定数量的参数进/出,用于查询计划缓存优化?

    这个问题仍然很有意思,甚至超过5年后被问到......

    编辑:我注意到在SQL Server上,在给定的情况下,具有许多值(如250或更多)的IN查询仍然很慢。虽然我希望DB在内部创建一种临时表并加入它,但它似乎只重复单值SELECT表达式n次。每个查询的时间长达200毫秒 - 甚至比将原始ID检索SELECT与其他相关表连接更糟糕。此外,SQL Server Profiler中有大约10到15个CPU单元,这对于重复执行相同参数化而言是不常见的查询,建议在重复调用时创建新的查询计划。也许像个人查询这样的临时性并不会更糟。我不得不将这些查询与非拆分查询进行比较,最终结论的大小不断变化,但就目前而言,似乎应该避免使用长IN条款。


    这是针对同一问题的解决方案的交叉帖子。比保留分隔符更强大 - 包括转义和嵌套数组,并理解NULL和空数组。

    C#和T-SQL string []打包/解包实用程序功能

    然后,您可以加入表值函数。


    唯一获胜的举动是不参加比赛。

    没有无限的可变性。只有有限的可变性。

    在SQL中你有一个这样的子句:

    1
    AND ( {1}==0 OR b.CompanyId IN ({2},{3},{4},{5},{6}) )

    在C#代码中,您可以执行以下操作:

    1
    2
    3
    4
    5
    6
    7
    8
      INT origCount = idList.Count;
      IF (origCount > 5) {
        throw NEW Exception("You may only specify up to five originators to filter on.");
      }
      while (idList.Count < 5) { idList.Add(-1); }  // -1 IS an impossible VALUE
      RETURN ExecuteQuery<PublishDate>(getValuesInListSQL,
                   origCount,  
                   idList[0], idList[1], idList[2], idList[3], idList[4]);

    所以基本上如果计数为0则没有过滤器,一切都会通过。如果计数高于0,则该值必须在列表中,但列表已填充到五个具有不可能的值(因此SQL仍然有意义)

    有时,蹩脚的解决方案是唯一真正有效的解决方案。


    这是Mark Bracket出色解决方案中可重复使用的解决方案。

    扩展方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public static class ParameterExtensions
    {
        public static Tuple<string, SqlParameter[]> ToParameterTuple< T >(this IEnumerable< T > VALUES)
        {
            var createName = NEW Func<INT, string>(INDEX =>"@value" + INDEX.ToString());
            var paramTuples = VALUES.Select((VALUE, INDEX) =>
            NEW Tuple<string, SqlParameter>(createName(INDEX), NEW SqlParameter(createName(INDEX), VALUE))).ToArray();
            var inClause = string.Join(",", paramTuples.Select(t => t.Item1));
            var parameters = paramTuples.Select(t => t.Item2).ToArray();
            RETURN NEW Tuple<string, SqlParameter[]>(inClause, parameters);
        }
    }

    用法:

    1
    2
    3
    4
    5
    6
    7
    8
            string[] tags = {"ruby","rails","scruffy","rubyonrails"};
            var paramTuple = tags.ToParameterTuple();
            var cmdText = $"SELECT * FROM Tags WHERE Name IN ({paramTuple.Item1})";

            USING (var cmd = NEW SqlCommand(cmdText))
            {
                cmd.Parameters.AddRange(paramTuple.Item2);
            }


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
        CREATE FUNCTION [dbo].[ConvertStringToList]


          (@str VARCHAR (MAX), @delimeter CHAR (1))
            RETURNS
            @RESULT TABLE (
                [ID] INT NULL)
        AS
        BEG

    IN

        DECLARE @x XML
        SET @x = '< T >' + REPLACE(@str, @delimeter, '</t>< T >') + '</t>'

        INSERT INTO @RESULT
        SELECT DISTINCT x.i.value('.', 'int') AS token
        FROM @x.nodes('//t') x(i)
        ORDER BY 1

    RETURN
    END

    - 您的查询

    1
    SELECT * FROM TABLE WHERE id IN ([dbo].[ConvertStringToList(YOUR comma separated string ,',')])


    使用动态查询。前端只生成所需的格式:

    1
    2
    3
    4
    5
    6
    DECLARE @invalue VARCHAR(100)
    SELECT @invalue = '''Bishnu'',''Gautam'''

    DECLARE @dynamicSQL VARCHAR(MAX)
    SELECT @dynamicSQL = 'SELECT * FROM #temp WHERE [name] IN (' + @invalue + ')'
    EXEC (@dynamicSQL)

    SQL小提琴


    有一种很好,简单且经过测试的方法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    /* Create table-value string: */
    CREATE TYPE [String_List] AS TABLE ([Your_String_Element] VARCHAR(MAX) PRIMARY KEY);
    GO
    /* Create procedure which takes this table as parameter: */

    CREATE PROCEDURE [dbo].[usp_ListCheck]
    @String_List_In [String_List] READONLY  
    AS  
    SELECT a.*
    FROM [dbo].[Tags] a
    JOIN @String_List_In b ON a.[Name] = b.[Your_String_Element];

    我已经开始使用这种方法来解决我们在实体框架中遇到的问题(对我们的应用程序来说不够强大)。所以我们决定给Dapper(和Stack一样)一个机会。同时将您的字符串列表指定为具有PK列的表,可以修复您的执行计划。
    这是一篇关于如何将表传递给Dapper的好文章 - 所有快速和清洁。


    步骤1:-

    1
    2
    string[] Ids = NEW string[] {"3","6","14" };
    string IdsSP = string.Format("'|{0}|'", string.Join("|", Ids));

    第2步:-

    1
    @CurrentShipmentStatusIdArray [nvarchar](255) = NULL

    第3步: -

    1
    WHERE @CurrentShipmentStatusIdArray IS NULL OR @CurrentShipmentStatusIdArray LIKE '%|' + CONVERT(nvarchar,Shipments.CurrentShipmentStatusId) + '|%'

    要么

    1
    WHERE @CurrentShipmentStatusIdArray IS NULL OR @CurrentShipmentStatusIdArray LIKE '%|' + Shipments.CurrentShipmentStatusId+ '|%'

    创建一个存储名称的临时表,然后使用以下查询:

    1
    2
    3
    SELECT * FROM Tags
    WHERE Name IN (SELECT DISTINCT name FROM temp)
    ORDER BY COUNT DESC


    在SQL SERVER 2016或更高版本中,您可以使用STRING_SPLIT。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    DECLARE @InParaSeprated VARCHAR(MAX) = 'ruby,rails,scruffy,rubyonrails'
    DECLARE @Delimeter VARCHAR(10) = ','
    SELECT
        *
    FROM
        Tags T
        INNER JOIN STRING_SPLIT(@InputParameters,@Delimeter) SS ON T.Name = SS.value
    ORDER BY
        COUNT DESC

    我使用它是因为有些时候加入比我在查询中的Like运算符更快。
    此外,您可以以任何您喜欢的分隔格式输入无限数量的输入。
    我喜欢这个 ..