如何使用相同的SqlConnection对象在多个SqlCommands中声明和使用T-SQL变量来执行多个插入?
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何使用相同的SqlConnection对象在多个SqlCommands中声明和使用T-SQL变量来执行多个插入?相关的知识,希望对你有一定的参考价值。
我想加载一个记录列表,给出一个可能很长的用户名列表(从一到几千个用户名)。忽略如何选择名称,并假设无法从数据库中的任何现有数据确定这些名称。这适用于SQL Server 2005。
我特别希望避免在where子句中使用带有数千个表达式的单个select语句,这会导致SqlCommand对象的命令文本过长(例如...where n='bob000001' or n='bob000002' or ... or n='bob003000'
)。听起来合理吗?
我决定通过使用用户名填充一个简单的表变量来执行选择,然后使用用户数据在表变量和表之间执行select / join。
所以,我需要做的第一件事是填充表变量。我有一些问题:
- SQL Server 2008之前的T-SQL语法很简单,因为在单个语句中将多行插入表中,需要像multiple selects and union alls这样的东西。
- 我没有使用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的持久化变量,但是声明表变量的第一个查询不涉及参数(即没有引用command.Parameters。添加了AddWithValue,因此它应该在以后可以调用存储过程的命令的范围内。
编辑:要使用临时表,只需将@
s更改为#
s和declare @
s表“到create table #
s,这很好。人们可能还想在最后删除临时表,但这不是必需的。
使用临时表,该表在会话/连接期间持续存在(多次调用)。表变量只有批处理的范围,基本上是一个调用。
冗长的命令文本有什么问题?我在一次通话中执行了几千字节的大查询。 SQL2005支持它,我认为它总是比往返更好。
为什么不这样创建查询?
select StudentID, FName, LName, [etc.] from Student
where StudentID in ('bob000001', 'bob000002', [etc.])
Erland的文章应该解决你的问题:
http://www.sommarskog.se/arrays-in-sql-2005.html
不确定这是否是最佳解决方案,但我相信您可以通过将它们包装在BEGIN / END块中来批量处理多个命令,并使用单个调用来运行所有命令。
我不知道如何解决你的问题,但是我对你的问题的替代方法是让一个存储过程接受逗号分隔的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传递(尽管我的偏好可能是为了简化分隔的字符串)。
此外,即使出于任何原因您无法在数据库上创建存储过程,也应该可以使用上述技术。
以上是关于如何使用相同的SqlConnection对象在多个SqlCommands中声明和使用T-SQL变量来执行多个插入?的主要内容,如果未能解决你的问题,请参考以下文章
SqlConnection 和 SqlDataReader 的复用
ADO .Net - 如何在具有多个 IP 地址的系统上的 SqlConnection 中指定源 IP 地址
如何判断SqlConnection是否附加了SqlDataReader?
using(SqlConnection connection=new SqlConnection(connectionString))