参数绑定

参数绑定意义

参数是SQL语句中的一个变量(占位符),它能够让ODBC应用程序做到:

  • SQL不用写死,一个SQL重复利用;
  • 有效地向表中的列插入值;
  • 增强在生成查询过程中的用户交互作用;
  • 管理CLOB,BLOB等神通数据库支持的C 类型。

例如,表STUDENT有列名分别为NO,NAME,AGE的列。如果要向表中插入值而不是用参数则需要生成这样的SQL语句:

INSERT INTO STUDENT (NO, NAME, AGE) VALUES(1, 'MIKE', 23)

虽然这样的语句可以插入一行已知的记录到表中,但是如果应用程序需要插入多行记录到表中,则这种方法是很低效的。

ODBC通过允许应用程序用问号 (?) 来代表参数标记来替换语句中的数据值来解决这个问题。

在下面的例子里,三个数据值被三个参数标记所替代:

INSERT INTO STUDENT (NO, NAME, AGE) VALUES(?,?,?)

参数标记然后就被绑定到了应用程序的变量上。要插入一行新的记录,应用程序只需重置变量的值并执行该语句就行了。 接下来驱动会在SQL语句中用变量的当前值替代相应的参数标记,并将它传送到数据库中。

如果语句需要被执行多次,则应用程序可以通过准备执行的方式来更高效的完成这个工作。

每个参数标记通过他们的序号来被参照,参数标记被从左到右的进行编号。 SQL语句中最左边的参数标记被定为1号参数,下一个参数标记是2号参数,依此类推。

注意

神通数据库ODBC的参数占位符只能是半角的问号”?”,请勿使用其他符号,全角的问号也是不行的。

注解

神通odbc从odbcw(v3.0.27)/odbc(v3.0.25)之后的版本兼容了支持Oracle用冒号加参数名称的方式作为参数占位符,比如参数占位符可以写成:INSERT INTO STUDENT (NO, NAME, AGE) VALUES(:no,:name,:age)

执行前参数绑定

在语句被执行前,SQL语句中的每一个参数标记可以被关联到或者说绑定到应用程序的某个变量上。通过调用SQLBindParameter函数来完成绑定。 SQLBindParameter函数向驱动描述了程序变量的信息,包括地址、C类型等等。 它也通过参数标记的序号来区分每一个参数标记,并描述参数标记所代表的SQL对象的特征,包括SQL数据类型,精度等等。

在语句被执行前,参数标记可以在任何时间被绑定或者重绑定。 参数的绑定直至下列事件发生前始终是保持有效的:

  • 调用SQLFreeStmt函数并设置Option参数为SQL_RESET_PARAMS来释放语句句柄上所有被绑定的参数。
  • 调用SQLBindParameter函数并设置ParameterNumber参数为某个被绑定参数的序号,则该参数先前的绑定将被自动的释放。

参数绑定示例如下:

#define ROW_ARRAY_SIZE 10
      #define DESC_LEN 31
      SQLRETURN       result = SQL_SUCCESS;
      char *        dsql = (char *)"drop table STUDENT;";
      char *        csql = (char *)"create table STUDENT(id int,name varchar(30))";
      char *        Statement = (char *)"INSERT INTO STUDENT (id, name)VALUES (?, ?)";

      SQLUINTEGER std_id = 1;
      SQLLEN std_id_indp;
      char std_Name[DESC_LEN] = "神通数据库";
      SQLLEN std_Name_indp;
      SQLPOINTER ValuePtr;

      //初始化指示器的值为SQL_NTS
      std_id_indp = SQL_NTS;
      std_Name_indp = SQL_NTS;

      //建表
      result = SQLExecDirect(hstmt1, (SQLCHAR*)dsql, SQL_NTS);
      result = SQLExecDirect(hstmt1, (SQLCHAR*)csql, SQL_NTS);
      SQLCloseCursor(hstmt1);

      result = SQLPrepare(hstmt1, (SQLCHAR*)Statement, SQL_NTS);

      // 绑定参数
      result = SQLBindParameter(hstmt1, 1, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER,sizeof(SQLINTEGER), 0, (SQLPOINTER)&std_id, 0, &std_id_indp));
