3

我想加载一个可能很长的用户名列表(从一个到数千个用户名)的记录列表。忽略名称是如何选择的,并假设它们无法从数据库中的任何现有数据中确定。这适用于 SQL Server 2005。

我特别想避免在 where 子句中使用带有数千个表达式的单个 select 语句,这会导致 SqlCommand 对象的命令文本过长(例如...where n='bob000001' or n='bob000002' or ... or n='bob003000')。听起来合理吗?

我决定通过使用用户名填充一个简单的表变量来执行选择,然后在表变量和带有用户数据的表之间执行选择/连接。

所以,我需要做的第一件事是填充表变量。我这里有一些问题:

  • SQL Server 2008 之前的 T-SQL 语法对于在单个语句中将多行插入表中是冗长的,需要类似multiple selects 和 union alls 之类的东西。
  • 我没有使用 SS2005 的冗长语法,甚至 SQL Server 2008 中可用的简洁语法,而是完全避免冗长的命令文本,而只是在单个连接上使用多个命令。
  • 在一个 SqlCommand 中声明表变量,当我尝试在后续 SqlCommand 中使用它时会产生“必须声明标量变量”错误。
  • 以任何方式涉及存储过程仍可能涉及传递巨大的字符串,或者可能会阻止变量在存储过程范围之外持续存在。假设创建存储过程不是一种选择。

第三点确实是我现在要解决的问题。我已经看到人们(声称)在单个 SqlCommand 中成功声明和使用变量而没有错误的示例。使用多个 SqlCommand 实例时如何实现这一点?我读到变量将针对多个命令的单个连接持续存在。可能以某种方式涉及交易帮助?

最后,请记住,我不想使用临时表;这样做会提供一个简单的解决方案,但它也避免了我提出的关于变量和多个 SqlCommand 的问题;但是,如果您真的认为这是最好的选择,请随意说出来。

这是一个显示正在发生的事情的代码片段:

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 持久化变量,但声明表变量的第一个查询不涉及参数(即不引用 command.Parameters。 AddWithValue 已生成),因此它应该在以后可能调用存储过程的命令的范围内。

编辑:要使用临时表,只需将@s 更改为#s 并将declare @s table" 更改为create table #s,这很好。人们可能还想在最后删除临时表,但这不是必需的。

4

4 回答 4

6

使用在会话/连接期间持续存在的临时表(多次调用)。表变量仅适用于批处理,这基本上是一次调用。

于 2009-05-27T14:19:52.047 回答
1

冗长的命令文本有什么问题?我在一次调用中执行了几千字节的大查询。SQL2005支持它,我认为它比一直往返更好。

为什么不创建这样的查询?

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

我不知道如何解决您的问题,但是我对您的问题的替代方法是使用一个接受逗号分隔的 ID 列表的存储过程,将这些 ID 插入到临时表中,然后执行“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

然后,您可以使用逗号分隔的 id 列表调用此存储过程:

exec dbo.SelectStudents '1,2'

您需要确保格式化的 id 列表不超过 8000 个字符(如果是,请多次调用数据库)但是这种方法效率更高,并且只需要一个大参数,而不是长命令文本- 大量访问数据库以插入每个 Id 将非常缓慢。

存在类似的方法将 Id 列表作为 Xml 传递(尽管我的偏好可能是为了简化分隔字符串)。

此外,即使出于某种原因您无法在数据库上创建存储过程,也应该可以使用上述技术。

于 2009-05-27T14:26:46.403 回答
0

不确定这是否是最好的解决方案,但我相信您可以通过将多个命令包装在 BEGIN/END 块中来批处理多个命令,并使用一个调用来运行所有命令。

于 2009-05-27T13:47:36.793 回答