文章目录

  • 前引
  • 一. ODBC?
  • 二. ODBC体系结构
  • 三. ODBC句柄
    • 3.1 环境句柄 SQL_HANDLE_ENV
    • 3.2 连接句柄 SQL_HANDLE_DBC
    • 3.3 语句句柄 SQL_HANDLE_STMT
  • 四. 连接数据库
  • 五. 执行SQL语句
    • 5.1 SQLExecDirect()
    • 5.2 SQLPrepare() + SQLExecute()
    • 5.3 Procedures
      • 5.3.1 About Procedures
      • 5.3.2 Execute Procedures
  • 六.获取结果集
    • 6.1 SQLBindCol() + SQLFetch()
    • 6.2 SQLFetch() + SQLGetData()
    • 6.3 SQLGetData() vs SQLBindCol()
  • 七. CRUD
    • 7.1 Update
    • 7.2 Delete
  • 八.一些概念
    • 1.句柄 Handle
    • 2.char 与 wchar_t
    • 3.游标 Cursor
    • 4.LPCSTR、LPCTSTR 和 LPTSTR
    • 5. 数据缓冲区 Buffers
      • 5.1 Relation Between Data Buffers and Length/Indicator Buffers
      • 5.2 Length/Indicator Buffers Values !!!
  • 九.问题:
  • 十. 代码总汇
  • 参考

前引

以下记录了博主学习用C++做ODBC开发实现对数据库进行CRUD的历程。我建议仔细阅读Microsoft官方文档以学习如何进行ODBC开发


一. ODBC?

微软提出的数据库访问接口标准。ODBC定义了访问数据库的API一个规范,这些API独立于不同厂商的DBMS,也独立于具体的编程语言,使用该API集可以访问任何提供了ODBC驱动程序的数据库。


二. ODBC体系结构


客户程序: 调用 ODBC 函数以提交 SQL 语句并检索结果。
驱动程序管理器: 管理对多个DBMS的同时访问
驱动程序: 处理 ODBC 函数调用,将 SQL 请求提交到特定数据源,并将结果返回到应用程序。如有必要,驱动程序会修改应用程序的请求,以便该请求符合关联的 DBMS 支持的语法。每种数据库都提供自己的ODBC驱动程序,ODBC接口通过专门的驱动程序与数据库交换信息。
各种关系数据库: 数据源DSN

应用程序与DBMS通过驱动程序联系起来
驱动程序管理器与驱动程序通过句柄联系起来:
The application uses Driver Manager handles when calling ODBC functions because it calls those functions in the Driver Manager. The Driver Manager uses this handle to find the corresponding driver handle and uses the driver handle when calling the function in the driver.
That is: 应用程序在驱动管理器中调用ODBC函数,驱动管理器通过自身句柄找到相应的驱动句柄,进而实现函数调用。


三. ODBC句柄

句柄是一个不透明的变量,是ODBC驱动程序实现数据库操作的手段。‎‎驱动程序管理器和驱动程序使用句柄查找有关项目的信息。‎环境、连接和语句句柄对于初始化和终止ODBC程序是必需的。
Handle -Microsoft

3.1 环境句柄 SQL_HANDLE_ENV

作用:
①环境句柄是指包含有关应用程序全局状态(如属性和连接)的信息的数据对象。
②此句柄通过调用SQLAllocHandle()进行分配(HandleType 设置为 SQL_HANDLE_ENV),并通过调用SQLFreeHandle()释放(HandleType 设置为 SQL_HANDLE_ENV)。
③使用ODBC的每个程序从创建环境句柄开始,以释放环境句柄结束。环境句柄在每个应用程序中只能创建一个。必须先分配环境句柄,然后才能分配连接句柄。Driver Manager 为连接到它的每个应用程序维护一个单独的环境句柄。

分配:

 SQLHENV hEnv = NULL ;ret =  SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &hEnv);

设置环境句柄属性:

ret = SQLSetEnvAttr(hEnv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, SQL_IS_INTEGER);

Environment Handles

3.2 连接句柄 SQL_HANDLE_DBC

作用:
①连接句柄是指包含与特定数据源的连接相关联的信息的数据对象。该句柄标识每个连接。
②连接句柄的分配和释放与环境句柄一致,区别在于要将HandleType 设置为 SQL_HANDLE_DBC
③一个应用程序可以同时连接到多个数据库服务器。应用程序需要为与数据库服务器的每个并发连接提供一个连接句柄。连接句柄在环境句柄上创建,可以有多个。

分配:

SQLHDBC hDbc = NULL;
ret = SQLAllocHandle(SQL_HANDLE_DBC, hEnv, &hDbc);

3.3 语句句柄 SQL_HANDLE_STMT

作用:
①语句句柄是指描述和跟踪 SQL 语句执行情况的数据对象。驱动程序分配了一个结构来存储有关语句的信息(result set, parameters),并将指向该结构的指针作为语句句柄返回。
②您可以通过调用 SQLAllocHandle() 来分配一个语句句柄来描述 SQL 语句(将 HandleType 设置为 SQL_HANDLE_STMT),必须先执行此操作,然后才能执行语句。
③语句句柄在连接句柄上创建,可以有多个。