result = SQLBindParameter(hstmt1, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR,DESC_LEN -1, 0, std_Name, DESC_LEN, &std_Name_indp));

//执行,如果成功,则数据库中STUDENT表中新增了1条数据
      result = SQLExecute(hstmt1);

由于参数绑定可以重复绑定,只保留最后一次正确绑定的信息,因此SQLBindParameter的调用可以在SQLExecute调用之前,也可以在SQLExecute调用之后。

参数数组绑定

一行一行的数据绑定在性能上存在瓶颈,因此建议应用程序也可以通过将参数绑定到数组变量上来完成SQL语句的批量执行,以此来提高数据处理效率。 数组的绑定列宽绑定和行宽绑定两种方式,通过SQLSetStmtAttr设置语句句柄的SQL_ATTR_PARAM_BIND_TYPE参数进行设置, 如果SQL_ATTR_PARAM_BIND_TYPE的值为SQL_PARAM_BIND_BY_COLUMN(0) 则为列宽绑定,如果参数的值为非0,则代表是行宽绑定。

两种绑定详细介绍如下:

列宽绑定(Column-Wise Binding)

当每个独立的参数被分别绑定到自己独立的数组变量上时,被称作列宽绑定。列宽绑定方式可以通过调用SQLSetStmtAttr函数来指定, 调用时将Attribute参数设置为SQL_ATTR_PARAM_BIND_TYPE,ValuePtr参数设定为SQL_PARAM_BIND_BY_COLUMN。这种方式也是默认的参数绑定方式。

列宽参数绑定示例如下:

#define ROW_ARRAY_SIZE 10
      #define DESC_LEN 31
      SQLRETURN       result = SQL_SUCCESS;
      char *        dsql = (char *)"drop table STUDENT;";
      char *        csql = (char *)"create table STUDENT(id int,name varchar(30))";
      char *        Statement = (char *)"INSERT INTO STUDENT (id, name)VALUES (?, ?)";
      SQLUINTEGER std_id[ROW_ARRAY_SIZE];
      SQLLEN std_id_indp[ROW_ARRAY_SIZE];
      SQLCHAR std_Name[ROW_ARRAY_SIZE][DESC_LEN];
      SQLLEN std_Name_indp[ROW_ARRAY_SIZE];

      SQLUSMALLINT   i, ParamStatusArray[ARRAY_SIZE];
      SQLINTEGER ParamsProcessed;
      SQLLEN nRows = 0;
      // 绑定变量赋值
      for (i = 0; i < ARRAY_SIZE; i++)
      {
              std_id[i]= i;
              strcpy((char *)std_Name[i], "a中文");
              std_id_indp[i] = SQL_NTS;
              std_Name_indp[i] = SQL_NTS;
      }
      // 建表
      result = SQLExecDirect(hstmt1, (SQLCHAR*)dsql, SQL_NTS);
      result = SQLExecDirect(hstmt1, (SQLCHAR*)csql, SQL_NTS);
      // 列宽绑定,SQL_ATTR_PARAM_BIND_TYPE的值为SQL_PARAM_BIND_BY_COLUMN
      result = SQLSetStmtAttr(hstmt1, SQL_ATTR_PARAM_BIND_TYPE, SQL_PARAM_BIND_BY_COLUMN, 0);
      // 设置一次性绑定的行数
      result = SQLSetStmtAttr(hstmt1, SQL_ATTR_PARAMSET_SIZE, (SQLPOINTER)ARRAY_SIZE, 0);
      // 获得参数处理行数
      result = SQLSetStmtAttr(hstmt1, SQL_ATTR_PARAMS_PROCESSED_PTR, &ParamsProcessed, 0);
      // 绑定参数
      result = SQLBindParameter(hstmt1, 1, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER,sizeof(SQLINTEGER), 0, (SQLPOINTER)std_id, 0, std_id_indp);
      result = SQLBindParameter(hstmt1, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR,DESC_LEN - 1, 0, std_Name, DESC_LEN, std_Name_indp);
      //执行,如果成功,则数据库中STUDENT表中新增了10条数据
      result = SQLExecDirect(hstmt1, (SQLCHAR*)Statement, SQL_NTS);

