使用表值参数
表值参数提供了一将多行数据从客户端应用程序封送到 SQL Server 的种简单方法,而无需进行多次往返或特殊的服务器端逻辑来处理数据。 可使用表值参数来封装客户端应用程序中的数据行,并以单个参数化命令将数据发送到服务器。 传入数据行存储在随后可使用 Transact-SQL 进行操作的表变量中。
可以使用标准的 Transact-SQL SELECT 语句来访问表值参数中的列值。 表值参数为强类型,其结构会自动进行验证。 表值参数的大小仅受服务器内存的限制。
备注
自 Microsoft JDBC Driver 6.0 for SQL Server 起,开始支持表值参数。
无法返回表值参数中的数据。 表值参数仅限输入;不支持 OUTPUT 关键字。
若要详细了解表值参数,请参阅以下资源。
资源 | 说明 |
---|---|
SQL Server 联机丛书中的表值参数(数据库引擎) | 介绍了如何创建和使用表值参数 |
SQL Server 联机丛书中的用户定义的表类型 | 介绍了用于声明表值参数的用户定义的表类型 |
在旧版 SQL Server 中传递多行
在 SQL Server 2008 中引入表值参数之前,用于将多行数据传递到存储过程或参数化 SQL 命令的选项受到限制。 开发人员可以选择下面的一种方法,将多行传递到服务器:
使用一系列单独的参数来表示多列和多行数据中的值。 使用这种方法可以传递的数据量受到允许使用的参数数量限制。 SQL Server 过程最多可以有 2100 个参数。 必须使用服务器端逻辑,将这些单独的值汇编到表变量或临时表中以供处理。
将多个数据值绑定到分隔字符串或 XML 文档,然后将这些文本值传递到过程或语句。 这要求过程或语句包含验证数据结构和解除绑定值所需的逻辑。
创建一系列单独的 SQL 语句,以执行影响多行的数据修改。 更改可以单独提交给服务器,也可以批量提交给组。 不过,即使是包含多个语句的批量提交,每个语句也是在服务器上单独执行。
使用 bcp 实用工具或 SQLServerBulkCopy 将多行数据加载到表中。 尽管这种技术很高效,但它不支持服务器端处理,除非将数据加载到临时表或表变量中。
创建表值参数类型
表值参数基于使用 Transact-SQL CREATE TYPE
语句定义的强类型表结构。 必须先在 SQL Server 中创建一个表类型并定义结构,才能在客户端应用程序中使用表值参数。 有关创建表类型的详细信息,请参阅 SQL Server 联机丛书中的用户定义的表类型。
CREATE TYPE dbo.CategoryTableType AS TABLE
( CategoryID int, CategoryName nvarchar(50) )
创建表类型后,可以根据相应类型来声明表值参数。 下面的 Transact-SQL 片段演示如何在存储过程定义中声明表值参数。 READONLY
是声明表值参数所必需的关键字。
CREATE PROCEDURE usp_UpdateCategories
(@tvpNewCategories dbo.CategoryTableType READONLY)
使用表值参数修改数据 (Transact-SQL)
表值参数可用于基于集的数据修改,这些修改通过执行一条语句影响多行。 例如,可以选择表值参数中的所有行并将它们插入数据库表,也可以通过将表值参数联接到要更新的表来创建更新语句。
下面的 Transact-SQL UPDATE 语句演示如何通过将表值参数联接到 Categories 表来使用它。 在 FROM 子句中结合使用表值参数和 JOIN 时,还必须对表值参数使用别名。如下所示,表值参数的别名为“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;
表值参数的限制
表值参数有几个限制:
不能将表值参数传递给用户定义的函数。
表值参数只能通过被编制索引来支持 UNIQUE 或 PRIMARY KEY 约束。 SQL Server 不维护表值参数的统计信息。
在 Transact-SQL 代码中表值参数是只读的。 既不能更新表值参数行中的列值,也不能插入或删除行。 若要修改表值参数中传递到存储过程或参数化语句的数据,必须将数据插入临时表或表变量。
不能使用 ALTER TABLE 语句来修改表值参数的设计。
可以在表值参数中流式传输大型对象。
配置表值参数
自 Microsoft JDBC Driver 6.0 for SQL Server 起,开始支持将表值参数与参数化语句或参数化存储过程一起配合使用。 可以从 SQLServerDataTable、ResultSet 或用户提供的 ISQLServerDataRecord 接口实现填充表值参数。 如果为准备的查询设置表值参数,必须指定类型名称,此名称必须与先前在服务器上创建的兼容类型的名称一致。
下面两个代码片段展示了如何使用 SQLServerPreparedStatement 和 SQLServerCallableStatement 将表值参数配置为插入数据。 其中的 sourceTVPObject 可以是 SQLServerDataTable、ResultSet 或 ISQLServerDataRecord 对象。 这些示例假定连接是活动的 Connection 对象。
// Using table-valued parameter with a SQLServerPreparedStatement.
SQLServerPreparedStatement pStmt =
(SQLServerPreparedStatement) connection.prepareStatement("INSERT INTO dbo.Categories SELECT * FROM ?");
pStmt.setStructured(1, "dbo.CategoryTableType", sourceTVPObject);
pStmt.execute();
// Using table-valued parameter with a SQLServerCallableStatement.
SQLServerCallableStatement pStmt =
(SQLServerCallableStatement) connection.prepareCall("exec usp_InsertCategories ?");
pStmt.setStructured(1, "dbo.CategoryTableType", sourceTVPObject);;
pStmt.execute();
注意
有关可用于设置表值参数的 API 的完整列表,请参阅下面的“JDBC 驱动程序的表值参数 API” 部分。
将表值参数作为 SQLServerDataTable 对象传递
自 Microsoft JDBC Driver 6.0 for SQL Server 起,SQLServerDataTable 类开始表示关系数据的内存中表。 下面的示例展示了如何使用 SQLServerDataTable 对象从内存中数据构造表值参数。 首选,此代码创建 SQLServerDataTable 对象,定义它的架构,并使用数据填充表。 然后,此代码配置 SQLServerPreparedStatement,用于将此数据表作为表值参数传递到 SQL Server。
/* Assumes connection is an active Connection object. */
// Create an in-memory data table.
SQLServerDataTable sourceDataTable = new SQLServerDataTable();
// Define metadata for the data table.
sourceDataTable.addColumnMetadata("CategoryID" ,java.sql.Types.INTEGER);
sourceDataTable.addColumnMetadata("CategoryName" ,java.sql.Types.NVARCHAR);
// Populate the data table.
sourceDataTable.addRow(1, "CategoryNameValue1");
sourceDataTable.addRow(2, "CategoryNameValue2");
// Pass the data table as a table-valued parameter using a prepared statement.
SQLServerPreparedStatement pStmt =
(SQLServerPreparedStatement) connection.prepareStatement(
"INSERT INTO dbo.Categories SELECT * FROM ?;");
pStmt.setStructured(1, "dbo.CategoryTableType", sourceDataTable);
pStmt.execute();
此示例与前一个示例类似。 唯一的区别在于,它在 SQLServerDataTable
上设置 TVP 名称,而不是依赖将 PreparedStatement
转换为 SQLServerPreparedStatement
来使用 setStructured
方法。
/* Assumes connection is an active Connection object. */
// Create an in-memory data table.
SQLServerDataTable sourceDataTable = new SQLServerDataTable();
sourceDataTable.setTvpName("dbo.CategoryTableType");
// Define metadata for the data table.
sourceDataTable.addColumnMetadata("CategoryID" ,java.sql.Types.INTEGER);
sourceDataTable.addColumnMetadata("CategoryName" ,java.sql.Types.NVARCHAR);
// Populate the data table.
sourceDataTable.addRow(1, "CategoryNameValue1");
sourceDataTable.addRow(2, "CategoryNameValue2");
// Pass the data table as a table-valued parameter using a prepared statement.
PreparedStatement pStmt =
connection.prepareStatement(
"INSERT INTO dbo.Categories SELECT * FROM ?;");
pStmt.setObject(1, sourceDataTable);
pStmt.execute();
注意
有关可用于设置表值参数的 API 的完整列表,请参阅下面的“JDBC 驱动程序的表值参数 API” 部分。
将表值参数作为 ResultSet 对象传递
下面的示例展示了如何将 ResultSet 中的数据行流式传输到表值参数。 首先,代码从 SQLServerDataTable 对象中的源表中检索数据,定义它的架构,并使用数据填充表。 然后,此代码配置 SQLServerPreparedStatement,用于将此数据表作为表值参数传递到 SQL Server。
/* Assumes connection is an active Connection object. */
// Create the source ResultSet object. Here SourceCategories is a table defined with the same schema as Categories table.
ResultSet sourceResultSet = connection.createStatement().executeQuery("SELECT * FROM SourceCategories");
// Pass the source result set as a table-valued parameter using a prepared statement.
SQLServerPreparedStatement pStmt =
(SQLServerPreparedStatement) connection.prepareStatement(
"INSERT INTO dbo.Categories SELECT * FROM ?;");
pStmt.setStructured(1, "dbo.CategoryTableType", sourceResultSet);
pStmt.execute();
注意
有关可用于设置表值参数的 API 的完整列表,请参阅下面的“JDBC 驱动程序的表值参数 API” 部分。
将表值参数作为 ISQLServerDataRecord 对象传递
自 Microsoft JDBC Driver 6.0 for SQL Server 起,新接口 ISQLServerDataRecord 可用于使用表值参数流式传输数据(具体取决于用户如何实现它)。 下面的示例展示了如何实现 ISQLServerDataRecord 接口,以及如何将它作为表值参数传递。 为简单起见,下面的示例仅将包含硬编码值的一行传递给表值参数。 理想情况下,用户会实现此接口,用于流式传输任何源(例如,文本文件)中的行。
class MyRecords implements ISQLServerDataRecord
{
int currentRow = 0;
Object[] row = new Object[2];
MyRecords(){
// Constructor. This implementation has just one row.
row[0] = new Integer(1);
row[1] = "categoryName1";
}
public int getColumnCount(){
// Return the total number of columns, for this example it is 2.
return 2;
}
public SQLServerMetaData getColumnMetaData(int columnIndex) {
// Return the column metadata.
if (1 == columnIndex)
return new SQLServerMetaData("CategoryID", java.sql.Types.INTEGER);
else
return new SQLServerMetaData("CategoryName", java.sql.Types.NVARCHAR);
}
public Object[] getRowData(){
// Return the columns in the current row as an array of objects. This implementation has just one row.
return row;
}
public boolean next(){
// Move to the next row. This implementation has just one row, after processing the first row, return false.
currentRow++;
if (1 == currentRow)
return true;
else
return false;
}
}
// Following code demonstrates how to pass MyRecords object as a table-valued parameter.
MyRecords sourceRecords = new MyRecords();
SQLServerPreparedStatement pStmt =
(SQLServerPreparedStatement) connection.prepareStatement(
"INSERT INTO dbo.Categories SELECT * FROM ?;");
pStmt.setStructured(1, "dbo.CategoryTableType", sourceRecords);
pStmt.execute();
注意
有关可用于设置表值参数的 API 的完整列表,请参阅下面的“JDBC 驱动程序的表值参数 API” 部分。
JDBC 驱动程序的表值参数 API
SQLServerMetaData
此类表示列的元数据。 它在 ISQLServerDataRecord 接口中使用,以将列元数据传递到表值参数。 此类中的方法为:
名称 | 说明 |
---|---|
public SQLServerMetaData(String columnName, int sqlType, int precision, int scale, boolean useServerDefault, boolean isUniqueKey, SQLServerSortOrder sortOrder, int sortOrdinal) | 使用指定的列名、SQL 类型、精度、规模和默认服务器值初始化 SQLServerMetaData 的新实例。 这种形式的构造函数支持表值参数,具体是允许你指定列在表值参数中是否唯一、列排序顺序以及排序列序号。 useServerDefault - 指定此列是否应使用默认服务器值;默认值为 false。 isUniqueKey - 指明表值参数中的列是否唯一;默认值为 false。 sortOrder - 指明列排序顺序;默认值为 SQLServerSortOrder.Unspecified。 sortOrdinal - 指定排序列序号;sortOrdinal 从 0 开始;默认值为 -1。 |
public SQLServerMetaData(String columnName, int sqlType) | 使用列名和 SQL 类型初始化 SQLServerMetaData 的新实例。 |
public SQLServerMetaData(String columnName, int sqlType, int length) | 使用列名、SQL 类型和长度(对于 String 数据)初始化 SQLServerMetaData 的新实例。 长度用于区分大字符串和长度短于 4000 个字符的字符串。 已在 JDBC 驱动程序版本 7.2 中引入。 |
public SQLServerMetaData(String columnName, int sqlType, int precision, int scale) | 使用列名、SQL 类型、精度和规模初始化 SQLServerMetaData 的新实例。 |
Public SQLServerMetaData(SQLServerMetaData sqlServerMetaData) | 从另一个 SQLServerMetaData 对象初始化 SQLServerMetaData 的新实例。 |
public String getColumName() | 检索列名。 |
public int getSqlType() | 检索 Java SQL 类型。 |
public int getPrecision() | 检索传递到列的类型的精度。 |
public int getScale() | 检索传递到列的类型的规模。 |
public SQLServerSortOrder getSortOrder() | 检索排序顺序。 |
public int getSortOrdinal() | 检索排序序号。 |
public boolean isUniqueKey() | 返回列是否唯一。 |
public boolean useServerDefault() | 返回列是否使用默认服务器值。 |
SQLServerSortOrder
定义排序顺序的枚举。 可取值为 Ascending、Descending 和 Unspecified。
SQLServerDataTable
此类表示要与表值参数一起使用的内存中数据表。 此类中的方法为:
名称 | 说明 |
---|---|
Public SQLServerDataTable() | 初始化 SQLServerDataTable 的新实例。 |
public Iterator<Entry<Integer, Object[]>> getIterator() | 检索数据表行上的迭代器。 |
public void addColumnMetadata(String columnName, int sqlType) | 添加指定列的元数据。 |
public void addColumnMetadata(SQLServerDataColumn column) | 添加指定列的元数据。 |
public void addRow(Object... values) | 将一行数据添加到数据表。 |
public Map<Integer, SQLServerDataColumn> getColumnMetadata() | 检索此数据表的列元数据。 |
public void clear() | 清除此数据表。 |
SQLServerDataColumn
此类表示由 SQLServerDataTable 表示的内存中数据表的列。 此类中的方法为:
名称 | 说明 |
---|---|
public SQLServerDataColumn(String columnName, int sqlType) | 使用列名和类型初始化 SQLServerDataColumn 的新实例。 |
public String getColumnName() | 检索列名。 |
public int getColumnType() | 检索列类型。 |
ISQLServerDataRecord
此类表示用户可以实现的接口,用于将数据流式传输到表值参数。 此接口中的方法为:
名称 | 说明 |
---|---|
public SQLServerMetaData getColumnMetaData(int column); | 检索给定列索引的列元数据。 |
public int getColumnCount(); | 检索总列数。 |
public Object[] getRowData(); | 获取作为对象数组的当前行的数据。 |
public boolean next(); | 移到下一行。 如果移动成功且有下一行,则返回 True,否则返回 False。 |
SQLServerPreparedStatement
为了支持传递表值参数,已将下面的方法添加到此类。
名称 | 说明 |
---|---|
public final void setStructured(int parameterIndex, String tvpName, SQLServerDataTable tvpDataTable) | 使用数据表填充表值参数。 parameterIndex 是参数索引,tvpName 是表值参数的名称,tvpDataTable 是源数据表对象。 |
public final void setStructured(int parameterIndex, String tvpName, ResultSet tvpResultSet) | 使用从另一个表检索到的 ResultSet 填充表值参数。 parameterIndex 是参数索引,tvpName 是表值参数的名称,tvpResultSet 是源结果集对象。 |
public final void setStructured(int parameterIndex, String tvpName, ISQLServerDataRecord tvpDataRecord) | 使用 ISQLServerDataRecord 对象填充表值参数。 ISQLServerDataRecord 用于流式处理数据,用户决定如何使用它。parameterIndex 是参数索引,tvpName 是表值参数的名称,tvpDataRecord 是 ISQLServerDataRecord 对象。 |
SQLServerCallableStatement
为了支持传递表值参数,已将下面的方法添加到此类。
名称 | 说明 |
---|---|
public final void setStructured(String paratemeterName, String tvpName, SQLServerDataTable tvpDataTable) | 使用数据表填充传递到存储过程的表值参数。 paratemeterName 是参数名称,tvpName 是类型 TVP 的名称,tvpDataTable 是数据表对象。 |
public final void setStructured(String paratemeterName, String tvpName, ResultSet tvpResultSet) | 使用从另一个表检索到的 ResultSet 填充传递到存储过程的表值参数。 paratemeterName 是参数名称,tvpName 是类型 TVP 的名称,tvpResultSet 是源数据集对象。 |
public final void setStructured(String paratemeterName, String tvpName, ISQLServerDataRecord tvpDataRecord) | 使用 ISQLServerDataRecord 对象填充传递到存储过程的表值参数。 ISQLServerDataRecord 用于流式处理数据,用户决定如何使用它。paratemeterName 是参数名称,tvpName 是类型 TVP 的名称,tvpDataRecord 是 ISQLServerDataRecord 对象。 |