分配:

ret = SQLAllocHandle(SQL_HANDLE_STMT, hDbc, &hStmt);

Statement Handles


四. 连接数据库

SQLConnect()在ODBC 驱动程序和数据源(MySQL)之间建立连接。

ret =  SQLConnect (hDbc, (SQLCHAR*)"Test", SQL_NTS, (SQLCHAR*)"root", SQL_NTS, (SQLCHAR*)"password", SQL_NTS);

参数分别为: 连接句柄,数据源DSN(data source name), SQL_NTS,数据库登录名(通常是root),SQL_NTS,数据库登录密码,SQL_NTS

若连接失败,则获取错误原因的方法:
(项目属性为多字节字符集)

if (ret == SQL_ERROR){SQLCHAR* state = new SQLCHAR[5]; SQLError (hEnv, hDbc, NULL, state, NULL, NULL, NULL, NULL);cout << "错误信息:"<< state << endl; //获取错误信息SQLSTATE}

SQLConnect()函数
SQLError()函数


五. 执行SQL语句

Executing a Statement

5.1 SQLExecDirect()

SQLCHAR* SQLToExe = NULL;
SQLToExe = (SQLCHAR*)"SELECT * FROM student";
ret = SQLExecDirect (hStmt, SQLToExe, SQL_NTS);

若执行失败,获取错误信息:

if (ret == SQL_ERROR){SQLCHAR* state2 = new SQLCHAR[5];SQLError (NULL, NULL, hStmt, state2, NULL, NULL, NULL, NULL);cout << "错误信息:" << state2 << endl; //获取错误信息SQLSTATE}

5.2 SQLPrepare() + SQLExecute()

主要用于执行带参数的SQL语句。对于多次执行的SQL语句使用此方法相较于SQLExecDirect()效率更高,因为语句只需编译一次
先用SQLPrepare()提交SQL语句,再用’SQLBindParameter()函数绑定参数,最后用SQLExecute()执行语句

    SQLCloseCursor(hStmt);// 关闭游标SQLCHAR* SQLToExe3 = (SQLCHAR*)"INSERT INTO student VALUES(?,?,?)";ret = SQLPrepare(hStmt, SQLToExe3, SQL_NTS);Test(ret);SQLCHAR SNOInput[SNO_Len], SNameInput[SName_Len], SDepartInput[SDepart_Len];cout << "Input student message:";cin >> SNOInput >> SNameInput >> SDepartInput;SQLLEN SNOLen = SQL_NTS, SNameLen2 = SQL_NTS, SDepartLen2 = SQL_NTS;SQLBindParameter(hStmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR, 20, 0, SNOInput, SNO_Len*sizeof(char), &SNOLen);SQLBindParameter(hStmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR, 20, 0, SNameInput, SName_Len*sizeof(char), &SNameLen2);SQLBindParameter(hStmt, 3, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR, 20, 0, SDepartInput, SNO_Len*sizeof(char), &SDepartLen2);ret = SQLExecute(hStmt);

博主在执行SQLExecute(hStmt)时,未能执行成功,函数返回SQL_NEED_DATA, 博主为此花了近一天来查找错误原因。最终在参考了官方文档 Setting Parameter Values, Using Length and Indicator Values后解决:
原因是我在SQLBindParameter()的最后一个参数StrLen_or_IndPtr未设置正确,在设置为SQL_NTSSQLExecute()正确运行。(此外应注意SQLExecute()BufferLength参数是以字节为单位,应以数组长度*sizeof(数据类型)为实际参数。— 只针对可变长的输出参数)

关于SQLPrepare()SQLExecute()中语句句柄的作用: Handle

SQLPrepare()
SQLBindParameter()
SQLExecute()
Statement Parameters
SQLExecDirect vs SQLPrepare+SQLExecute

5.3 Procedures

5.3.1 About Procedures

存储过程是永久存储在数据源上的可执行对象,在运行时执行一次或多次。
①过程通常是执行 SQL 语句的最快方式。
②修改过程,而无需重新编译应用程序。
③过程可以与应用程序的其余部分分开开发。
④过程被编写用于实现特定任务,因此适用于特定功能的应用程序(custom applications)的开发,而不适用于通用应用程序(generic applicaitions),
⑤可移植性低。ODBC没有提供创建过程的标准语法,所以对不同的DBMS需要编写不同的过程,并且一些DBMS不支持过程。

5.3.2 Execute Procedures

执行过程必须有参数

    cout << "-------" << "Execute Procedures:" << "-------" << endl;SQLCloseCursor(hStmt);if (ret == SQL_SUCCESS)cout << "Close Cursor Successfully!" << endl;SQLCHAR countDep[SDepart_Len]= "CS";SQLLEN countDepInd =SQL_NTS;SQLLEN retCSNum = 100,retCSNumInd = SQL_IS_INTEGER;ret = SQLBindParameter(hStmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR,20,NULL,countDep,SDepart_Len*sizeof(char),&countDepInd);if (ret == SQL_SUCCESS)cout << "Bind Successfully!" << endl;ret = SQLBindParameter(hStmt, 2, SQL_PARAM_OUTPUT, SQL_C_LONG, SQL_INTEGER, 0, 0, &retCSNum, sizeof(int), &retCSNumInd);if (ret == SQL_SUCCESS)cout << "Bind Successfully!" << endl;ret = SQLExecDirect(hStmt, (SQLCHAR*)"call CSNum(?,?)", SQL_NTS);.// do not use {sql}if (ret == SQL_SUCCESS)cout << "Exeute Procedures Successfully!" << endl;else if (ret == SQL_ERROR){SQLCHAR* state2 = new SQLCHAR[5];SQLError(NULL, NULL, hStmt, state2, NULL, NULL, NULL, NULL);cout << "error message:" << state2 << endl; //SQLSTATE:42S22 --> Column did not exist.}cout << retCSNum << endl;// return Number of students in CS

在这里遇到的问题:

  1. SQLExecDirect()执行失败,返回错误:42S22。经查阅SQLExecDirect()的诊断信息,博主了解到这个错误的原因是未找到列。起先博主自以为是程序未找到存储过程,为此查看了很久的有关ODBC 执行存储过程的官方文档,但未解决问题。后来博主验证MySQL上的存储过程CSNum确实是没有问题的,因此再次把目光集中到解决找不到列这个问题上,最后发现应用程序不支持调用的过程中出现:Procedures_name.parameter_name,只允许在过程中直接使用parameter.name,在将过程CSNum中的CSNum.Depart 改为 DepartSQLExecDirect()执行成功,MySQL上的过程CSNum如下:
delimiter $$
CREATE PROCEDURE CSNum(in Depart VARCHAR(20), out s_count INTEGER)
BEGIN SELECT COUNT(*) into s_countFROM studentWHERE student.SDepart = Depart;
END $$
delimiter;
  1. 解决问题1后,打印输出参数retCSNum发现其值无效,还是等于最初定义的100,这说明Call CSNum
    没有执行成功,在将SQLExecDirect()中的“{call CsNum(?,?)}"改为“call CsNum(?,?)”retCSNum正确返回数量信息。显然这与官方说明Procedure Call有差异,对于具体的数据库系统MySQL,在应用程序中调用存储过程的SQL语句应该是:“[?=]call procedure-name(?,?, ...)]”,而不该加上大括号

Procedures ODBC
Procedure Parameters


六.获取结果集

6.1 SQLBindCol() + SQLFetch()

SQLBindCol()
SQLFetch()

获取结果集的过程: SQLBindCol()将结果集中的某一列绑定到C++程序的一个存储空间(通常是一个由指针开辟的连续存储空间,如数组),接着由SQLFetch()实现以下两点功能:①数据传输:移动游标至下一行,返回关联列的数据 ②数据类型转换:同时将数据类型从 SQL type转换为在 SQLBindCol()fCType参数中指定的C 类型变量。

    cout << "获取结果集---SQLBindCol + SQLFetch()方式:" << endl;SQLCHAR SNO[SNO_Len] = {'0'};SQLLEN len =0;ret = SQLBindCol(hStmt, 1, SQL_C_DEFAULT, SNO, SNO_Len, &len);Test(ret);  // 8 通过打印len的值可以知道,此时还没有取回数据到 SNO数组中,执行SQLFetch后 SNO才有了取回的数据SQLRETURN ret = SQL_SUCCESS;while (true) // 遍历该列所有记录{ret = SQLFetch(hStmt);if (ret == SQL_NO_DATA_FOUND) // 读完所有行,游标位于结果集之后{cout << "End of data." << endl;break;}if (ret == SQL_ERROR){cout << "Error!" << endl;break;}cout << "SNO:" << SNO << "   ";}

SQLBindCol()的最后两个参数 cbValueMaxpcbValue的说明:前者表示应用程序提供的数据缓冲区的大小(BufferLenth),后者用于接收返回的数据在数据缓冲区实际占用大小这一信息(DataLen)。

6.2 SQLFetch() + SQLGetData()

SQLGetData():检索结果集当前行/游标所指向的行中单个列的数据。

必须在SQLGetData()之前调用SQLFetch() 。经博主实操后确认,直接执行SQLGetData()会导致游标无效:重新打开游标后,游标指向结果集之前而不是行。

重新打开游标是因为我已经调用了SQLBindCol()+SQLFetch()遍历过一遍行,所以需要重置游标位置

获取结果集的过程: 先调用SQLFetch()把结果集传到应用程序,接着调用SQLGetData()接收数据。 改变SQLGetData()icol参数可以获得不同列的值

    cout << "获取结果集---SQLFetch()+SQLGetData()方式:" << endl;ret = SQLCloseCursor(hStmt);// 关闭游标ret = SQLExecDirect(hStmt, SQLToExe, SQL_NTS); // 重新打开游标SQLCHAR SNO2[SNO_Len] = {'0'};  SQLCHAR SName[SName_Len] = { '0' };SQLLEN len2 =0,Name_Len; while (true) // 遍历该列所有记录{ret = SQLFetch(hStmt);if (ret == SQL_NO_DATA_FOUND) // 读完所有行,游标位于结果集之后{cout << "End of data." << endl;break;}if (ret == SQL_ERROR){cout << "Error!" << endl;break;}SQLGetData(hStmt, 1, SQL_C_DEFAULT, SNO2, SNO_Len, &len2);SQLGetData(hStmt, 2, SQL_C_DEFAULT, SName, SNO_Len, &Name_Len);cout << "SNO:" << SNO2 << " SNAME:"<< SName << "   ";}

6.3 SQLGetData() vs SQLBindCol()

SQLGetData() vs SQLBindCol()
区别在于:SQLGetData()在调用SQLFetch() 之后绑定变量;SQLBindCol() 在调用SQLFetch()前绑定变量。 SQLGetData()可用于检索未绑定列。区别不大,性能上,SQLBindCol()+SQLFetch() 优于 SQLFetch()+SQLGetData(); 但灵活性SQLGetData()优于SQLBindCol(),因为无需提前绑定列即可获取列值。

C 和 SQL 对应的数据类型


七. CRUD

7.1 Update

更新学习小组的任务量属性:

 cout << "-------" << "UPDATE" << "-------" << endl;ret = SQLCloseCursor(hStmt);Test(ret, hStmt);SQLLEN wordTask = 100, wordTaskInd = SQL_IS_INTEGER;cout<<"Please input the task to be assigned: ";cin >> wordTask;ret = SQLBindParameter(hStmt, 1 , SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER, 0, 0, &wordTask, sizeof(int), &wordTaskInd);Test(ret, hStmt);ret = SQLExecDirect(hStmt, (SQLCHAR*)"UPDATE stuGroup SET GTask = ?",SQL_NTS); // error: set to be null Test(ret, hStmt);

博主在这里遇到了问题。API全部返回SQL_SUCCESS,但数据库中的GTask却没有被修改为wordTask值,而是被置为了NULL。博主为此排查了几个小时了也没有解决。
在博主仔细阅读有关SQLBindParameter()参数绑定的官方文档后,将 wordTaskInd = SQL_IS_INTEGER改为wordTaskInd = SQL_IS_UINTEGER,或SQLBindParameter()填入0后UPDATE语句成功执行

SQLBindParameter()
Binding Parameter Markers By SQLBindParameter()
Binding Parameters ODBC

7.2 Delete

从学生表中删除指定学号的学生信息:

    cout << "-------" << "DELETE" << "-------" << endl;ret = SQLCloseCursor(hStmt);Test(ret, hStmt);SQLCHAR SnoToDel[SNO_Len];cout << "Please input the SN0 to be deleted: ";cin >> SnoToDel;ret = SQLPrepare(hStmt, (SQLCHAR*)"DELETE FROM STUDENT WHERE SNO = ?", SQL_NTS);Test(ret, hStmt);ret = SQLBindParameter(hStmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR, 20, 0, &SnoToDel, 0, 0);Test(ret, hStmt);ret = SQLExecute(hStmt);Test(ret, hStmt);

八.一些概念

1.句柄 Handle

句柄是标识特定项目的不透明 32 位值

标识符,标识一个对象。
中间媒介,是获取另一个对象的方法——一个广义的指针,通过这个中间媒介可控制、操作对象。创建句柄的目的就是建立起与被访问对象之间的联系

一个通俗的解释: 什么是句柄?

2.char 与 wchar_t

char用于 ANSI编码系列: 一个字符 → \rightarrow → 一个字节
wchar_t用于Unicode编码系列: 一个字符 → \rightarrow →多个字节

关于编码:
Unicode和多字节字符集
ASCII,Unicode 和 UTF-8 - 阮一峰
unicode占几个字节?:
由具体的存储方式而定
UCS-2/UTF-16 : 两个字节
UCS-4/UTF-32 : 四个字节,其中前两个字节表示平面数,后两个字节表示字符。
一般还是说占两个字节(16位)

3.游标 Cursor

游标是一个数据缓冲区,用于暂时存放SQL语句的执行结果/结果集
游标由结果集和结果集中指向特定记录的游标位置组成。 游标总是与一条SQL 查询语句相关联,因为游标的结果集是由SELECT语句产生,
游标充当指针,是一种能从包括多条数据记录的结果集中每次提取一条记录的机制,它还提供对基于游标位置而对表中数据进行删除或更新的能力

当执行创建结果集的语句时,会隐式打开游标。打开游标时,它位于结果集的第一行之前。使用SQLFetch() 可将游标前进到结果集的下一行.在嵌入式 SQL 和 ODBC 中,游标必须在应用程序完成使用后关闭。

游标在结果集中只正向移动而不更新结果集,对于这种应用情况,游标行为相对比较简单。缺省情况下,ODBC 应用程序会请求此行为。ODBC 定义一个只读的单向游标。单向游标只能向前移动,要重置游标,必须先关闭游标SQLCloseCursor(),再通过再次执行 SELECT 语句来重新打开游标。单向游标对与只需要浏览一次的应用非常有用,而且效率很高。

Cursors -Microsoft
SQL游标(cursor)详细说明 -博客园

4.LPCSTR、LPCTSTR 和 LPTSTR

LP == Long Pointer.认为是指针或字符’ * ’
C = Const,在这种情况下,我认为它们意味着字符串是一个const,而不是指针是const。
STR表示字符串
T表示TCHAR, 如果定义了 UNICODE,则 TCHAR =WCHAR,否则 TCHAR = CHAR。因此,如果未定义UNICODE,则LPCTSTR == LPCSTR。

5. 数据缓冲区 Buffers

5.1 Relation Between Data Buffers and Length/Indicator Buffers

缓冲区是用于在应用程序和驱动程序之间传递数据的任何应用程序内存。

①数据缓冲区的地址表现为 SQLPOINTER 类型的参数, 如SQLBindParameter()函数中的 SQLPOINTER ParameterValuePtr参数,SQLBindCol()函数中的SQLPOINTER TargetValuePtr参数,

②数据缓冲区Data buffers用于传递数据本身,而长度/指示符缓冲区length/indicator buffers用于传递数据缓冲区中数据的长度。两者关系如图
③数据缓冲区及其包含的数据的长度均以字节为单位,而不是字符。这也是为什么SQLBindParameter()SQLLEN BufferLength参数要注意数据类型长度。但是只有对输出缓冲区需要说明数据缓冲区长度,输入参数只需将该参数置为0;驱动程序仅在缓冲区包含可变长度数据(例如字符或二进制数据)时才检查数据缓冲区长度,而对固定长度的参数(例如整数或日期结构)驱动程序忽略数据缓冲区长度并假设缓冲区足够大以容纳数据。

5.2 Length/Indicator Buffers Values !!!

绑定参数的函数总要涉及数据长度/指标缓冲区,博主一开始没有搞清楚传什么参才正确,因此总是会出现些未知错误,而花费博主几个小时去找,直到找到该参数上。为避免再次出现这样的情况,博主决定好好总结一下这个参数的用法。

Length/Indicator Buffers有两个作用
① 传递数据缓冲区中数据的字节长度
② 传递一个特殊的指标,用来说明Data Buffers中数据。(通过把传入的实参设置为特殊值。如SQL_NULL_DATA表示数据为NULL数据值,则相应数据缓冲区中的值被忽略。)
Length/Indicator Buffers在函数中通常以参数StrLen_or_Ind 或类似名称来表述。

对Data Buffers中不同类型的数据,Length/Indicator Buffers 传递不同的指标,常用的有以下对应关系
①以空值结尾的字符串 (variable-length data) ------ SQL_NTS
②数字(non-null, fixed-length data) ------ 0(对输入参数) , NULL (对输出参数)

即当数据缓冲区的数据是一个字符串时,把参数StrLen_or_Ind设置为SQL_NTS; 数据是一个固定长的值时如整数,则把参数StrLen_or_Ind设置为0等。目的是向驱动程序管理器指示数据缓冲区的性质。

更多信息见:
Buffers
Using Length and Indicator Values
Data Buffer Length


九.问题:

1.为什么以unicode编码接受输入,以unicode编码输出时显示乱码(项目属性 使用 Unicode 字符集)

ret =  SQLConnectW (hDbc, (SQLWCHAR*)"Test", SQL_NTS, (SQLWCHAR*)L"root", SQL_NTS, (SQLWCHAR*)L"200111", SQL_NTS);
// Test前不加L是为了连接失败// 连接数据库if (ret == SQL_ERROR){SQLWCHAR* state = new SQLWCHAR[5];  //UNICODE编码SQLErrorW (hEnv, hDbc, NULL, state, NULL, NULL, NULL, NULL);cout << "错误信息:"<< state << endl; //获取错误信息SQLSTATE}


L 的含义:字符串前的L表示该字符串是一个宽字符串,类型是 wchar_t ,而非char。


十. 代码总汇

#include<Windows.h>
#include<sql.h>
#include<sqlext.h>
#include<sqltypes.h>
#include<iostream>
#include <iomanip>
#include<string>
#define SNO_Len 20
#define SName_Len 20
#define SDepart_Len 20
using namespace std;
void Test(SQLRETURN, const SQLHSTMT&);
void DisplayValue(const SQLHSTMT&, const SQLCHAR*, const SQLCHAR*, const SQLCHAR*);
void DisplayValue(const SQLHSTMT&);int main()
{SQLHENV hEnv = NULL;  //  保存环境句柄  --> 一个指针变量SQLHDBC hDbc = NULL;  //  保存连接句柄  SQLHSTMT hStmt = NULL;//  保存语句句柄SQLRETURN  ret; // test       SQLINTEGER/SQLRETURN ret = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &hEnv);// 分配环境句柄ret = SQLSetEnvAttr(hEnv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3, 0);// SQLPOINTER = void* // 2  修改环境句柄的属性:确定某些功能是表现出 ODBC 2.0 行为还是 ODBC 3.0 行为。ret = SQLAllocHandle(SQL_HANDLE_DBC, hEnv, &hDbc);// 分配连接句柄SQLSetConnectAttr(hDbc, SQL_ATTR_AUTOCOMMIT, (SQLPOINTER)SQL_AUTOCOMMIT_ON, 0); // 设置连接句柄-- 自动提交模式ret = SQLConnect(hDbc, (SQLCHAR*)"Test", SQL_NTS, (SQLCHAR*)"root", SQL_NTS, (SQLCHAR*)"200111", SQL_NTS);//Function: 连接数据库if (ret == SQL_ERROR){SQLCHAR* state = new SQLCHAR[7];SQLError(hEnv, hDbc, NULL, state, NULL, NULL, NULL, NULL);//Function:获取错误信息SQLSTATEcout << "错误信息:" << state << endl;}ret = SQLAllocHandle(SQL_HANDLE_STMT, hDbc, &hStmt); // Function:分配语句句柄SQLSetStmtAttr(hStmt, SQL_ATTR_QUERY_TIMEOUT, (SQLPOINTER)3, 0); //Function:设置语句属性cout << endl;cout << "-------" << "SQLExecDirect -> Read" << "-------" << endl;SQLCHAR* SQLToExe = NULL;SQLToExe = (SQLCHAR*)"SELECT * FROM student";ret = SQLExecDirect(hStmt, SQLToExe, SQL_NTS);SQLCHAR type_name[10];SQLColAttribute(hStmt, 1, SQL_DESC_TYPE_NAME, type_name, sizeof(type_name), NULL, NULL);//Function:获取字段的数据类型 cout << "SQL_Type:" << type_name << endl << endl;cout << "①获取结果集---SQLBindCol()+ SQLFetch():" << endl;SQLCHAR SNO[SNO_Len] = { '0' }, SName[SName_Len] = { '0' }, SDepart[SDepart_Len] = { '0' };SQLLEN len = 0, SNameLen = 0, SDepartLen = 0;ret = SQLBindCol(hStmt, 1, SQL_C_DEFAULT, SNO, SNO_Len, &len);ret = SQLBindCol(hStmt, 2, SQL_C_DEFAULT, SName, SName_Len, &SNameLen);ret = SQLBindCol(hStmt, 3, SQL_C_DEFAULT, SDepart, SDepart_Len, &SDepartLen);// 8 通过打印len的值可以知道,此时还没有取回数据到 SNO数组中,执行SQLFetch后 SNO才有了取回的数据DisplayValue(hStmt, SNO, SName, SDepart);cout << endl;cout << "②获取结果集---SQLFetch()+SQLGetData():" << endl;ret = SQLCloseCursor(hStmt);// 关闭游标ret = SQLExecDirect(hStmt, SQLToExe, SQL_NTS); // 重新打开游标DisplayValue(hStmt);cout << endl;cout << "-------" << "SQLPrepare + SQLExecute  -->Insert" << "-------" << endl;SQLCloseCursor(hStmt);// 关闭游标if (ret == SQL_SUCCESS)cout << "Close Cursor Successfully!" << endl;SQLCHAR* SQLToExe3 = (SQLCHAR*)"INSERT INTO student VALUES(?,?,?)";ret = SQLPrepare(hStmt, SQLToExe3, SQL_NTS);SQLCHAR SNOInput[SNO_Len], SNameInput[SName_Len], SDepartInput[SDepart_Len];cout << "Input student message:";cin >> SNOInput >> SNameInput >> SDepartInput;SQLLEN SNOLen = SQL_NTS, SNameLen2 = SQL_NTS, SDepartLen2 = SQL_NTS; SQLBindParameter(hStmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR, 20, 0, SNOInput, 0, &SNOLen);SQLBindParameter(hStmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR, 20, 0, SNameInput, 0, &SNameLen2);SQLBindParameter(hStmt, 3, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR, 20, 0, SDepartInput, 0, &SDepartLen2);ret = SQLExecute(hStmt); // 首次执行失败! SQL_NEED_DATATest(ret, hStmt);SQLLEN rowCount = 0 ;SQLRowCount(hStmt, &rowCount);cout << "the number of rows affected : " << rowCount << endl; // cout << endl;cout << "-------" << "Execute Procedures:" << "-------" << endl; // 返回计算机学院的学生总人数SQLCloseCursor(hStmt);if (ret == SQL_SUCCESS)cout << "Close Cursor Successfully!" << endl;SQLCHAR countDep[SDepart_Len]= "CS";SQLLEN countDepInd =SQL_NTS;SQLLEN retCSNum=100;ret = SQLBindParameter(hStmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR,20,NULL,countDep,0,&countDepInd);if (ret == SQL_SUCCESS)cout << "Bind Successfully!" << endl;ret = SQLBindParameter(hStmt, 2, SQL_PARAM_OUTPUT, SQL_C_LONG, SQL_INTEGER, 0, 0, &retCSNum, sizeof(int),0); // bufferlength 参数被Drivers忽略if (ret == SQL_SUCCESS)cout << "Bind Successfully!" << endl;ret = SQLExecDirect(hStmt, (SQLCHAR*)"call CsNum(?,?)", SQL_NTS);//Test(ret, hStmt);cout << "The Number of students in CS:" << retCSNum << endl;cout << endl;cout << "-------" << "UPDATE" << "-------" << endl;ret = SQLCloseCursor(hStmt);Test(ret, hStmt);SQLLEN wordTask = 100;ret = SQLPrepare(hStmt, (SQLCHAR*)"UPDATE stuGroup SET GTask = ?", SQL_NTS);Test(ret, hStmt);cout<<"Please input the task to be assigned: ";cin >> wordTask;ret = SQLBindParameter(hStmt, 1 , SQL_PARAM_INPUT, SQL_C_LONG, SQL_INTEGER, 0, 0, &wordTask, 0, 0);Test(ret, hStmt);//ret = SQLExecDirect(hStmt, (SQLCHAR*)"UPDATE stuGroup SET GTask = ?",SQL_NTS); // error: set to be null - SOLVED//Test(ret, hStmt);ret = SQLExecute(hStmt);Test(ret, hStmt);cout << endl;cout << "-------" << "DELETE" << "-------" << endl; // 从student表中删除指定学号的学生ret = SQLCloseCursor(hStmt);Test(ret, hStmt);SQLCHAR SnoToDel[SNO_Len];cout << "Please input the SN0 to be deleted: ";cin >> SnoToDel;ret = SQLPrepare(hStmt, (SQLCHAR*)"DELETE FROM STUDENT WHERE SNO = ?", SQL_NTS);Test(ret, hStmt);ret = SQLBindParameter(hStmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_VARCHAR, 20, 0, &SnoToDel, 0, 0);Test(ret, hStmt);ret = SQLExecute(hStmt);Test(ret, hStmt);SQLFreeHandle(SQL_HANDLE_STMT, hStmt); // 释放语句句柄SQLDisconnect(hDbc); // 断开连接SQLFreeHandle(SQL_HANDLE_DBC,hDbc); // 释放连接句柄SQLFreeHandle(SQL_HANDLE_ENV,hEnv);  // 释放环境句柄return 0;
}void DisplayValue(const SQLHSTMT& hStmt,const SQLCHAR* SNO,const SQLCHAR*SName,const SQLCHAR* SDepart)
{SQLRETURN ret = SQL_SUCCESS;while (true) // 遍历该列所有记录{ret = SQLFetch(hStmt);if (ret == SQL_NO_DATA_FOUND) // 读完所有行,游标位于结果集之后{cout << "End of data." << endl;break;}if (ret == SQL_ERROR){cout << "Error!" << endl;break;}cout << "SNO:" << SNO << " SNAME:" << SName << " SDepart:" << SDepart << endl;}
}void DisplayValue(const SQLHSTMT& hStmt)
{SQLRETURN ret;SQLCHAR SNO2[SNO_Len] = { '0' };SQLCHAR SName[SName_Len] = { '0' }, SDepart[SDepart_Len] = { '0' };SQLLEN len2 = 0, SNameLen, SDepartLen;while (true) // 遍历该列所有记录{ret = SQLFetch(hStmt);  //  HY010: (DM) The specified StatementHandle was not in an executed state.//  The function was called without first calling SQLExecDirect, SQLExecute or a catalog function.if (ret == SQL_NO_DATA_FOUND) // 读完所有行,游标位于结果集之后{cout << "End of data." << endl;break;}if (ret == SQL_ERROR){cout << "Error!" << endl;SQLCHAR* state2 = new SQLCHAR[5];SQLError(NULL, NULL, hStmt, state2, NULL, NULL, NULL, NULL);cout << "错误信息:" << state2 << endl; //获取错误信息SQLSTATEbreak;}SQLGetData(hStmt, 1, SQL_C_DEFAULT, SNO2, SNO_Len, &len2);SQLGetData(hStmt, 2, SQL_C_DEFAULT, SName, SName_Len, &SNameLen);SQLGetData(hStmt, 3, SQL_C_DEFAULT, SDepart, SDepart_Len, &SDepartLen);cout << "SNO:" << SNO2 << " SNAME:" << SName << " SDepart:" << SDepart << endl;}
}void Test(SQLRETURN ret,const SQLHSTMT& hStmt)
{static int count = 0;count++;if (ret == SQL_SUCCESS)cout <<count<< "-- SUCCESS!" << endl;else if (ret == SQL_ERROR){SQLCHAR* state = new SQLCHAR[5];SQLError(NULL, NULL, hStmt, state, NULL, NULL, NULL, NULL);cout << "error message:" << state<< endl; //SQLSTATE:42S22 --> Column did not exist.}else if (ret == SQL_INVALID_HANDLE )cout << "error:SQL_INVALID_HANDLE" << endl;else if (ret == SQL_NO_DATA_FOUND)cout << "error:SQL_NO_DATA_FOUND" << endl;else if (ret == SQL_NEED_DATA)cout << "error:SQL_NEED_DATA" << endl;
}

参考

ODBC数据库编程 --CSDN博主 超级大洋葱806
数据库访问接口之ODBC -51CTO博客
ODBC函数 -Hitachi
ODBC函数 -IBM
ODBC开发示例 -华为云
SQLxxx类型对应的C类型/<sqltypes.h>源码 -GitHub

C++ ODBC开发历程相关推荐

  1. 一文了解Gauss数据库:开发历程、OLTPOLAP特点、行式列式存储,及与Oracle和AWS对比

    摘要:华为在IT的底层架构,逐步搭建起自己的基础架构,建立华为生态.我们这次详解华为数据库,并对目前主流的数据库进行对比.只有对比,才能发现不同. 数据库的重要性&华为推出新一代Gauss数据 ...

  2. paip.odbc DSN的存储与读取

    paip.odbc DSN的存储与读取 作者Attilax ,  EMAIL:1466519819@qq.com  来源:attilax的专栏 地址:http://blog.csdn.net/atti ...

  3. ODBC、OLE连接各种数据库的连接字符串

    简介 我们在使用数据库的时候,首先需要打开这个数据库. 我们可以找到集中类型的数据库,每个都使用不同的连接方式. 下面列举了一下主要的数据库的连接字符串 A:使用ODBC方式 1:dBASE连接字符串 ...

  4. 系统dsn oracle,linux平台配置oracle odbc dsn的方法.docx

    linux平台配置oracle odbc dsn的方法.docx LINUX平台配置ORACLEODBCDSN的方法1DSN配置方法ORACLE11编辑配置文件一.OSRHEL5X86(ORACLED ...

  5. cocos creator 安卓原生平台环境_竞技对抗小游戏单挑篮球开发历程 | Cocos技术派第12期...

    本文来自于"Cocos 荣耀讲师"征稿活动第1期,最先发表于 Cocos 中文社区,作者 ID:蟹老板,2017年加入社区,文章作品包括<猎头专家的开发历程>等. Co ...

  6. win8数据源设置mysql_Win8系统ODBC数据源有何重要功能?

    对计算机发展比较有研究的朋友一定会知道ODBC,它是一个比较古老的东西,发展到现在Win8系统上版本已经是3.8了.微软虽然没有对ODBC做很大的更新,但是正因为ODBC是一个比较成熟和古老的规范,因 ...

  7. unix odbc php 连接sqlserver,Ubuntu下通过unixODBC连接MS SqlServer2005

    一.下载相关软件 unixODBC.freetds (1) Linux系统的ODBC      unixODBC-2.2.8.tar.gz ( http://www.unixodbc.org ) (2 ...

  8. MySQL安装ODBC驱动出现126错误

    需求:MySQL导入ODBC文件,需要安装ODBC驱动. 问题:本机的MySQL是5.0版本,刚开始下载的是5.3ODBC,然后出现以下错误: 解决方法:ODBC版本应该与MySQL版本一致,重新安装 ...

  9. ODBC更新记录集提示”记录集为只读“

    创建的ODBC应用程序默认的记录集不具有只读属性,但是再更新记录表时会提示"记录集为只读",这是为什么呢? 今天看书找到了答案: 因为MFC中的数据库类不支持需要连接两个或者多个表 ...

最新文章

  1. markdown编辑器的小建议
  2. 美容院会籍管理,看着简单,其实很复杂
  3. 什么是系统调用?为什么要用系统调用?
  4. matlab datetime时间处理、时间转换
  5. eclipse占用内存过大_MySQL 服务占用cpu 100%,如何排查问题? (MySQL面试第七弹)...
  6. scrapy爬虫系列之三--爬取图片保存到本地
  7. 论文阅读 - Joint Beat and Downbeat Tracking with Recurrent Neural Networks
  8. 再把鼻子涂黑的飞鸽沟通最简单
  9. POCO C++库导游【转】
  10. tomcat 转发 http接口的绝对路径文件
  11. r语言实现sem_统计基础:【18】使用Excel和R语言来实现抽样
  12. Antd Upload 和 Antd Form 结合的踩坑记录
  13. webstorage html5,HTML5-WebStorageAPIs的简述
  14. php 字符串循环替换字符串,php – 替换字符串中的重复字符串
  15. gem install XXX报错
  16. 依赖注入原理,作用,注入方式——Spring IOC/DI(二)
  17. 【转】7本免费的Java电子书和教程
  18. 为什么蓝鸽的听力下载完还是听不了_听力训练方法干货-说说我与雅思听力的那些事情...
  19. w ndows无法完成格式化,win10系统windows无法完成格式化的处理方法
  20. html倒计时面自动跳转,小代码   html 自己网页倒计时跳转

热门文章

  1. 用VB写歌词搜索程序
  2. java jre 配置_Java环境的配置
  3. SwiftUI实战之日期日历年月日星期天干地支十二时辰时分秒
  4. 【强烈推荐】【超强去水印神器】支持给图片视频一键去除/添加水印!
  5. 四川途志传媒:抖音直播带货靠谱吗?
  6. 使用Apollo 注入静态变量
  7. 痛惜:4月17日浙江、18日北京火灾分析
  8. 一句话解释Dubbo服务本地暴露和远程暴露
  9. HTML标签快速查询
  10. 2022-2028全球与中国宠物雨衣市场现状及未来发展趋势