【问题标题】:WHERE IN (array of IDs)WHERE IN(ID 数组)
【发布时间】:2010-09-15 23:21:54
【问题描述】:

我有 web 服务,它传递了一个整数数组。 我想按如下方式执行 select 语句,但不断出现错误。我需要将数组更改为字符串吗?

[WebMethod]
public MiniEvent[] getAdminEvents(int buildingID, DateTime startDate)
{    
    command.CommandText = @"SELECT id,
                            startDateTime, endDateTime From
                            tb_bookings WHERE buildingID IN
                            (@buildingIDs) AND startDateTime <=
                            @fromDate";

    SqlParameter buildID = new SqlParameter("@buildingIDs", buildingIDs);
}

【问题讨论】:

  • 如果您使用的是 SQL Server 2008,则可以使用表值参数,该参数允许您在单个参数中传递多个值。然后,您很可能会执行联接而不是“在 () 中的位置”,尽管两者都可以。 Table-Valued Parameters
  • 嗯...没有注意到这个问题最初是在 2008 年 10 月提出的。所以可能不会...希望这至少可以帮助任何将其作为搜索结果的人。

标签: c# sql-server tsql ado.net sqlparameter


【解决方案1】:

你不能(很遗憾)那样做。一个 Sql 参数只能是一个值,所以你必须这样做:

WHERE buildingID IN (@buildingID1, @buildingID2, @buildingID3...)

当然,这需要您知道有多少建筑物 ID,或者动态构建查询。

作为一种解决方法*,我做了以下操作:

WHERE buildingID IN (@buildingID)

command.CommandText = command.CommandText.Replace(
  "@buildingID", 
  string.Join(buildingIDs.Select(b => b.ToString()), ",")
);

它将用数字替换语句的文本,结果如​​下:

WHERE buildingID IN (1,2,3,4)
  • 请注意,这接近于 Sql 注入漏洞,但由于它是一个 int 数组,因此是安全的。任意字符串安全,但无法将 Sql 语句嵌入整数(或日期时间、布尔值等)中。

【讨论】:

  • 对此的一个警告是,如果buildingIDs 为空,则该代码将产生语法错误,因此您必须单独处理这种情况。
【解决方案2】:

首先你需要一个函数和一个存储过程。该函数将拆分您的数据并返回一个表格:

CREATE function IntegerCommaSplit(@ListofIds nvarchar(1000))
returns @rtn table (IntegerValue int)
AS
begin
While (Charindex(',',@ListofIds)>0)
Begin
    Insert Into @Rtn 
    Select ltrim(rtrim(Substring(@ListofIds,1,Charindex(',',@ListofIds)-1)))
    Set @ListofIds = Substring(@ListofIds,Charindex(',',@ListofIds)+len(','),len(@ListofIds))
end
Insert Into @Rtn 
    Select  ltrim(rtrim(@ListofIds))
return 
end

接下来你需要一个存储过程来使用它:

create procedure GetAdminEvents 
    @buildingids nvarchar(1000),
    @startdate datetime
as
SELECT id,startDateTime, endDateTime From
            tb_bookings t INNER JOIN 
dbo.IntegerCommaSplit(@buildingids) i
on i.IntegerValue = t.id
 WHERE startDateTime <= @fromDate

最后,你的代码:

[WebMethod]
        public MiniEvent[] getAdminEvents(int[] buildingIDs, DateTime startDate)
        command.CommandText = @"exec GetAdminEvents";
 SqlParameter buildID= new SqlParameter("@buildingIDs", buildingIDs);

这远远超出了您的问题,但它会满足您的需求。

注意:如果你传入任何不是 int 的东西,整个数据库函数都会失败。我将对此的错误处理留给最终用户练习。

