Как я могу объявить и использовать переменные T-SQL в нескольких SqlCommands, используя один и тот же объект SqlConnection для выполнения нескольких вставок? - PullRequest
2 голосов
/ 27 мая 2009

Я хочу загрузить список записей с возможно длинным списком имен пользователей (от одного до тысяч имен пользователей). Не обращайте внимания на то, как выбраны имена, и предположите, что они не могут быть определены на основе существующих данных в базе данных. Это относится к SQL Server 2005.

Я специально хочу избегать , используя оператор одиночного выбора с тысячами выражений в предложении where , что привело бы к чрезмерно длинному тексту команды для объект SqlCommand (например, ...where n='bob000001' or n='bob000002' or ... or n='bob003000'). Звучит разумно?

Я решил выполнить выбор, заполнив простую табличную переменную именами пользователей, затем выполнив выбор / объединение между табличной переменной и таблицей с пользовательскими данными.

Итак, первое, что мне нужно сделать, это заполнить табличную переменную. У меня есть некоторые проблемы здесь:

  • Синтаксис T-SQL до SQL Server 2008 является многословным для вставки нескольких строк в таблицу в одном операторе, для чего требуется что-то вроде множественное выделение и объединение всех .
  • Вместо того, чтобы использовать подробный синтаксис SS2005 или даже краткий синтаксис, доступный в SQL Server 2008, я вообще избегаю длинных текстов команд и просто использую несколько команд по одному соединению.
  • Объявление табличной переменной в одной SqlCommand приводит к ошибке «необходимо объявить скалярную переменную», когда я пытаюсь использовать ее в последующих SqlCommands.
  • Вовлечение хранимых процедур любым способом может по-прежнему включать передачу огромных строк или препятствовать сохранению переменных вне области действия хранимой процедуры. Предположим, создание хранимых процедур не вариант.

Этот третий пункт - действительно проблема, которую я пытаюсь решить сейчас. Я видел примеры, когда люди (утверждают, что) успешно объявляют и используют переменную в одном SqlCommand без ошибки. Как этого достичь при использовании нескольких экземпляров SqlCommand? Я читал, что переменные будут сохраняться для одного соединения между несколькими командами. Может ли помочь транзакция каким-либо образом?

Наконец, имейте в виду, что я не хочу использовать временные таблицы; это предложит простое решение, но оно также позволит избежать вопроса, который я задаю относительно переменных и нескольких SqlCommands; однако, если вы действительно считаете, что это лучший вариант, не стесняйтесь так говорить.

Вот фрагмент кода, который показывает, что происходит:

public static List<Student> Load( SqlConnection conn, List<StudentID> usernames )
{
    //Create table variable
    SqlCommand  command = new SqlCommand( "declare @s table (id varchar(30))", conn );
    command.ExecuteNonQuery();

    //Populate a table variable with the usernames to load
    command = new SqlCommand( "insert into @s (id) values (@p)", conn );
    command.Parameters.Add( "@p", SqlDbType.VarChar );
    int len = usernames.Count;
    for (int i = 0; i < len; i++)
    {
        command.Parameters["@p"].Value = usernames[i].ToString();
        command.ExecuteNonQuery(); //ERROR: must declare scalar variable @s
    }

    //Select all students listed in the table variable
    command = new SqlCommand( "select StudentID, FName, LName, [etc.] from Student inner join @s on StudentID = @s.id order by StudentID", conn );

    //Execute the query to get the student info from the database.
    List<Student> students = new List<Student>()
    using(SqlDataReader reader = command.ExecuteReader())
    {
        //code to load results and populate students list
    }
    return students;
}

Примечание: я знаю, что SqlCommand, включающий параметры, внутренне вызывает хранимую процедуру, которая обычно предотвращает сохранение переменных в нескольких SqlCommands, но первый запрос, который объявляет табличную переменную, не включает параметры (т.е. нет ссылки на команду .Parameters.AddWithValue), поэтому он должен находиться в области действия для последующих команд, которые могут вызывать хранимые процедуры.

Редактировать: Чтобы использовать временную таблицу, нужно просто изменить @ s на # s и declare @ s table "на create table # s, что хорошо. Можно также захотеть сбросить временную таблицу стол в конце, но это не обязательно.

Ответы [ 5 ]

5 голосов
/ 27 мая 2009

Используйте временную таблицу, которая сохраняется в течение сеанса / соединения (несколько вызовов). Табличные переменные имеют область действия только для пакета, который в основном является одним вызовом.

1 голос
/ 27 мая 2009

В статье Эрланда должны быть рассмотрены ваши вопросы:

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

1 голос
/ 27 мая 2009

Что не так с длинными текстами команд ? Я выполнил несколько больших килобайтных запросов за один вызов. SQL2005 поддерживает его, и я думаю, что это лучше, чем все время обхода.

Почему бы не создать такой запрос?

select StudentID, FName, LName, [etc.] from Student 
where StudentID in ('bob000001', 'bob000002', [etc.])
0 голосов
/ 27 мая 2009

Я не знаю, как решить ваш вопрос, однако мой альтернативный подход к вашей проблеме вместо этого будет иметь хранимую процедуру, которая принимает список идентификаторов, разделенных запятыми, вставляет эти идентификаторы во временную таблицу и затем выполняет «SELECT WHERE». IN ", например (изменено из другого блога):

CREATE PROC dbo.SelectStudents
(
    @StudentIdList varchar(8000)
)
AS
BEGIN
    SET NOCOUNT ON

    CREATE TABLE #StudentIdList (
        StudentId int,
    )

    DECLARE @StudentId varchar(10), @Pos int

    SET @StudentIdList = LTRIM(RTRIM(@StudentIdList)) + ','
    SET @Pos = CHARINDEX(',', @StudentIdList, 1)

    IF REPLACE(@StudentIdList, ',', '') <> ''
    BEGIN
        WHILE @Pos > 0
        BEGIN
            SET @StudentId = LTRIM(RTRIM(LEFT(@StudentIdList, @Pos - 1)))
            IF @StudentId <> ''
            BEGIN
                INSERT INTO #StudentIdList (StudentId) VALUES (CAST(@StudentId AS int))
            END
            SET @StudentIdList = RIGHT(@StudentIdList, LEN(@StudentIdList) - @Pos)
            SET @Pos = CHARINDEX(',', @StudentIdList, 1)
        END
    END 

    SELECT  * 
    FROM    dbo.Students
    WHERE   StudentId IN
    (
        SELECT StudentId FROM #StudentIdList
    )
END

Затем вы можете вызвать эту хранимую процедуру с разделенным запятыми списком идентификаторов:

exec dbo.SelectStudents '1,2'

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

Аналогичные подходы существуют для передачи списка идентификаторов в формате Xml (хотя я бы предпочел пойти на простоту разделенных строк).

Также возможно использовать вышеуказанную технику, даже если по какой-либо причине вы не можете создавать хранимые процедуры в своей базе данных.

0 голосов
/ 27 мая 2009

Не уверен, что это лучшее решение, но я считаю, что вы можете пакетировать несколько команд, поместив их в блок BEGIN / END, и использовать один вызов для их выполнения.

...