SQL Server 2005:动态地将参数添加到存储过程

时间:2010-02-12 14:05:15

标签: c# sql-server tsql stored-procedures

方案

我有一个存储过程,它接受一个参数。我想更新这个存储过程以获取VARIABLE NUMBER OF PARAMETERS - 一个我永远不会知道的数字。 我目前通过C#接口使用SQLConnections,以便将单个参数传递给存储过程并返回结果。

SQL部件

假设我有一个存储过程,它根据单个输入参数“@ccy” - (货币)返回结果列表。现在让我们说我想更新这个存储过程来获取一个货币列表而不是一个货币列表,但这个数字将根据情况变化。

SQL代码

ALTER PROCEDURE [dbo].[SEL_BootStrapperInstRICs]
(
@ccy varchar(10)
)
AS

SELECT DISTINCT i.CCY, i.Instrument, i.Tenor, r.RIC, r.[Server], r.RIType
FROM MDR.dbo.tblBootStrapperInstruments as i, MDR.dbo.tblBootStrapperRICs as r
WHERE i.Instrument = r.MurexInstrument
AND
i.Tenor = r.Tenor
AND i.CCY = r.CCY
AND i.CCY = @ccy
AND r.RIType NOT LIKE '%forward%'

C#部分

从使用“SqlCommand.Parameters.AddWithValue()”方法的C#WinForms应用程序调用此特定存储过程。如前所述,此方法当前将单个Currency作为参数传递给存储过程,并将结果作为DataSet返回。

    public DataSet GetBootStrapperInstRICsDS(List<string> ccys)
    {
        DataSet ds;
        SqlConnection dbConn = null;
        SqlCommand dbCmd = new SqlCommand();

        try
        {
            dbConn = GetSQLConnection();
            dbCmd = GetSqlCommand();
            dbCmd.CommandType = CommandType.StoredProcedure;
            dbCmd.CommandText = Utils.Instance.GetSetting     ("SELBootStrapInsRics", "default");
            foreach(string ccy in ccys)
              dbCmd.Parameters.AddWithValue("@ccy", ccy);

            dbCmd.CommandTimeout = 600;
            dbCmd.Connection = dbConn;

            SqlDataAdapter adapter = new SqlDataAdapter(dbCmd);
            ds = new DataSet();
            adapter.Fill(ds, "tblBootStrapperInstRICs");

            dbCmd.Connection.Open();

            return ds;
        }
        catch (Exception ex)
        {
            ApplicationException aex = new ApplicationException        ("GetBootStrapperInstRICsDS", ex);
            aex.Source = "Dal.GetBootStrapperInstRICsDS " + ex.Message;
            MainForm.job.Log(aex.Source, Job.MessageType.Error);
            Job.incurredErrors = true;
            throw aex;
        }
        finally
        {
            if (dbCmd != null)
                dbCmd.Dispose();
            if (dbConn != null)
            {
                dbConn.Close();
                dbConn.Dispose();
            }
        }
    }

问题

在C#方面,我认为我最好的选择是使用“foreach / for循环”来迭代参数列表并动态地向SPROC添加新参数。 (我已经在上面的C#代码中进行了此更新。)

