使用 SQLGetData 检索输出参数
在 ODBC 3.8 之前,应用程序只能检索具有绑定输出缓冲区的查询的输出参数。 但是,当参数值的大小非常大(例如,大型图像)时,很难分配非常大的缓冲区。 ODBC 3.8 引入了分部分检索的输出参数的新方法。 应用程序现在可以用小缓冲区多次调用 SQLGetData,以检索一个大型参数值。 这与检索大型列数据相似。
要绑定要分部分检索的输出参数或输入/输出参数,请使用设置为 SQL_PARAM_OUTPUT_STREAM 或 SQL_PARAM_INPUT_OUTPUT_STREAM 的 InputOutputType 参数调用 SQLBindParameter。 使用 SQL_PARAM_INPUT_OUTPUT_STREAM,应用程序可以使用 SQLPutData 将数据输入到参数中,然后使用 SQLGetData 检索输出参数。 输入数据必须位于执行时数据 (DAE) 窗体中,使用 SQLPutData 而不是将其绑定到预先分配的缓冲区。
此功能可由 ODBC 3.8 应用程序或重新编译的 ODBC 3.x 和 ODBC 2.x 应用程序使用,并且这些应用程序必须具有支持使用 SQLGetData 和 ODBC 3.8 驱动程序管理器检索输出参数的 ODBC 3.8 驱动程序。 有关如何使旧应用程序能够使用新的 ODBC 功能的信息,请参阅兼容性矩阵。
用法示例
例如,请考虑执行存储过程 {CALL sp_f(?,?)},其中两个参数都绑定为 SQL_PARAM_OUTPUT_STREAM,并且存储过程不返回结果集(在本主题的后面,你将找到更复杂的方案):
对于每个参数,通过将 InputOutputType 设置为 SQL_PARAM_OUTPUT_STREAM 并将 ParameterValuePtr 设置为令牌来调用 SQLBindParameter,例如参数编号、指向数据的指针或指向应用程序用来绑定输入参数的结构的指针。 此示例将使用参数序号作为令牌。
使用 SQLExecDirect 或 SQLExecute 执行查询。 将返回 SQL_PARAM_DATA_AVAILABLE,指示有可用于检索的流式输出参数。
调用 SQLParamData 以获取可用于检索的参数。 SQLParamData 将使用第一个可用参数的令牌返回 SQL_PARAM_DATA_AVAILABLE,该令牌在 SQLBindParameter 中设置(步骤 1)。 令牌在 ValuePtrPtr 指向的缓冲区中返回。
通过将参数 Col_or_Param_Num 设置为参数序号来调用 SQLGetData,以检索第一个可用参数的数据。 如果 SQLGetData 返回 SQL_SUCCESS_WITH_INFO 和 SQLState 01004(数据截断),并且该类型在客户端和服务器上都是可变长度,则可从第一个可用参数中检索更多数据。 你可以继续调用 SQLGetData,直到它返回具有不同 SQLState 的 SQL_SUCCESS 或 SQL_SUCCESS_WITH_INFO。
重复步骤 3 和步骤 4 以检索当前参数。
再次调用 SQLParamData。 如果它返回除 SQL_PARAM_DATA_AVAILABLE 之外的任何内容,则不再有要检索的流式处理参数数据,并且返回代码将是执行的下一个语句的返回代码。
调用 SQLMoreResults 以处理下一组参数,直到返回 SQL_NO_DATA。 如果语句属性 SQL_ATTR_PARAMSET_SIZE 设置为 1,则 SQLMoreResults 将返回此示例中的 SQL_NO_DATA。 否则,SQLMoreResults 将返回 SQL_PARAM_DATA_AVAILABLE,以指示有可用于下一组参数检索的流式输出参数。
与 DAE 输入参数相似的是,SQLBindParameter(步骤 1)中的参数 ParameterValuePtr 使用的令牌可以是指向应用程序数据结构的指针,该结构包含参数序号和更多特定于应用程序的信息(如有必要)。
返回的流式输出或输入/输出参数的顺序特定于驱动程序,并且可能并不总是与查询中指定的顺序相同。
如果应用程序未在步骤 4 中调用 SQLGetData,则放弃参数值。 同样,如果应用程序在 SQLGetData 读取所有参数值之前调用 SQLParamData,则放弃值的其余部分,并且应用程序可以处理下一个参数。
如果应用程序在处理所有流式输出参数之前调用 SQLMoreResults(SQLParamData 仍然返回 SQL_PARAM_DATA_AVAILABLE),则所有剩余的参数都会被丢弃。 同样,如果应用程序在 SQLGetData 读取所有参数值之前调用 SQLMoreResults,则放弃值的其余部分和所有的剩余参数,并且应用程序可以继续处理下一个参数组。
请注意,应用程序可以在 SQLBindParameter 和 SQLGetData 中指定 C 数据类型。 使用 SQLGetData 指定的 C 数据类型将替代 SQLBindParameter 中指定的 C 数据类型,除非 SQLGetData 中指定的 C 数据类型SQL_APD_TYPE。
尽管当输出参数的数据类型为 BLOB 时,流式输出参数更有用,但此功能也可用于任何数据类型。 流式输出参数支持的数据类型在驱动程序中指定。
如果有要处理的 SQL_PARAM_INPUT_OUTPUT_STREAM 参数,则 SQLExecute 或 SQLExecDirect 将首先返回 SQL_NEED_DATA。 应用程序可以调用 SQLParamData 和 SQLPutData 来发送 DAE 参数数据。 处理所有 DAE 输入参数时,SQLParamData 返回 SQL_PARAM_DATA_AVAILABLE 以指示流式输出参数可用。
当有要处理的流式输出参数和绑定的输出参数时,驱动程序将确定处理输出参数的顺序。 因此,如果输出参数绑定到缓冲区(SQLBindParameter 参数 InputOutputType 设置为 SQL_PARAM_INPUT_OUTPUT 或 SQL_PARAM_OUTPUT),则在 SQLParamData 返回 SQL_SUCCESS 或 SQL_SUCCESS_WITH_INFO 之前,可能无法填充缓冲区。 只有在 SQLParamData 返回 SQL_SUCCESS or SQL_SUCCESS_WITH_INFO 之后(即处理所有流式输出参数之后),应用程序才应读取绑定缓冲区。
除了流式输出参数外,数据源还可以返回警告和结果集。 通常,警告和结果集通过调用 SQLMoreResults 与流式输出参数分开处理。 在处理流式输出参数之前处理警告和结果集。
下表描述了发送到服务器的单个命令的不同方案,以及应用程序应如何工作。
场景 | 从 SQLExecute 或 SQLExecDirect 返回值 | 下一步操作 |
---|---|---|
数据仅包括流式输出参数 | SQL_PARAM_DATA_AVAILABLE | 使用 SQLParamData 和 SQLGetData 检索流式输出参数。 |
数据包括结果集和流式输出参数 | SQL_SUCCESS | 使用 SQLBindCol 和 SQLGetData 检索结果集。 调用 SQLMoreResults 以开始处理流式输出参数。 它应返回 SQL_PARAM_DATA_AVAILABLE。 使用 SQLParamData 和 SQLGetData 检索流式输出参数。 |
数据包括警告消息和流式输出参数 | SQL_SUCCESS_WITH_INFO | 使用 SQLGetDiagRec 和 SQLGetDiagField 处理警告消息。 调用 SQLMoreResults 以开始处理流式输出参数。 它应返回 SQL_PARAM_DATA_AVAILABLE。 使用 SQLParamData 和 SQLGetData 检索流式输出参数。 |
数据包括警告消息、结果集和流式输出参数 | SQL_SUCCESS_WITH_INFO | 使用 SQLGetDiagRec 和 SQLGetDiagField 处理警告消息。 然后调用 SQLMoreResults 开始处理结果集。 使用 SQLBindCol 和 SQLGetData 检索结果集。 调用 SQLMoreResults 以开始处理流式输出参数。 SQLMoreResults 应返回 SQL_PARAM_DATA_AVAILABLE。 使用 SQLParamData 和 SQLGetData 检索流式输出参数。 |
使用 DAE 输入参数进行查询,例如流式输入/输出 (DAE) 参数 | SQL NEED_DATA | 调用 SQLParamData 和 SQLPutData 来发送 DAE 输入参数数据。 处理所有 DAE 输入参数后,SQLParamData 可以返回 SQLExecute 和 SQLExecDirect 可以返回的任何返回代码。 然后,可以应用此表中的案例。 如果返回代码是 SQL_PARAM_DATA_AVAILABLE,则流式输出参数可用。 应用程序必须再次调用 SQLParamData 才能检索流式输出参数的令牌,如此表的第一行中所述。 如果返回代码是 SQL_SUCCESS,则存在要处理的结果集或处理完成。 如果返回代码是 SQL_SUCCESS_WITH_INFO,则存在要处理的警告消息。 |
当 SQLExecute、SQLExecDirect 或 SQLMoreResults 返回 SQL_PARAM_DATA_AVAILABLE 后,如果应用程序调用不在以下列表中的函数,则函数序列错误将产生:
SQLAllocHandle / SQLAllocHandleStd
SQLDataSources / SQLDrivers
SQLGetInfo / SQLGetFunctions
SQLGetConnectAttr / SQLGetEnvAttr / SQLGetDescField / SQLGetDescRec
SQLNumParams
SQLDescribeParam
SQLNativeSql
SQLParamData
SQLMoreResults
SQLGetDiagField / SQLGetDiagRec
SQLCancel
SQLCancelHandle(带语句句柄)
SQLFreeStmt(带选项 = SQL_CLOSE、SQL_DROP 或 SQL_UNBIND)
SQLCloseCursor
SQLDisconnect
SQLFreeHandle(带 HandleType = SQL_HANDLE_STMT)
SQLGetStmtAttr
应用程序仍然可以使用 SQLSetDescField 或 SQLSetDescRec 设置绑定信息。 字段映射不会更改。 但是,描述符内的字段可能会返回新值。 例如,SQL_DESC_PARAMETER_TYPE 可能会返回 SQL_PARAM_INPUT_OUTPUT_STREAM 或 SQL_PARAM_OUTPUT_STREAM。
使用方案:从结果集中分部分检索图像
当存储过程返回包含有关图像的一行元数据并在大型输出参数中返回图像时,SQLGetData 可用于分部分获取数据。
// CREATE PROCEDURE SP_TestOutputPara
// @ID_of_picture as int,
// @Picture as varbinary(max) out
// AS
// output the image data through streamed output parameter
// GO
BOOL displayPicture(SQLUINTEGER idOfPicture, SQLHSTMT hstmt) {
SQLLEN lengthOfPicture; // The actual length of the picture.
BYTE smallBuffer[100]; // A very small buffer.
SQLRETURN retcode, retcode2;
// Bind the first parameter (input parameter)
SQLBindParameter(
hstmt,
1, // The first parameter.
SQL_PARAM_INPUT, // Input parameter: The ID_of_picture.
SQL_C_ULONG, // The C Data Type.
SQL_INTEGER, // The SQL Data Type.
0, // ColumnSize is ignored for integer.
0, // DecimalDigits is ignored for integer.
&idOfPicture, // The Address of the buffer for the input parameter.
0, // BufferLength is ignored for integer.
NULL); // This is ignored for integer.
// Bind the streamed output parameter.
SQLBindParameter(
hstmt,
2, // The second parameter.
SQL_PARAM_OUTPUT_STREAM, // A streamed output parameter.
SQL_C_BINARY, // The C Data Type.
SQL_VARBINARY, // The SQL Data Type.
0, // ColumnSize: The maximum size of varbinary(max).
0, // DecimalDigits is ignored for binary type.
(SQLPOINTER)2, // ParameterValuePtr: An application-defined
// token (this will be returned from SQLParamData).
// In this example, we used the ordinal
// of the parameter.
0, // BufferLength is ignored for streamed output parameters.
&lengthOfPicture); // StrLen_or_IndPtr: The status variable returned.
retcode = SQLPrepare(hstmt, L"{call SP_TestOutputPara(?, ?)}", SQL_NTS);
if ( retcode == SQL_ERROR )
return FALSE;
retcode = SQLExecute(hstmt);
if ( retcode == SQL_ERROR )
return FALSE;
// Assume that the retrieved picture exists. Use SQLBindCol or SQLGetData to retrieve the result-set.
// Process the result set and move to the streamed output parameters.
retcode = SQLMoreResults( hstmt );
// SQLGetData retrieves and displays the picture in parts.
// The streamed output parameter is available.
while (retcode == SQL_PARAM_DATA_AVAILABLE) {
SQLPOINTER token; // Output by SQLParamData.
SQLLEN cbLeft; // #bytes remained
retcode = SQLParamData(hstmt, &token); // returned token is 2 (according to the binding)
if ( retcode == SQL_PARAM_DATA_AVAILABLE ) {
// A do-while loop retrieves the picture in parts.
do {
retcode2 = SQLGetData(
hstmt,
(UWORD) token, // the value of the token is the ordinal.
SQL_C_BINARY, // The C-type.
smallBuffer, // A small buffer.
sizeof(smallBuffer), // The size of the buffer.
&cbLeft); // How much data we can get.
}
while ( retcode2 == SQL_SUCCESS_WITH_INFO );
}
}
return TRUE;
}
使用方案:以流式输入/输出参数的形式发送和接收大型对象
当存储过程将大型对象作为输入/输出参数传递时,SQLGetData 可用于分部分获取和发送数据,从而将值流式传输到数据库和从数据库中传输。 无需在内存中存储所有数据。
// CREATE PROCEDURE SP_TestInOut
// @picture as varbinary(max) out
// AS
// output the image data through output parameter
// go
BOOL displaySimilarPicture(BYTE* image, ULONG lengthOfImage, SQLHSTMT hstmt) {
BYTE smallBuffer[100]; // A very small buffer.
SQLRETURN retcode, retcode2;
SQLLEN statusOfPicture;
// First bind the parameters, before preparing the statement that binds the output streamed parameter.
SQLBindParameter(
hstmt,
1, // The first parameter.
SQL_PARAM_INPUT_OUTPUT_STREAM, // I/O-streamed parameter: The Picture.
SQL_C_BINARY, // The C Data Type.
SQL_VARBINARY, // The SQL Data Type.
0, // ColumnSize: The maximum size of varbinary(max).
0, // DecimalDigits is ignored.
(SQLPOINTER)1, // An application defined token.
0, // BufferLength is ignored for streamed I/O parameters.
&statusOfPicture); // The status variable.
statusOfPicture = SQL_DATA_AT_EXEC; // Input data in parts (DAE parameter at input).
retcode = SQLPrepare(hstmt, L"{call SP_TestInOut(?) }", SQL_NTS);
if ( retcode == SQL_ERROR )
return FALSE;
// Execute the statement.
retcode = SQLExecute(hstmt);
if ( retcode == SQL_ERROR )
return FALSE;
if ( retcode == SQL_NEED_DATA ) {
// Use SQLParamData to loop through DAE input parameters. For
// each, use SQLPutData to send the data to database in parts.
// This example uses an I/O parameter with streamed output.
// Therefore, the last call to SQLParamData should return
// SQL_PARAM_DATA_AVAILABLE to indicate the end of the input phrase
// and report that a streamed output parameter is available.
// Assume retcode is set to the return value of the last call to
// SQLParamData, which is equal to SQL_PARAM_DATA_AVAILABLE.
}
// Start processing the streamed output parameters.
while ( retcode == SQL_PARAM_DATA_AVAILABLE ) {
SQLPOINTER token; // Output by SQLParamData.
SQLLEN cbLeft; // #bytes remained
retcode = SQLParamData(hstmt, &token);
if ( retcode == SQL_PARAM_DATA_AVAILABLE ) {
do {
retcode2 = SQLGetData(
hstmt,
(UWORD) token, // the value of the token is the ordinal.
SQL_C_BINARY, // The C-type.
smallBuffer, // A small buffer.
sizeof(smallBuffer), // The size of the buffer.
&cbLeft); // How much data we can get.
}
while ( retcode2 == SQL_SUCCESS_WITH_INFO );
}
}
return TRUE;
}