语言
表值参数提供了一将多行数据从客户端应用程序封送到 SQL Server 的种简单方法,而无需进行多次往返或特殊的服务器端逻辑来处理数据。 可使用表值参数来封装客户端应用程序中的数据行,并以单个参数化命令将数据发送到服务器。 传入数据行存储在随后可使用 Transact-SQL 进行操作的表变量中。
可以使用标准 Transact-SQL SELECT 语句访问表值参数中的列值。 表值参数为强类型,其结构会自动进行验证。 表值参数的大小仅受服务器内存的限制。
注释
无法返回表值参数中的数据。 表值参数仅用作输入参数;不支持关键字 OUTPUT。
若要详细了解表值参数,请参阅以下资源。
| 资源 | DESCRIPTION |
|---|---|
| 使用表值参数(数据库引擎) | 介绍如何创建和使用表值参数。 |
| 用户定义的表类型 | 说明用于声明表值参数的用户定义的表类型。 |
在 SQL Server 的早期版本中传递多行
在 SQL Server 2008 中引入表值参数之前,用于将多行数据传递到存储过程或参数化 SQL 命令的选项受到限制。 开发人员可以选择下面的一种方法,将多行传递到服务器:
使用一系列单独的参数来表示多列和多行数据中的值。 使用这种方法可以传递的数据量受到允许使用的参数数量限制。 SQL Server 过程最多可以有 2100 个参数。 必须使用服务器端逻辑,将这些单独的值汇编到表变量或临时表中以供处理。
将多个数据值绑定到分隔字符串或 XML 文档,然后将这些文本值传递到过程或语句。 这要求过程或语句包含验证数据结构和解除绑定值所需的逻辑。
针对影响多个行的数据修改创建一系列的单个 SQL 语句,例如通过 SqlDataAdapter 的
Update方法创建的内容。 单独或批量提交更改。 即使在批处理中,服务器也会单独执行每个语句。使用
bcp实用工具或 SqlBulkCopy 对象将多行数据加载到表中。 尽管此方法非常高效,但它不支持服务器端处理,除非将数据加载到临时表或表变量中。
创建表值参数类型
表值参数基于使用 Transact-SQL CREATE TYPE 语句定义的强类型表结构。 必须先在 SQL Server 中创建一个表类型并定义结构,才能在客户端应用程序中使用表值参数。 有关创建表类型的详细信息,请参阅 User-Defined 表类型。
以下语句创建一个名为 CategoryTableType 的表类型,其中包含 CategoryID 列和 CategoryName 列:
CREATE TYPE dbo.CategoryTableType AS TABLE
( CategoryID int, CategoryName nvarchar(50) )
创建表类型后,可以基于该类型声明表值参数。 下面的 Transact-SQL 片段演示如何在存储过程定义中声明表值参数。 请注意,声明表值参数时需要使用 READONLY 关键字。
CREATE PROCEDURE usp_UpdateCategories
(@tvpNewCategories dbo.CategoryTableType READONLY)
使用 Table-Valued 参数修改数据(Transact-SQL)
表值参数可用于基于集合的数据修改,这些修改通过执行一个语句来影响多行。 例如,可以选择表值参数中的所有行并将它们插入数据库表,也可以通过将表值参数联接到要更新的表来创建更新语句。
以下 Transact-SQL UPDATE 语句演示如何通过将表值参数联接到 Categories 表来使用表值参数。 在JOIN子句中将表值参数与FROM一起使用时,还必须为其添加别名,如下所示,其中表值参数的别名为“ec”:
UPDATE dbo.Categories
SET Categories.CategoryName = ec.CategoryName
FROM dbo.Categories INNER JOIN @tvpEditedCategories AS ec
ON dbo.Categories.CategoryID = ec.CategoryID;
本 Transact-SQL 示例演示如何从表值参数中选择行,以在单个基于集的操作中执行 INSERT 。
INSERT INTO dbo.Categories (CategoryID, CategoryName)
SELECT nc.CategoryID, nc.CategoryName FROM @tvpNewCategories AS nc;
Table-Valued 参数的限制
表值参数有几个限制:
不能将表值参数传递给 CLR 用户定义的函数。
表值参数只能建立索引以支持
UNIQUE或PRIMARY KEY约束。 SQL Server 不维护表值参数的统计信息。在 Transact-SQL 代码中表值参数是只读的。 不能更新表值参数行中的列值,也不能插入或删除行。 若要修改表值参数中传递到存储过程或参数化语句的数据,必须将数据插入临时表或表变量。
不能使用
ALTER TABLE语句修改表值参数的设计。
配置 SqlParameter 示例
安装 Microsoft.Data.SqlClient NuGet 包并导入System.Data和Microsoft.Data.SqlClient。 对于客户端记录序列,请使用 Microsoft.Data.SqlClient.Server.SqlDataRecord 和 Microsoft.Data.SqlClient.Server.SqlMetaData。 不要用 Microsoft.SqlServer.Server 中的 SQL CLR 服务器端类型进行替代。
Microsoft.Data.SqlClient 支持来自 SqlDataRecord 对象的 DataTable、DbDataReader 或 IEnumerable<T> 的表值参数值。 将参数的 TypeName 设置为以前在服务器上创建的兼容表类型的名称。 以下片段配置参数以插入数据。
在以下示例中,addedCategories 变量包含一个 DataTable。 若要查看如何填充变量,请参阅下一节中的示例,将表值参数传递给存储过程。
// Configure the command and parameter.
SqlCommand insertCommand = new SqlCommand(sqlInsert, connection);
SqlParameter tvpParam = insertCommand.Parameters.AddWithValue("@tvpNewCategories", addedCategories);
tvpParam.SqlDbType = SqlDbType.Structured;
tvpParam.TypeName = "dbo.CategoryTableType";
' Configure the command and parameter.
Dim insertCommand As New SqlCommand(sqlInsert, connection)
Dim tvpParam As SqlParameter = _
insertCommand.Parameters.AddWithValue( _
"@tvpNewCategories", addedCategories)
tvpParam.SqlDbType = SqlDbType.Structured
tvpParam.TypeName = "dbo.CategoryTableType"
你也可以使用从DbDataReader中派生的任何对象,将数据行流式传输到表值参数,如本代码片段所示:
// Configure the SqlCommand and table-valued parameter.
SqlCommand insertCommand = new SqlCommand("usp_InsertCategories", connection);
insertCommand.CommandType = CommandType.StoredProcedure;
SqlParameter tvpParam = insertCommand.Parameters.AddWithValue("@tvpNewCategories", dataReader);
tvpParam.SqlDbType = SqlDbType.Structured;
' Configure the SqlCommand and table-valued parameter.
Dim insertCommand As New SqlCommand("usp_InsertCategories", connection)
insertCommand.CommandType = CommandType.StoredProcedure
Dim tvpParam As SqlParameter = _
insertCommand.Parameters.AddWithValue("@tvpNewCategories", _
dataReader)
tvpParam.SqlDbType = SqlDbType.Structured
将 Table-Valued 参数传递给存储过程
此示例将表值参数数据传递给存储过程。 代码调用 GetChanges 以将添加的行提取到新的 DataTable 中。 它会创建 SqlCommand,并将 CommandType 设置为 StoredProcedure。 它使用 AddWithValue 添加该参数,并将 SqlDbType 设为 Structured。 最后,代码调用 ExecuteNonQuery。
// Assumes connection is an open SqlConnection object.
using (connection)
{
// Create a DataTable with the modified rows.
DataTable addedCategories = CategoriesDataTable.GetChanges(DataRowState.Added);
// Configure the SqlCommand and SqlParameter.
SqlCommand insertCommand = new SqlCommand("usp_InsertCategories", connection);
insertCommand.CommandType = CommandType.StoredProcedure;
SqlParameter tvpParam = insertCommand.Parameters.AddWithValue("@tvpNewCategories", addedCategories);
tvpParam.SqlDbType = SqlDbType.Structured;
// Execute the command.
insertCommand.ExecuteNonQuery();
}
' Assumes connection is an open SqlConnection object.
Using connection
' Create a DataTable with the modified rows.
Dim addedCategories As DataTable = _
CategoriesDataTable.GetChanges(DataRowState.Added)
' Configure the SqlCommand and SqlParameter.
Dim insertCommand As New SqlCommand( _
"usp_InsertCategories", connection)
insertCommand.CommandType = CommandType.StoredProcedure
Dim tvpParam As SqlParameter = _
insertCommand.Parameters.AddWithValue( _
"@tvpNewCategories", addedCategories)
tvpParam.SqlDbType = SqlDbType.Structured
' Execute the command.
insertCommand.ExecuteNonQuery()
End Using
将 Table-Valued 参数传递给参数化 SQL 语句
以下示例使用 dbo.Categories 语句和读取表值参数的 INSERT 子查询将数据插入 SELECT。 对于参数化 SQL 语句,请将 TypeName 设置为以前在服务器上创建的兼容表类型的名称。 此示例使用 dbo.CategoryTableType。
注释
如果为表值参数中的标识列提供值,则必须为会话发出 SET IDENTITY_INSERT 语句。
// Assumes connection is an open SqlConnection.
using (connection)
{
// Create a DataTable with the modified rows.
DataTable addedCategories = CategoriesDataTable.GetChanges(DataRowState.Added);
// Define the INSERT-SELECT statement.
string sqlInsert =
"INSERT INTO dbo.Categories (CategoryID, CategoryName)"
+ " SELECT nc.CategoryID, nc.CategoryName"
+ " FROM @tvpNewCategories AS nc;";
// Configure the command and parameter.
SqlCommand insertCommand = new SqlCommand(sqlInsert, connection);
SqlParameter tvpParam = insertCommand.Parameters.AddWithValue("@tvpNewCategories", addedCategories);
tvpParam.SqlDbType = SqlDbType.Structured;
tvpParam.TypeName = "dbo.CategoryTableType";
// Execute the command.
insertCommand.ExecuteNonQuery();
}
' Assumes connection is an open SqlConnection.
Using connection
' Create a DataTable with the modified rows.
Dim addedCategories As DataTable = _
CategoriesDataTable.GetChanges(DataRowState.Added)
' Define the INSERT-SELECT statement.
Dim sqlInsert As String = _
"INSERT INTO dbo.Categories (CategoryID, CategoryName)" _
& " SELECT nc.CategoryID, nc.CategoryName" _
& " FROM @tvpNewCategories AS nc;"
' Configure the command and parameter.
Dim insertCommand As New SqlCommand(sqlInsert, connection)
Dim tvpParam As SqlParameter = _
insertCommand.Parameters.AddWithValue( _
"@tvpNewCategories", addedCategories)
tvpParam.SqlDbType = SqlDbType.Structured
tvpParam.TypeName = "dbo.CategoryTableType"
' Execute the query
insertCommand.ExecuteNonQuery()
End Using
使用 DataReader 对行进行流处理
可以使用任何派生自 DbDataReader 的对象,将行流式传输到表值参数。 以下片段使用 OracleCommand 和 OracleDataReader 从 Oracle 数据库中检索数据。 然后,代码配置 SqlCommand 以使用单个输入参数调用存储过程。 它将SqlDbType设置为Structured,并通过OracleDataReader将AddWithValue结果集作为表值参数传递。
// Assumes connection is an open SqlConnection.
// Retrieve data from Oracle.
OracleCommand selectCommand = new OracleCommand(
"Select CategoryID, CategoryName FROM Categories;",
oracleConnection);
OracleDataReader oracleReader = selectCommand.ExecuteReader(
CommandBehavior.CloseConnection);
// Configure the SqlCommand and table-valued parameter.
SqlCommand insertCommand = new SqlCommand(
"usp_InsertCategories", connection);
insertCommand.CommandType = CommandType.StoredProcedure;
SqlParameter tvpParam =
insertCommand.Parameters.AddWithValue(
"@tvpNewCategories", oracleReader);
tvpParam.SqlDbType = SqlDbType.Structured;
// Execute the command.
insertCommand.ExecuteNonQuery();
' Assumes connection is an open SqlConnection.
' Retrieve data from Oracle.
Dim selectCommand As New OracleCommand( _
"Select CategoryID, CategoryName FROM Categories;", _
oracleConnection)
Dim oracleReader As OracleDataReader = _
selectCommand.ExecuteReader(CommandBehavior.CloseConnection)
' Configure SqlCommand and table-valued parameter.
Dim insertCommand As New SqlCommand("usp_InsertCategories", connection)
insertCommand.CommandType = CommandType.StoredProcedure
Dim tvpParam As SqlParameter = _
insertCommand.Parameters.AddWithValue("@tvpNewCategories", _
oracleReader)
tvpParam.SqlDbType = SqlDbType.Structured
' Execute the command.
insertCommand.ExecuteNonQuery()