行宽绑定(Row-Wise Binding)

当SQL语句中的所有参数被作为整体绑定到一个结构数组上,并且该结构包含了各个参数所绑定的变量时,被称作行宽绑定。 行宽绑定方式可以通过调用SQLSetStmtAttr函数来指定,调用时将Attribute参数设置为SQL_ATTR_PARAM_BIND_TYPE,ValuePtr参数设定为包含程序变量的结构的大小。

行宽参数绑定示例如下:

#define ROW_ARRAY_SIZE 10
#define DESC_LEN 31
SQLRETURN       result = SQL_SUCCESS;
typedef struct {
        SQLUINTEGER   Id;
        SQLCHAR       Name[20];
        SQLLEN   IdLenOrInd;
        SQLLEN    NameLenOrInd;
} STUDENT;
char *        dsql = (char *)"drop table STUDENT;";
char *        csql = (char *)"create table STUDENT(id int,name varchar(30))";
char *      Statement = (char *)"INSERT INTO STUDENT (id, name)VALUES (?, ?)";

STUDENT std[ROW_ARRAY_SIZE];
SQLUSMALLINT   i, ParamStatusArray[ARRAY_SIZE];
SQLINTEGER ParamsProcessed;
SQLLEN nRows = 0;

//设置结构体的值
for (i = 0; i < ARRAY_SIZE; i++)
{
        std[i].Id = i;
        strcpy((char *)std[i].Name, "a中文");
        std[i].IdLenOrInd = SQL_NTS;
        std[i].NameLenOrInd = SQL_NTS;
}
//建表
result = SQLExecDirect(hstmt1, (SQLCHAR*)dsql, SQL_NTS);
result = SQLExecDirect(hstmt1, (SQLCHAR*)csql, SQL_NTS);

// 行宽绑定,SQL_ATTR_PARAM_BIND_TYPE的值为结构体的宽度
result = SQLSetStmtAttr(hstmt1, SQL_ATTR_PARAM_BIND_TYPE, (SQLPOINTER)sizeof(STUDENT), 0);

// 设置一次性绑定的行数
result = SQLSetStmtAttr(hstmt1, SQL_ATTR_PARAMSET_SIZE, (SQLPOINTER)ARRAY_SIZE, 0);

// 获得参数处理行数
result = SQLSetStmtAttr(hstmt1, SQL_ATTR_PARAMS_PROCESSED_PTR, &ParamsProcessed, 0);

//绑定参数
result = SQLBindParameter(hstmt1, 1, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER,sizeof(SQLINTEGER), 0, (SQLPOINTER)&(std->Id), 0, &(std->IdLenOrInd));
result = SQLBindParameter(hstmt1, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, DESC_LEN-1, 0, std->Name, DESC_LEN, &(std->NameLenOrInd));

//执行,如果成功,则数据库中STUDENT表中新增了10条数据
result = SQLExecDirect(hstmt1, (SQLCHAR*)Statement, SQL_NTS);

注意

对于ODBC内部来说,如果设置的SQL_ATTR_PARAM_BIND_TYPE属性值大于0,则为行宽绑定,但如果传入的值与实际数据跨度长度,可能导致错误数据入库;等于0则为列宽绑定。

执行后参数绑定

参数绑定时,SQL语句中的参数标志除了可以被绑定到应用程序的某个变量外,也可以标志该参数为执行期参数。 此时,SQLBindParameter的 *StrLen_or_IndPtr 参数应该被设置成SQL_DATA_AT_EXEC。

当应用程序执行一个有执行期参数的SQL语句时,ODBC驱动会返回SQL_NEED_DATA。此时,应用程序需要根据这个执行期参数的数据类型, 调用SQLPutData提供数据;如果这个参数的数据较多,SQLPutData可以被重复调用从而分部分的放入数据。

