Recuperar parámetros de salida mediante SQLGetData
Antes de ODBC 3.8, una aplicación solo podía recuperar los parámetros de salida de una consulta con un búfer de salida enlazado. Sin embargo, es difícil asignar un búfer muy grande cuando el tamaño del valor del parámetro es muy grande (por ejemplo, una imagen grande). ODBC 3.8 presenta una nueva forma de recuperar los parámetros de salida en partes. Ahora una aplicación puede llamar varias veces a SQLGetData con un búfer pequeño para recuperar un valor de parámetro grande. Esto es similar a la recuperación de datos de columna grandes.
Para enlazar un parámetro de salida o un parámetro de entrada/salida que se va a recuperar en partes, llame a SQLBindParameter con el argumento InputOutputType establecido en SQL_PARAM_OUTPUT_STREAM o SQL_PARAM_INPUT_OUTPUT_STREAM. Con SQL_PARAM_INPUT_OUTPUT_STREAM, una aplicación puede usar SQLPutData para introducir datos en el parámetro y, a continuación, usar SQLGetData para recuperar el parámetro de salida. Los datos de entrada deben estar en forma de datos en el momento de la ejecución (DAE), con SQLPutData en lugar de enlazarlos a un búfer asignado previamente.
Las aplicaciones de ODBC 3.8 o las aplicaciones recompiladas de ODBC 3.x y ODBC 2.x pueden usar esta característica y estas aplicaciones deben tener un controlador ODBC 3.8 que admita la recuperación de parámetros de salida mediante SQLGetData y el Administrador de controladores ODBC 3.8. Para obtener información sobre cómo habilitar una aplicación anterior para usar nuevas características de ODBC, consulte Matriz de compatibilidad.
Ejemplo de uso
Por ejemplo, considere la posibilidad de ejecutar un procedimiento almacenado, {CALL sp_f(?,?)}, en el que ambos parámetros estén enlazados como SQL_PARAM_OUTPUT_STREAM y el procedimiento almacenado no devuelva ningún conjunto de resultados (más adelante en este tema encontrará un escenario más complejo):
Para cada parámetro, llame a SQLBindParameter con el valor de InputOutputType establecido en SQL_PARAM_OUTPUT_STREAM y el de ParameterValuePtr establecido en un token, como un número de parámetro, un puntero a datos o un puntero a una estructura que la aplicación usa para enlazar parámetros de entrada. En este ejemplo se usará el ordinal de parámetro como token.
Ejecute la consulta con SQLExecDirect o SQLExecute. Se devolverá SQL_PARAM_DATA_AVAILABLE, lo que indica que hay parámetros de salida transmitidos disponibles para su recuperación.
Llame a SQLParamData para obtener el parámetro que está disponible para su recuperación. SQLParamData devolverá SQL_PARAM_DATA_AVAILABLE con el token del primer parámetro disponible, que se establece en SQLBindParameter (paso 1). El token se devuelve en el búfer al que apunta ValuePtrPtr.
Llame a SQLGetData con el argumento Col_or_Param_Num establecido en el ordinal del parámetro para recuperar los datos del primer parámetro disponible. Si SQLGetData devuelve SQL_SUCCESS_WITH_INFO y SQLState 01004 (Datos truncados) y el tipo es una longitud variable en el cliente y en el servidor, hay más datos que recuperar del primer parámetro disponible. Puede seguir llamando a SQLGetData hasta que devuelva SQL_SUCCESS o SQL_SUCCESS_WITH_INFO con un valor de SQLState diferente.
Repita el paso 3 y el paso 4 para recuperar el parámetro actual.
Vuelva a llamar a SQLParamData. Si devuelve algo excepto SQL_PARAM_DATA_AVAILABLE, no hay más datos de parámetros transmitidos que recuperar y el código de retorno será el código de retorno de la siguiente instrucción que se ejecuta.
Llame a SQLMoreResults para procesar el siguiente conjunto de parámetros hasta que devuelva SQL_NO_DATA. SQLMoreResults devolverá SQL_NO_DATA en este ejemplo si el atributo de instrucción SQL_ATTR_PARAMSET_SIZE se estableció en 1. De lo contrario, SQLMoreResults devolverá SQL_PARAM_DATA_AVAILABLE para indicar que hay parámetros de salida transmitidos disponibles para el siguiente conjunto de parámetros que se van a recuperar.
De forma similar a un parámetro de entrada de DAE, el token usado en el argumento ParameterValuePtr en SQLBindParameter (paso 1) puede ser un puntero que apunte a una estructura de datos de aplicación, que contiene el ordinal del parámetro y más información específica de la aplicación, si es necesario.
El orden de los parámetros de salida o entrada/salida transmitidos que se devuelven es específico del controlador y es posible que no siempre sea el mismo que el orden especificado en la consulta.
Si la aplicación no llama a SQLGetData en el paso 4, se descarta el valor del parámetro. Del mismo modo, si la aplicación llama a SQLParamData antes de que SQLGetData haya leído todo el valor de un parámetro, se descarta el resto del valor y la aplicación puede procesar el siguiente parámetro.
Si la aplicación llama a SQLMoreResults antes de que se procesen todos los parámetros de salida transmitidos (SQLParamData sigue devolviendo SQL_PARAM_DATA_AVAILABLE), se descartan todos los parámetros restantes. Del mismo modo, si la aplicación llama a SQLMoreResults antes de que SQLGetData haya leído todo el valor de un parámetro, se descartan el resto del valor y los parámetros restantes y la aplicación puede seguir procesando el siguiente conjunto de parámetros.
Tenga en cuenta que una aplicación puede especificar el tipo de datos C en SQLBindParameter y SQLGetData. El tipo de datos C especificado con SQLGetData invalida el tipo de datos C especificado en SQLBindParameter, a menos que el tipo de datos C especificado en SQLGetData sea SQL_APD_TYPE.
Aunque un parámetro de salida transmitido es más útil cuando el tipo de datos del parámetro de salida es de tipo BLOB, esta funcionalidad también se puede usar con cualquier tipo de datos. Los tipos de datos que admiten los parámetros de salida transmitidos se especifican en el controlador.
Si hay parámetros SQL_PARAM_INPUT_OUTPUT_STREAM que procesar, SQLExecute o SQLExecDirect devolverán primero SQL_NEED_DATA. Una aplicación puede llamar a SQLParamData y SQLPutData para enviar datos de parámetros de DAE. Cuando se procesan todos los parámetros de entrada de DAE, SQLParamData devuelve SQL_PARAM_DATA_AVAILABLE para indicar que hay parámetros de salida transmitidos disponibles.
Cuando hay parámetros de salida transmitidos y parámetros de salida enlazados que procesar, el controlador determina el orden de procesamiento de los parámetros de salida. Por lo tanto, si un parámetro de salida está enlazado a un búfer (el valor de InputOutputType del parámetro SQLBindParameter está establecido en SQL_PARAM_INPUT_OUTPUT o SQL_PARAM_OUTPUT), es posible que el búfer no se rellene hasta que SQLParamData devuelva SQL_SUCCESS o SQL_SUCCESS_WITH_INFO. Una aplicación debe leer un búfer enlazado solo después de que SQLParamData devuelva SQL_SUCCESS o SQL_SUCCESS_WITH_INFO, es decir, una vez procesados todos los parámetros de salida transmitidos.
El origen de datos puede devolver una advertencia y un conjunto de resultados, además del parámetro de salida transmitido. En general, las advertencias y los conjuntos de resultados se procesan por separado de un parámetro de salida transmitido mediante una llamada a SQLMoreResults. Procese las advertencias y el conjunto de resultados antes de procesar el parámetro de salida transmitido.
En la tabla siguiente se describen distintos escenarios de un único comando enviado al servidor y cómo debe funcionar la aplicación.
Escenario | Valor devuelto de SQLExecute o SQLExecDirect | Pasos siguientes |
---|---|---|
Los datos solo incluyen parámetros de salida transmitidos | SQL_PARAM_DATA_AVAILABLE | Use SQLParamData y SQLGetData para recuperar parámetros de salida transmitidos. |
Los datos incluyen un conjunto de resultados y parámetros de salida transmitidos | SQL_SUCCESS | Recupere el conjunto de resultados con SQLBindCol y SQLGetData. Llame a SQLMoreResults para iniciar el procesamiento de parámetros de salida transmitidos. Debe devolver SQL_PARAM_DATA_AVAILABLE. Use SQLParamData y SQLGetData para recuperar parámetros de salida transmitidos. |
Los datos incluyen un mensaje de advertencia y parámetros de salida transmitidos | SQL_SUCCESS_WITH_INFO | Use SQLGetDiagRec y SQLGetDiagField para procesar mensajes de advertencia. Llame a SQLMoreResults para iniciar el procesamiento de parámetros de salida transmitidos. Debe devolver SQL_PARAM_DATA_AVAILABLE. Use SQLParamData y SQLGetData para recuperar parámetros de salida transmitidos. |
Los datos incluyen un mensaje de advertencia, un conjunto de resultados y parámetros de salida transmitidos | SQL_SUCCESS_WITH_INFO | Use SQLGetDiagRec y SQLGetDiagField para procesar mensajes de advertencia. A continuación, llame a SQLMoreResults para empezar a procesar el conjunto de resultados. Recupere un conjunto de resultados con SQLBindCol y SQLGetData. Llame a SQLMoreResults para iniciar el procesamiento de parámetros de salida transmitidos. SQLMoreResults debe devolver SQL_PARAM_DATA_AVAILABLE. Use SQLParamData y SQLGetData para recuperar parámetros de salida transmitidos. |
Consulta con parámetros de entrada de DAE, por ejemplo, un parámetro de entrada/salida transmitido (DAE) | SQL NEED_DATA | Llame a SQLParamData y SQLPutData para enviar datos de parámetros de entrada de DAE. Una vez procesados todos los parámetros de entrada de DAE, SQLParamData puede devolver cualquier código de retorno que SQLExecute y SQLExecDirect puedan devolver. A continuación, se pueden aplicar los casos de esta tabla. Si el código de retorno es SQL_PARAM_DATA_AVAILABLE, los parámetros de salida transmitidos están disponibles. Una aplicación debe llamar a SQLParamData de nuevo para recuperar el token del parámetro de salida transmitido, como se describe en la primera fila de esta tabla. Si el código de retorno es SQL_SUCCESS, hay un conjunto de resultados que procesar o el procesamiento se ha completado. Si el código de retorno es SQL_SUCCESS_WITH_INFO, hay mensajes de advertencia que procesar. |
Cuando SQLExecute, SQLExecDirect o SQLMoreResults devuelva SQL_PARAM_DATA_AVAILABLE, se producirá un error de secuencia de funciones si una aplicación llama a una función que no está en la lista siguiente:
SQLAllocHandle / SQLAllocHandleStd
SQLDataSources / SQLDrivers
SQLGetInfo / SQLGetFunctions
SQLGetConnectAttr / SQLGetEnvAttr / SQLGetDescField / SQLGetDescRec
SQLNumParams
SQLDescribeParam
SQLNativeSql
SQLParamData
SQLMoreResults
SQLGetDiagField / SQLGetDiagRec
SQLCancel
SQLCancelHandle (con identificador de instrucción)
SQLFreeStmt (con Option = SQL_CLOSE, SQL_DROP o SQL_UNBIND)
SQLCloseCursor
SQLDisconnect
SQLFreeHandle (con HandleType = SQL_HANDLE_STMT)
SQLGetStmtAttr
Las aplicaciones pueden seguir usando SQLSetDescField o SQLSetDescRec para establecer la información de enlace. No se cambiará la asignación de campos. Sin embargo, los campos del descriptor podrían devolver nuevos valores. Por ejemplo, SQL_DESC_PARAMETER_TYPE podría devolver SQL_PARAM_INPUT_OUTPUT_STREAM o SQL_PARAM_OUTPUT_STREAM.
Escenario de uso: recuperar una imagen en partes de un conjunto de resultados
SQLGetData se puede usar para obtener datos en partes cuando un procedimiento almacenado devuelve un conjunto de resultados que contiene una fila de metadatos sobre una imagen y la imagen se devuelve en un parámetro de salida grande.
// 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;
}
Escenario de uso: enviar y recibir un objeto grande como parámetro de entrada/salida transmitido
SQLGetData se puede usar para obtener y enviar datos en partes cuando un procedimiento almacenado pasa un objeto grande como parámetro de entrada/salida y transmite el valor hacia y desde la base de datos. No es necesario almacenar todos los datos en la memoria.
// 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;
}