3

おそらく長いユーザー名のリスト(1から数千のユーザー名)を指定して、レコードのリストをロードしたいと思います。名前の選択方法を無視し、データベース内の既存のデータから名前を判別できないと想定します。これはSQLServer2005に適用されます。

特に、where句に数千の式を含む単一のselectステートメントを使用しないようにします。これにより、SqlCommandオブジェクトのコマンドテキストが非常に長くなります(例)。合理的に聞こえますか?...where n='bob000001' or n='bob000002' or ... or n='bob003000'

単純なテーブル変数にユーザー名を入力し、テーブル変数とテーブルとユーザーデータの間で選択/結合を実行して、選択を実行することにしました。

したがって、最初に行う必要があるのは、テーブル変数にデータを入力することです。ここでいくつか問題があります:

  • SQL Server 2008より前のT-SQL構文は、単一のステートメントでテーブルに複数の行を挿入するために冗長であり、複数のselectやunionallsなどが必要です。
  • SS2005の冗長な構文、またはSQL Server 2008で使用可能な簡潔な構文を使用するのではなく、長いコマンドテキストを完全に避け、単一の接続で複数のコマンドを使用するだけです。
  • 1つのSqlCommandでテーブル変数を宣言すると、後続のSqlCommandで使用しようとすると、「スカラー変数を宣言する必要があります」というエラーが発生します。
  • 何らかの方法でストアドプロシージャを含めると、依然として巨大な文字列を渡す必要がある場合や、変数がストアドプロシージャの範囲外に存続するのを妨げる場合があります。ストアドプロシージャの作成はオプションではないと想定します。

その3番目のポイントは、私が今解決しようとしている問題です。私は、人々(主張する)がエラーなしで単一の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は、ストアドプロシージャを内部的に呼び出すことを認識しています。これにより、通常、複数のSqlCommand間で変数を永続化できなくなりますが、テーブル変数を宣言する最初のクエリにはパラメーターが含まれません(つまり、command.Parametersへの参照はありません)。 AddWithValueが作成されます)。したがって、ストアドプロシージャを呼び出す可能性のある後のコマンドのスコープ内にある必要があります。

@編集:一時テーブルを使用するには、 sを#sに、declare @sテーブルをsに変更するだけcreate table #です。これは便利です。最後に一時テーブルを削除することもできますが、必須ではありません。

4

4 に答える 4

6

セッション/接続(複数の呼び出し)の間持続する一時テーブルを使用します。テーブル変数には、基本的に1回の呼び出しであるバッチのみのスコープがあります。

于 2009-05-27T14:19:52.047 に答える
1

長いコマンドテキストの何が問題になっていますか?1回の呼び出しで数キロバイトの大きなクエリを実行しました。SQL2005はそれをサポートしており、常にラウンドトリップするよりも優れていると思います。

このようなクエリを作成してみませんか?

select StudentID, FName, LName, [etc.] from Student 
where StudentID in ('bob000001', 'bob000002', [etc.])
于 2009-05-27T15:02:31.493 に答える
0

これが最善の解決策かどうかはわかりませんが、BEGIN / ENDブロックでラップすることで複数のコマンドをバッチ処理し、1回の呼び出しですべてを実行できると思います。

于 2009-05-27T13:47:36.793 に答える
0

私はあなたの質問を解決する方法を知りませんが、あなたの問題に対する私の代替アプローチは、代わりにIDのコンマ区切りリストを受け入れ、それらのIDを一時テーブルに挿入してから、「SELECTWHEREIN」を実行するストアドプロシージャを持つことです。たとえば(別のブログから変更):

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

次に、IDのコンマ区切りリストを使用してこのストアドプロシージャを呼び出すことができます。

exec dbo.SelectStudents '1,2'

フォーマットされたIDのリストが8000文字以下であることを確認する必要があります(そうである場合はデータベースを複数回呼び出します)が、この方法ははるかに効率的で、長いコマンドテキストではなく大きなパラメーターのみを必要とします-各IDを挿入するためにデータベースに何度もアクセスすると、非常に時間がかかります。

IdのリストをXmlとして渡すための同様のアプローチが存在します(ただし、私の好みは、区切られた文字列の単純さのために行くことでしょう)。

また、何らかの理由でデータベースにストアドプロシージャを作成できない場合でも、上記の手法を使用できる必要があります。

于 2009-05-27T14:26:46.403 に答える