【讨论】:

    【解决方案3】:

    注意:我通常不喜欢使用未参数化的查询。然而,在本例中,鉴于我们正在处理一个整数数组,您可以 做这样的事情,这样会更有效率。但是,鉴于每个人似乎都想降级答案,因为它不符合他们的有效建议标准,我将提交另一个执行得很糟糕但可能会在 LINK2SQL 中运行的答案。

    假设,正如您的问题所述,您有一个整数数组,您可以使用以下代码返回一个字符串,该字符串将包含 SQL 接受的逗号分隔列表:

    private string SQLArrayToInString(Array a)
    {
     StringBuilder sb = new StringBuilder();
     for (int i = 0; i < a.GetUpperBound(0); i++)
      sb.AppendFormat("{0},", a.GetValue(i));
     string retVal = sb.ToString();
     return retVal.Substring(0, retVal.Length - 1);
    }
    

    然后,我建议您跳过尝试参数化命令鉴于这是一个整数数组,只需使用:

    command.CommandText = @"SELECT id,
                startDateTime, endDateTime From
                tb_bookings WHERE buildingID IN
                (" + SQLArrayToInString(buildingIDs) + ") AND startDateTime <=
                @fromDate";
    

    【讨论】:

    • 不,请不要跳过参数化语句,这是危险的建议!
    • 我同意。请不要建议人们使用未参数化的查询。
    • 谁能详细说明是什么让这变得如此危险?
    • @jeffrey - 一方面,未参数化的查询会使您容易受到 SQL 注入攻击
    • @tjmoore 究竟如何使用整数数组执行 SQL 注入攻击?
    【解决方案4】:

    一种不需要不安全代码或用户定义函数的超快速 XML 方法:

    您可以使用存储过程并传递以逗号分隔的建筑物 ID 列表:

    Declare @XMLList xml
    SET @XMLList=cast('<i>'+replace(@buildingIDs,',','</i><i>')+'</i>' as xml)
    SELECT x.i.value('.','varchar(5)') from @XMLList.nodes('i') x(i))
    

    所有功劳归 Guru Brad Schulz's Blog

    【讨论】:

      【解决方案5】:

      请访问 T-SQL stored procedure that accepts multiple Id values 了解如何执行此操作。

      【讨论】:

        【解决方案6】:

        我使用这种方法并为我工作。

        我的变量 act = 我在字符串中的 ID 列表。

        act = "1, 2, 3, 4"

         command = new SqlCommand("SELECT x FROM y WHERE x.id IN (@actions)", conn);    
         command.Parameters.AddWithValue("@actions", act);
         command.CommandText = command.CommandText.Replace("@actions", act);
        

        【讨论】:

          【解决方案7】:

          [网络方法]

          public MiniEvent[] getAdminEvents(int buildingID, DateTime startDate)

          ...

          SqlParameter buildID= new SqlParameter("@buildingIDs", buildingIDs);

          也许我说得太详细了,但这个方法接受一个 int,而不是一个 int 数组。如果您希望传入一个数组,则需要更新您的方法定义以拥有一个 int 数组。获得该数组后,如果您打算在 SQL 查询中使用该数组,则需要将该数组转换为字符串。

          【讨论】:

            【解决方案8】:

            你可以使用它。在 SQLServer 中执行以在您的数据库上创建一个函数(仅一次):

            IF EXISTS(
                SELECT *
                FROM sysobjects
                WHERE name = 'FN_RETORNA_ID_FROM_VARCHAR_TO_TABLE_INT')
            BEGIN
                DROP FUNCTION FN_RETORNA_ID_FROM_VARCHAR_TO_TABLE_INT
            END
            GO
            
            CREATE FUNCTION [dbo].FN_RETORNA_ID_FROM_VARCHAR_TO_TABLE_INT (@IDList VARCHAR(8000))
            RETURNS
                @IDListTable TABLE (ID INT)
            AS
            BEGIN
            
                DECLARE
                    --@IDList VARCHAR(100),
                    @LastCommaPosition INT,
                    @NextCommaPosition INT,
                    @EndOfStringPosition INT,
                    @StartOfStringPosition INT,
                    @LengthOfString INT,
                    @IDString VARCHAR(100),
                    @IDValue INT
            
                --SET @IDList = '11,12,113'
            
                SET @LastCommaPosition = 0
                SET @NextCommaPosition = -1
            
                IF LTRIM(RTRIM(@IDList)) <> ''
                BEGIN
            
                    WHILE(@NextCommaPosition <> 0)
                    BEGIN
            
                        SET @NextCommaPosition = CHARINDEX(',',@IDList,@LastCommaPosition + 1)
            
                        IF @NextCommaPosition = 0
                            SET @EndOfStringPosition = LEN(@IDList)
                        ELSE
                            SET @EndOfStringPosition = @NextCommaPosition - 1
            
                        SET @StartOfStringPosition  = @LastCommaPosition + 1
                        SET @LengthOfString = (@EndOfStringPosition + 1) - @StartOfStringPosition
            
                        SET @IDString =  SUBSTRING(@IDList,@StartOfStringPosition,@LengthOfString)                  
            
                        IF @IDString <> ''
                            INSERT @IDListTable VALUES(@IDString)
            
                        SET @LastCommaPosition = @NextCommaPosition
            
                    END --WHILE(@NextCommaPosition <> 0)
            
                END --IF LTRIM(RTRIM(@IDList)) <> ''
            
                RETURN
            
            ErrorBlock:
            
                RETURN
            
            END --FUNCTION
            

            创建函数后,您必须在代码上调用它:

            command.CommandText = @"SELECT id,
                                    startDateTime, endDateTime From
                                    tb_bookings WHERE buildingID IN
                                    (SELECT ID FROM FN_RETORNA_ID_FROM_VARCHAR_TO_TABLE_INT(@buildingIDs))) AND startDateTime <=
                                    @fromDate";
            
            command.Parameters.Add(new SqlParameter(){
                                       DbType = DbType.String,
                                       ParameterName = "@buildingIDs",
                                       Value = "1,2,3,4,5" //Enter the parameters here separated with commas
                                   });
            

            此函数获取“数组”上的文本内逗号,并以该值为 int 的形式创建一个表,称为 ID。当此功能在您的数据库中时,您可以在任何项目中使用。


            感谢微软 MSDN。

            伊戈·文图拉

            微软 MVA

            Sistema Ari de Sá

            igo1-2@hotmail.com

            P.S.:我来自巴西。对不起我的英语... XD

            【讨论】:

              【解决方案9】:

              这是我想出的一个 Linq 解决方案。它会自动将列表中的所有项目作为参数@item0、@item1、@item2、@item3 等插入。

              [WebMethod]
              public MiniEvent[] getAdminEvents(Int32[] buildingIDs, DateTime startDate)
              {
                  // Gets a list with numbers from 0 to the max index in buildingIDs,
                  // then transforms it into a list of strings using those numbers.
                  String idParamString = String.Join(", ", (Enumerable.Range(0, buildingIDs.Length).Select(i => "@item" + i)).ToArray());
                  command.CommandText = @"SELECT id,
                                      startDateTime, endDateTime From
                                      tb_bookings WHERE buildingID IN
                                      (" + idParamString + @") AND startDateTime <=
                                      @fromDate";
                  // Reproduce the same parameters in idParamString 
                  for (Int32 i = 0; i < buildingIDs.Length; i++)
                          command.Parameters.Add(new SqlParameter ("@item" + i, buildingIDs[i]));
                  command.Parameters.Add(new SqlParameter("@fromDate", startDate);
                  // the rest of your code...
              }
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2011-08-13
                • 1970-01-01
                • 1970-01-01
                • 2018-11-14
                • 1970-01-01
                • 2017-09-27
                相关资源
                最近更新 更多