SQL Server 2005: динамическое добавление параметров в хранимую процедуру - PullRequest
3 голосов
/ 12 февраля 2010

Сценарий

У меня есть хранимая процедура, которая принимает один параметр. Я хочу обновить эту хранимую процедуру, чтобы получить переменное число параметров - число, которое я никогда не узнаю. В настоящее время я использую SQLConnections через интерфейс C #, чтобы передать один параметр хранимой процедуре и вернуть результат.

Часть SQL

Допустим, у меня есть хранимая процедура, которая возвращает список результатов на основе одного входного параметра "@ccy" - (Currency). Теперь допустим, что я хочу обновить эту хранимую процедуру, чтобы взять список валют вместо одной, но это число будет переменным в зависимости от ситуации.

Код 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 #

Эта конкретная хранимая процедура вызывается из приложения C # WinForms, которое использует метод "SqlCommand.Parameters.AddWithValue ()". Как упоминалось ранее, этот метод в настоящее время передает в качестве параметра для хранимой процедуры одну валюту и возвращает результат в виде набора данных.

    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? Мои мысли разделены двумя потенциальными вариантами: либо создайте 20 или более параметров в SPROC (каждый с тем же именем, но с возрастающим числом в конце, например, - @ 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 ]

4 голосов
/ 12 февраля 2010

Вы никогда не указывали версию SQL Server, но для 2008 года существует Табличные параметры , которые могут вам помочь:

Табличные параметры - это новый тип параметров в SQL Server 2008. Табличные параметры объявляются с использованием пользовательских типов таблиц. Вы можете использовать табличные параметры для отправки нескольких строк данных в инструкцию Transact-SQL или подпрограмму, такую ​​как хранимая процедура или функция, без создания временной таблицы или множества параметров.

2 голосов
/ 12 февраля 2010

Я работал в компании, которая должна была это сделать. Гораздо проще просто передать nvarchar, который действительно является списком, разделенным запятыми, а затем проанализировать его, когда вы попадете в сохраненный процесс и вставите значения во временную таблицу. Другой вариант будет иметь параметр xml в вашем proc. Это также должно работать. Это все для SQL 2005. В 2008 году вы получите переменную таблицы, и это будет вашим лучшим вариантом.

Я бы постарался избежать динамического изменения вашего хранимого процесса, потому что я думаю, что это будет трудно поддерживать. В любой момент, если вы попытаетесь посмотреть на процесс, он может отличаться. Кроме того, что происходит, когда 2 человека пытаются использовать ваш сайт и одновременно запустить этот процесс? Сессия одного человека будет изменять процедуру, а другие будут пытаться это сделать. Это может вызвать блокировку хранимого процесса или другие проблемы. В любом случае это было бы неэффективно.

0 голосов
/ 12 февраля 2010

Я использую эту функцию для разделения текста 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
0 голосов
/ 12 февраля 2010

Я бы не пытался изменить хранимую процедуру, но (поскольку у вас SQL Server 2005 и у вас нет параметров табличных переменных) просто передайте список значений через запятую и дайте процедуре разделить их на части. Вы можете изменить цикл C # так, чтобы он просто строил строку CSV, и после создания процедуры SQL-разделения используйте ее следующим образом:

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

Я предпочитаю подход таблицы чисел для разделения строки в 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 в процедуру и обработать только строки с заданными идентификаторами:

SELECT
    y.*
    FROM YourTable y
        INNER JOIN dbo.FN_ListToTable(',',@GivenCSV) s ON y.ID=s.ListValue
0 голосов
/ 12 февраля 2010

Вот еще один вариант - хотя я думаю, что ответ Антона лучше. Вы можете передать строку csv как один параметр. Используйте пользовательскую функцию для преобразования строки csv в таблицу значений, которую вы можете присоединить в своем запросе. В SO и других местах есть несколько функций синтаксического анализа csv (хотя, извините, сейчас я не могу найти ссылку).

изменить: вот еще один вариант. Передайте ту же строку csv, затем сгенерируйте запрос sql как строку в процедуре и выполните строку. Используйте CSV в предложении «in»:

where i.ccy in (1,2,3,4)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...