一个执行期参数的所有数据放置完后,应用程序需要调用SQLParamData告诉ODBC驱动。 接下来ODBC驱动会尝试重新执行SQL语句,如果所有的执行期参数都已经提供数据,则SQL语句会被发送到服务器执行, 否则驱动会同样返回SQL_NEED_DATA;此时应用程序需要重复SQLPutData和SQLParamData的过程。

执行后绑定示例:

#define ROW_ARRAY_SIZE 10
      #define DESC_LEN 31
SQLRETURN     result = SQL_SUCCESS;
      char *        dsql = (char *)"drop table STUDENT;";
      char *        csql = (char *)"create table STUDENT(id int,name varchar(30))";
      char *        Statement = (char *)"INSERT INTO STUDENT (id, name)VALUES (?, ?)";

      SQLUINTEGER std_id = 1;
      SQLLEN std_id_indp;
      char std_Name[DESC_LEN];
      SQLLEN std_Name_indp;
      SQLPOINTER ValuePtr;

      //初始化指示器的值,std_Name为执行后绑定,则指示器的值为SQL_DATA_AT_EXEC
      std_id_indp = SQL_NTS;
      std_Name_indp = SQL_DATA_AT_EXEC;

      //建表
      result = SQLExecDirect(hstmt1, (SQLCHAR*)dsql, SQL_NTS);
      result = SQLExecDirect(hstmt1, (SQLCHAR*)csql, SQL_NTS));
      SQLCloseCursor(hstmt1);

      result = SQLPrepare(hstmt1, (SQLCHAR*)Statement, SQL_NTS);

      // 绑定参数
      result = SQLBindParameter(hstmt1, 1, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER,sizeof(SQLINTEGER), 0, (SQLPOINTER)&std_id, 0, &std_id_indp));
result = SQLBindParameter(hstmt1, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR,DESC_LEN -1, 0, std_Name, DESC_LEN, &std_Name_indp));
//执行
      result = SQLExecute(hstmt1);
      if (result == SQL_NEED_DATA) {
              while ((result = SQLParamData(hstmt1, &ValuePtr)) == SQL_NEED_DATA)
              {
                      if (ValuePtr == &std_id)
                      {
                              std_id = 1;
                      }
                      else if (ValuePtr == std_Name)
                      {
                              strcpy(std_Name, "1234567890123456789");
                              result = SQLPutData(hstmt1, std_Name, strlen(std_Name));
                      }
                      else
                      {
                              printf("获取putDate指针失败\n");
                      }
              }
      }
      else {
              if (result == SQL_SUCCESS) {
                      printf("没有要求输入执行期参数\n");
              }
      }

绑定NULL值

如果想通过参数绑定方式插入NULL值,则在绑定时给列的指示器设置为SQL_NULL_DATA,执行成功后,数据库中的数据为NULL。

值得注意的是,神通数据库中默认是区分NULL和空串''的,如果想插入空串,则在绑定时给定的数据长度为0即可。

绑定用例:

#define ROW_ARRAY_SIZE 10
      #define DESC_LEN 31

      SQLRETURN       result = SQL_SUCCESS;

      char *        dsql = (char *)"drop table STUDENT;";
      char *        csql = (char *)"create table STUDENT(id int,name varchar(30))";
      char *        Statement = (char *)"INSERT INTO STUDENT (id, name)VALUES (?, ?)";

      SQLUINTEGER std_id = 1;
      SQLLEN std_id_indp;
      char std_Name[DESC_LEN];
      SQLLEN std_Name_indp;
      SQLPOINTER ValuePtr;

      //初始化指示器的值,std_Name列的指示器的值为SQL_NULL_DATA
      std_id_indp = SQL_NTS;
      std_Name_indp = SQL_NULL_DATA

      //建表
      result = SQLExecDirect(hstmt1, (SQLCHAR*)dsql, SQL_NTS);
      result = SQLExecDirect(hstmt1, (SQLCHAR*)csql, SQL_NTS);
      SQLCloseCursor(hstmt1);

      result = SQLPrepare(hstmt1, (SQLCHAR*)Statement, SQL_NTS);

      // 绑定参数
      result = SQLBindParameter(hstmt1, 1, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER,sizeof(SQLINTEGER), 0, (SQLPOINTER)&std_id, 0, &std_id_indp));