但是 - 在SQL存储过程中是否有某些方法可以做到这一点?我的想法分为两个可能的选项 - 在SPROC中创建20个或更多参数(每个参数名称相同,但最后有一个递增的数字,例如 - @ ccy1,@ ccy2等)并使用“for(int i = 0;我

    for(int i=0;i<NumberOfCurrenciesToAdd;i++)
       dbCmd.Parameters.AddWithValue("@ccy"+i, currencyArray[i]);   

或者另一种选择是做一些完全不同的事情,减少垃圾和黑客攻击。非常感谢。

编辑 - SQL Server 2005

EDIT2 - 必须使用SPROCS - 公司规范要求。

5 个答案:

答案 0 :(得分:4)

您从未指定过SQL Server版本,但对于2008年,有Table-Valued Parameters,这可能会对您有所帮助:

  

表值参数是SQL Server 2008中的新参数类型。表值参数是使用用户定义的表类型声明的。您可以使用表值参数将多行数据发送到Transact-SQL语句或例程(例如存储过程或函数),而无需创建临时表或许多参数。

答案 1 :(得分:2)

我曾在一家必须这样做的公司工作过。简单地传递nvarchar更容易,它实际上是一个逗号分隔的列表,然后在进入存储过程并将值插入临时表时解析它。另一种选择是在proc中有一个xml参数。这也应该有效。这完全适用于SQL 2005. 2008确实为您提供了表变量,这将是您的最佳选择。

我会尽量远离动态更改存储过程,因为我认为这很难维护。在任何给定的时间,如果你试图看看proc,它可能会有所不同。此外,当2个人试图使用您的网站并在同一时刻点击该进程时会发生什么?一个人的会话将修改程序,其他人将尝试这样做。这可能会导致存储过程锁定,或者可能导致其他问题。无论它效率都很低。

答案 2 :(得分:0)

这是另一种选择 - 尽管我认为安东的答案更好。您可以将csv字符串作为单个参数传递。使用用户定义的函数将csv字符串转换为值表,您可以将其加入查询中。在SO和其他地方列出了几个csv解析函数(但很抱歉,我现在无法提供链接)。

编辑:这是另一种选择。传入相同的csv字符串,然后在过程中生成sql查询作为字符串,并执行该字符串。在'in'子句中使用csv:

where i.ccy in (1,2,3,4)

答案 3 :(得分:0)

我不会尝试更改存储过程,但是(因为您在SQL Server 2005上并且没有表变量参数)只需传入逗号分隔的值列表,然后让过程将它们分开。您可以将C#循环更改为仅构建CSV字符串,并在创建SQL拆分过程后使用它,如:

SELECT
    *
    FROM YourTable                               y
    INNER JOIN dbo.yourSplitFunction(@Parameter) s ON y.ID=s.Value

I prefer the number table approach to split a string in TSQL

要使此方法起作用,您需要执行以下一次性表设置:

SELECT TOP 10000 IDENTITY(int,1,1) AS Number
    INTO Numbers
    FROM sys.objects s1
    CROSS JOIN sys.objects s2
ALTER TABLE Numbers ADD CONSTRAINT PK_Numbers PRIMARY KEY CLUSTERED (Number)

设置Numbers表后,创建此拆分功能:

CREATE FUNCTION [dbo].[FN_ListToTable]
(
     @SplitOn  char(1)      --REQUIRED, the character to split the @List string on
    ,@List     varchar(8000)--REQUIRED, the list to split apart
)
RETURNS TABLE
AS
RETURN 
(

    ----------------
    --SINGLE QUERY-- --this will not return empty rows
    ----------------
    SELECT
        ListValue
        FROM (SELECT
                  LTRIM(RTRIM(SUBSTRING(List2, number+1, CHARINDEX(@SplitOn, List2, number+1)-number - 1))) AS ListValue
                  FROM (
                           SELECT @SplitOn + @List + @SplitOn AS List2
                       ) AS dt
                      INNER JOIN Numbers n ON n.Number < LEN(dt.List2)
                  WHERE SUBSTRING(List2, number, 1) = @SplitOn
             ) dt2
        WHERE ListValue IS NOT NULL AND ListValue!=''

);
GO 

您现在可以轻松地将CSV字符串拆分为表格并加入其中:

select * from dbo.FN_ListToTable(',','1,2,3,,,4,5,6777,,,')

输出:

ListValue
-----------------------
1
2
3
4
5
6777

(6 row(s) affected)

您可以将CSV字符串传递到过程中,并仅处理给定ID的行:

SELECT
    y.*
    FROM YourTable y
        INNER JOIN dbo.FN_ListToTable(',',@GivenCSV) s ON y.ID=s.ListValue

答案 4 :(得分:0)

我使用此函数将CSV文本拆分为数字表,由于各种优化(例如返回带有主键的表,这会极大地影响查询优化器,从而为极大数据生成良好的查询计划)集)。

此外,它不限于4000个字符,因此您可以传入非常大的字符串。

CREATE Function [dbo].[TextSplitToInt](@list  text,
                               @delim char(1) = N',')
   RETURNS @T TABLE (ID_T int primary key)

   BEGIN
      DECLARE @slices TABLE (slice nvarchar(4000) NOT NULL)
      DECLARE @slice nvarchar(4000),
              @textpos int,
              @maxlen int,
              @stoppos int

      SELECT @textpos = 1, @maxlen = 4000 - 2
      WHILE datalength(@list) / 2 - (@textpos - 1) >= @maxlen
      BEGIN
         SELECT @slice = substring(@list, @textpos, @maxlen)
         SELECT @stoppos = @maxlen - charindex(@delim, reverse(@slice))
         INSERT @slices (slice) VALUES (@delim + left(@slice, @stoppos) + @delim)
         SELECT @textpos = @textpos - 1 + @stoppos + 2   -- On the other side of the comma.
      END
      INSERT @slices (slice)
          VALUES (@delim + substring(@list, @textpos, @maxlen) + @delim)

      INSERT @T (ID_T)
         SELECT distinct Cast(str as int)
         FROM   (SELECT str = ltrim(rtrim(substring(s.slice, N.Number + 1,
                        charindex(@delim, s.slice, N.Number + 1) - N.Number - 1)))
                 FROM  Numbers N
                 JOIN  @slices s ON N.Number <= len(s.slice) - 1
                                AND substring(s.slice, N.Number, 1) = @delim) AS x

      RETURN
   END