result = SQLBindParameter(hstmt1, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR,DESC_LEN -1, 0, std_Name, DESC_LEN, &std_Name_indp));

//执行,如果成功,则数据库中STUDENT表中新增了1条数据,但name列的值为NULL
      result = SQLExecute(hstmt1);

获得OUT/INOUT参数

对于操作数据库存储过程等对象,可能会返回OUT/INOUT类型的参数,获取这类参数需要将SQLBindParameter函数的InputOutputType 参数设置为 SQL_PARAM_OUTPUT或者SQL_PARAM_INPUT_OUTPUT。当程序调用SQLExecute后,绑定的参数内存中就会返回out参数的值。对于returning语句也可以采用类似操作。

RETCODE     rc = SQL_SUCCESS , rc2 = SQL_SUCCESS;
SQLHSTMT        hstmtx = NULL;
int a = 1;
SQLLEN pind1 = 0;
SQLLEN pind2 = SQL_DATA_AT_EXEC;
SQLLEN pind3 = 0;
SQLPOINTER ValuePtr;

char  piecedatab[100] = { 0 };
char  piecedatac[100] = { 0 };
SQLLEN datalen = 0;

char * data = "abcdef";

SQLWCHAR *sql_drop = L"drop table test_odbc";
SQLWCHAR *sql_create = L"create table test_odbc( a int , b varchar(100) ,c varchar(100))";
SQLWCHAR *sql_insert = L"insert into test_odbc values(1,repeat('b',100),repeat('c',100))";
SQLWCHAR *sql_drop_pl = L"drop procedure pl_odbc";
SQLWCHAR *sql_pl = L"create or replace procedure pl_odbc(pa int , pb inout varchar(100), pc out varchar(100)) " \
                                                " as " \
                                                " begin" \
                                                " update test_odbc set b  = pb where a = pa returning b,c into pb,pc;" \
                                                " end;";

rc = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmtx);

rc = SQLExecDirect(hstmtx, sql_drop, SQL_NTS);
rc = SQLExecDirect(hstmtx, sql_create, SQL_NTS);
rc = SQLExecDirect(hstmtx, sql_insert, SQL_NTS);
rc = SQLExecDirect(hstmtx, sql_drop_pl, SQL_NTS);
rc = SQLExecDirect(hstmtx, sql_pl, SQL_NTS);

rc = SQLPrepareW(hstmtx, (SQLWCHAR*)L"exec pl_odbc(?,?,?)", SQL_NTS);
rc = SQLBindParameter(hstmtx, 1, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER, 0, 0, &a, sizeof(a), &pind1);
rc = SQLBindParameter(hstmtx, 2, SQL_PARAM_INPUT_OUTPUT, SQL_C_BINARY, SQL_LONGVARBINARY, 0, 0, piecedatab, sizeof(piecedatab), &pind2);
rc = SQLBindParameter(hstmtx, 3, SQL_PARAM_OUTPUT, SQL_C_BINARY, SQL_LONGVARBINARY, 0, 0, piecedatac, sizeof(piecedatac), &pind3);

rc = SQLExecute(hstmtx);

//piecedatab和piecedatac内存此时有返回的值

rc = SQLFreeHandle(SQL_HANDLE_STMT, hstmtx);

SQLGetData获得OUT/INOUT参数

在 ODBC 3.8 之前,应用程序只能检索具有绑定输出缓冲区的查询的输出参数。 但是,当参数值的大小非常大时,很难分配非常大的缓冲区 (例如,大型图像) 。 ODBC 3.8 引入了一种检索部件中的输出参数的新方法。 应用程序现在可以多次调用具有小缓冲区的 SQLGetData 来检索大型参数值。 这类似于检索大型列数据。

若要绑定要分部分检索的输出参数或输入/输出参数,请使用 InputOutputType 参数设置为 SQL_PARAM_OUTPUT_STREAM 或 SQL_PARAM_INPUT_OUTPUT_STREAM 调用 SQLBindParameter。 使用 SQL_PARAM_INPUT_OUTPUT_STREAM,应用程序可以使用 SQLPutData 将数据输入参数,然后使用 SQLGetData 检索输出参数。 输入数据必须采用执行时数据 (DAE) 形式,使用 SQLPutData 而不是将其绑定到预先分配的缓冲区。

实例1:用SQLGetData获取存储过程返回的LOB对象参数

RETCODE     rc = SQL_SUCCESS , rc2 = SQL_SUCCESS;
SQLHSTMT        hstmtx = NULL;
int a = 1;
SQLLEN pind1 = 0;
SQLLEN pind2 = SQL_DATA_AT_EXEC;
SQLLEN pind3 = 0;
SQLPOINTER ValuePtr;

char  piecedata[100] = { 0 };
SQLLEN datalen = 0;

char * data = "abcdef";

SQLWCHAR *sql_drop = L"drop table test_odbc";
SQLWCHAR *sql_create = L"create table test_odbc( a int , b blob ,c blob)";
SQLWCHAR *sql_insert = L"insert into test_odbc values(1,repeat('b',1002),repeat('c',1002))";
SQLWCHAR *sql_drop_pl = L"drop procedure pl_odbc";
SQLWCHAR *sql_pl = L"create or replace procedure pl_odbc(pa int , pb inout blob, pc out blob) " \
                                                " as " \
                                                " begin" \
                                                " update test_odbc set b  = pb where a = pa returning b,c into pb,pc;" \
                                                " end;";

rc = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmtx);

rc = SQLExecDirect(hstmtx, sql_drop, SQL_NTS);
rc = SQLExecDirect(hstmtx, sql_create, SQL_NTS);
rc = SQLExecDirect(hstmtx, sql_insert, SQL_NTS);
rc = SQLExecDirect(hstmtx, sql_drop_pl, SQL_NTS);
rc = SQLExecDirect(hstmtx, sql_pl, SQL_NTS);

rc = SQLPrepareW(hstmtx, (SQLWCHAR*)L"exec pl_odbc(?,?,?)", SQL_NTS);
rc = SQLBindParameter(hstmtx, 1, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER, 0, 0, &a, sizeof(a), &pind1);
rc = SQLBindParameter(hstmtx, 2, SQL_PARAM_INPUT_OUTPUT_STREAM, SQL_C_BINARY, SQL_LONGVARBINARY, 0, 0, piecedata, sizeof(piecedata), &pind2);
rc = SQLBindParameter(hstmtx, 3, SQL_PARAM_OUTPUT_STREAM, SQL_C_BINARY, SQL_LONGVARBINARY, 0, 0, piecedata, sizeof(piecedata), &pind3);

rc = SQLExecute(hstmtx);
if (SQL_NEED_DATA == rc)
{
        SQLPOINTER token = NULL;
        while (SQL_NEED_DATA == (rc = SQLParamData(hstmtx, &token)))
        {
                datalen = 0;
                rc = SQLPutData(hstmtx, data, strlen(data));
                rc = SQLPutData(hstmtx, data, strlen(data));
                rc = SQLPutData(hstmtx, data, strlen(data));
        }

        while (rc == SQL_PARAM_DATA_AVAILABLE)
        {
                int gettimes = 0;
                rc = SQLParamData(hstmtx, &token);
                datalen = 0;
                if (rc == SQL_PARAM_DATA_AVAILABLE) {
                        do {
                                rc2 = SQLGetData(
                                        hstmtx,
                                        (UWORD)token,          // the value of the token is the ordinal.
                                        SQL_C_BINARY,           // The C-type.
                                        piecedata,            // A small buffer.
                                        sizeof(piecedata),    // The size of the buffer.
                                        &datalen);               // How much data we can get.
                                gettimes++;
                        } while (rc2 == SQL_SUCCESS_WITH_INFO);
                        if ((UWORD)token == 2)
                        {
                           //gettimes等于1
                        }
                        else
                        {
                                //gettimes等于6
                        }

                        printf("parameter %d success get %d times \n" , (UWORD)token , gettimes);
                }
        }
}
rc = SQLFreeHandle(SQL_HANDLE_STMT, hstmtx);

实例2:用SQLGetData获取returning返回的列值

RETCODE     rc = SQL_SUCCESS;
SQLHSTMT        hstmtx = NULL;
int a = 1;
int a_out = 0;
SQLLEN pind1 = 0;
SQLLEN pind2 = 0;
SQLLEN pind3 = 0;
SQLLEN pind4 = 0;

char b_out[20] = { 0 };
char c_out[30] = { 0 };

SQLWCHAR *sql_drop = L"drop table test_odbc";
SQLWCHAR *sql_create = L"create table test_odbc( a int ,b varchar(10), c timestamp)";

SQLLEN datalen = 0;
rc = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmtx);
rc = SQLExecDirect(hstmtx, sql_drop, SQL_NTS);
rc = SQLExecDirect(hstmtx, sql_create, SQL_NTS);

rc = SQLPrepareW(hstmtx, (SQLWCHAR*)L"insert into  test_odbc values(?,'abc','2023-08-04 12:11:10') returning a,b,c into ?,?,?", SQL_NTS);
rc = SQLBindParameter(hstmtx, 1, SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER, 0, 0, &a, sizeof(a), &pind1);
rc = SQLBindParameter(hstmtx, 2, SQL_PARAM_OUTPUT_STREAM, SQL_C_LONG, SQL_INTEGER, 0, 0, &a_out, sizeof(a_out), &pind2);
rc = SQLBindParameter(hstmtx, 3, SQL_PARAM_OUTPUT_STREAM, SQL_C_CHAR, SQL_CHAR, 0, 0, b_out, sizeof(b_out), &pind3);
rc = SQLBindParameter(hstmtx, 4, SQL_PARAM_OUTPUT_STREAM, SQL_C_CHAR, SQL_CHAR, 0, 0, c_out, sizeof(c_out), &pind4);

rc = SQLExecute(hstmtx);
if (SQL_PARAM_DATA_AVAILABLE == rc)
{
        SQLPOINTER token = NULL;
        while (SQLParamData(hstmtx, &token) == SQL_PARAM_DATA_AVAILABLE)
        {
                int gettimes = 0;
                datalen = 0;

                if ((UWORD)token == 2)
                {
                        while (SQLGetData(hstmtx, (UWORD)token, SQL_C_LONG, &a_out, sizeof(a_out), &datalen) != SQL_NO_DATA)
                        {
                                gettimes++;
                        }
                        //此处gettimes等于1,a_out 等于1
                }
                if ((UWORD)token == 3)
                {
                        while (SQLGetData(hstmtx, (UWORD)token, SQL_C_CHAR, b_out, sizeof(b_out), &datalen) != SQL_NO_DATA)
                        {
                                gettimes++;
                        }
                        //此处gettimes等于1,b_out等于"abc"
                }

                if ((UWORD)token == 4)
                {
                        while (SQLGetData(hstmtx, (UWORD)token, SQL_C_CHAR, c_out, sizeof(c_out), &datalen) != SQL_NO_DATA)
                        {
                                gettimes++;
                        }
                        //此处gettimes等于1,c_out等于"2023-08-04 12:11:10"
                }
        }
}

rc = SQLFreeHandle(SQL_HANDLE_STMT, hstmtx);

注解

SQLGetData 主要应用在返回比较大的数据情况下的流式获取,比如返回CLOB/BLOBTEXT类型, 避免程序占用很大的内存。但SQLGetData是支持通过流获取任意类型,对于普通类型使用这种方式的实际意义不大,反而可能会降低效率。