数据库访问接口之ODBC
ODBC API 实现数据库操作的手段是句柄。在ODBC中,使用不同的句柄(HANDLE)来标志环境(environment)、连接(Connection)、语句(statement)、描述符(description)等。句柄是一个应用程序变量,系统用它来存储关于应用程序的上下文信息和应用程序所用到的一些对象。
1.1.1 环境句柄
环境是存取数据的全局性背景,与环境相关的是全局的所有信息。例如:环境状态、当前环境状态诊断、当前在环境上分配的连接句柄、每个环境属性的当前设置。
在实现ODBC(Driver Manager或驱动程序)的代码中,环境句柄标识包含这个信息的结构。环境句柄在ODBC应用程序中经常用来调用函数SQLAllocHandle、SQLEndTran、SQLFreeHandle、SQLGetDiagField和SQLGetDiagReg。
环境句柄是ODBC中整个上下文的句柄,使用ODBC的每个程序从创建环境句柄开始,以释放环境句柄结束。所有其他的句柄都由环境句柄的上下文来管理,环境句柄在每个应用程序中只能创建一个。
1.1.2 连接句柄
一个连接包含一个驱动程序和一个数据源。连接句柄标识每个连接。连接句柄定义使用哪个驱动程序和该驱动程序使用的数据源。在执行ODBC(DriverManager或者驱动程序)的代码中,连接句柄标志一个包含连接信息的结构。比如:连接状态、当前连接层诊断、语句句柄和当前连接上分配的描述符、每个连接属性的当前设置。
如果驱动程序支持多个同时连接,ODBC并不阻止多个同时的连接。因此,在特定的ODBC环境中,多个连接句柄可能指向不同的驱动程序和数据源、相同的驱动程序和不同的数据源甚至是与相同的驱动程序和数据源的多个连接。
与数据源进行连接(SQLConnect、SQLDriverConnect或SQLBrowseConnect)、从数据源上断开(SQLDisconnect)、获取驱动程序及数据源信息(SQLGetInfo)、检索诊断(SQLGetDiagField和SQLGetDiagRec)和执行事务(SQLEndTran)时,都需要使用连接句柄。当设置和获取连接属性(SQLSetConnectAttr)时,也使用它们。
在应用程序中,可在任何适当的时候连接或脱离数据源,但不要轻易的建立或脱离连接。
1.1.3 语句句柄
每个语句由语句句柄标识。一个语句与单个的连接相关,并且在那个连接上可能有多个语句。在实现ODBC(DriverManager或驱动程序)的代码中,语句句柄标识一个包含语句信息的结构,如:语句状态、当前语句层诊断、应用程序变量绑定到语句参数和结果集列的地址、每个语句属性的当前设置。
语句句柄在大多数ODBC函数中使用,它们用于函数绑定参数及结果集列(SQLBindParameter和SQLBindCol)、准备执行语句(SQLPrepare、SQLExecute和SQLExecDirect)、检索元数据(SQLColAttribute和SQLDescribeCol)、取结果(SQLFetch)和检索诊断(SQLGetDiagField和SQLGetDiagRec)。语句句柄使用SQLAllocHandle分配,使用SQLFreeHandle释放。
1.2ODBC数据类型
在使用ODBC开发时一个重要的问题就是数据转换的问题,在ODBC中存在下面的几类数据:
(1)数据库中SQL语言表达数据的类型
(2)ODBC中表达数据的类型
(3)C语言中表达数据的类型
在程序运行过程中数据需要经历两次转换:C语言的数据或结构类型与ODBC的数据类型的转换,ODBC与SQL间数据类型的转换。所以ODBC所定义的数据类型起到了中间桥梁的作用,在ODBC的驱动程序调用自己的DBMS数据库访问接口时就需要对数据类型进行转换。我们所需要关注的是C语言的数据类型和ODBC数据类型间的转换关系。
1.2.1 SQL数据类型
SQL数据类型是在数据源中保存的数据类型。每个数据源都定义了自己的SQL数据类型,此处为SWIFTSQL数据类型。在基本数据源中,每一种数据类型如何映射为ODBC的SQL类型标识符是由驱动程序制定的。下表为对SWIFT SQL数据类型的描述及其与ODBC SQL类型的映射关系。
表1-1SWIFT SQL数据类型
序号
SWIFT
SQL类型
ODBC SQL类型标识
描述
有效范围
1
smallint
SQL_SMALLINT
整型数据
[-32768, 32767]
2
int
SQL_INTEGER
整型数据
[-2147483648, 2147483647]
3
bigint
SQL_BIGINT
整型数据
[-9223372036854775808, 9223372036854775807]
4
double
precision
SQL_DOUBLE
浮点双精度数
[2.2250738585072014e-308,1.7976931348623158e+308]
5
real
SQL_REAL
浮点精度数字
[-1E+308, 1E+308]
6
text
SQL_LONGVARCHAR
可变长度的字符数据
最大长度为1G个字符
7
char(n)
SQL_CHAR
定长字符串
长度为n
8
varchar(n)
SQL_VARCHAR
变长字符串
最大长度为n
9
bytea
SQL_VARBINARY
变长的二进制字串
理论上没有限制,可以达到4个G
10
boolean
SQL_BIT
布尔类型
true/false
11
timestamp
SQL_TYPE_TIMESTAMP
时间戳
‘2013-01-01 00:00:00’
1.2.2 C数据类型
使用C语言开发,必定与ODBC语言之间存在数据转换的问题,因为ODBC所存在的一些数据类型在C语言中是不存在的。ODBC定义了应用程序变量及其相应的数据标识符所使用的C数据类型。它们也用于绑定至结果集列和语句参数的缓冲区。ODBC定义了从每个数据类型到一个C数据类型的默认映射,如下表所示:
表1-2 ODBC 数据类型和C数据类型的映射
C语言数据类型名称
ODBC 数据类型定义
C语言实际类型
SQL_C_SSHORT
SQLSMALLINT
short int
SQL_C_SLONG
SQLINTEGER
long int
SQL_C_FLOAT
SQLREAL
Float
SQL_C_DOUBLE
SQLDOUBLE, SQLFLOAT
Double
SQL_C_BIT
SQLCHAR
unsigned char
SQL_C_CHAR
SQLCHAR *
unsigned char *
SQL_C_SBIGINT
SQLBIGINT
_int64
SQL_C_BINARY
SQLCHAR *
unsigned char *
SQL_C_TYPE_TIMESTAMP
SQL_TIMESTAMP_STRUCT
struct tagTIMESTAMP_STRUCT {
SQLSMALLINT year;
SQLUSMALLINT month;
SQLUSMALLINT day;
SQLUSMALLINT hour;
SQLUSMALLINT minute;
SQLUSMALLINT second;
SQLUINTEGER fraction;
} TIMESTAMP_STRUCT;
所以在ODBC的开发过程中不要使用int,float 之类的C语言的实际类型来定义变量而应该使用ODBC定义的数据类型来定义变量,如:SQLINTEGER,SQLFLOAT。
表1-3 ODBC SQL 与 ODBC C 数据类型的转换
ODBC SQL数据类型
ODBC C 数据类型
SQL_SMALLINT
SQL_C_SSHORT
SQL_INTEGER
SQL_C_SLONG
SQL_BIGINT
SQL_C_SBIGINT
SQL_DOUBLE
SQL_C_DOUBLE
SQL_REAL
SQL_C_FLOAT
SQL_CHAR
SQL_C_CHAR
SQL_VARCHAR
SQL_C_CHAR
SQL_LONGVARCHAR
SQL_C_CHAR
SQL_BIT
SQL_C_BIT
SQL_VARBINARY
SQL_C_BINARY
SQL_TYPE_TIMESTAMP
SQL_C_TYPE_TIMESTAMP
1.3 ODBC诊断
为了在程序开发过程中调试程序,发现程序错误,ODBC API通过两种方式返回有关ODBC API函数执行的信息:返回码和诊断记录。返回码返回函数执行的返回值,说明函数执行成功与否;诊断记录说明函数执行的详细信息。
1.3.1 返回码
每一个ODBC API函数都返回一个返回码,指示函数执行的成功与否。如果函数调用成功,返回码为SQL_SUCCESS或SQL_SUCCESS_WITH_INFO。如果函数调用失败,返回码为SQL_ERROR。获得结果集数据没有更多的数据时,返回码为SQL_NO_DATA。如果程序执行错误,返回码为SQL_INVALID_HANDLE,程序无法执行,而其他的返回码都带有程序执行信息。几个常见的返回码及其说明,如下表所示:
返回码
说明
SQL_SUCCESS
无错误。
SQL_SUCCESS_WITH_INFO
该函数完成,但是对 SQLError 的调用将显示警告。这种状态最常见的情况是:返回的值太长,应用程序提供的缓冲区不够用。
SQL_ERROR
函数未完成,因为出现了错误。调用 SQLError 可获取有关此问题的详细信息。
SQL_INVALID_HANDLE
作为参数传递的环境、连接或语句句柄无效。
如果在释放句柄后再使用该句柄,或者句柄为空值指针,则通常会发生这种情况。
SQL_NO_DATA
没有可用信息。
使用这种状态的最常见情况是在从游标进行读取时,这种状态表示游标中没有更多行。
下面一段代码根据函数SQLFetch执行的返回码,判断函数执行的成功与否,从而据此进行相应的处理:
SQLRETURNretcode;
SQLHSTMThstmt;
while(retcode= SQLFetch(hstmt) != SQL_NO_DATA)
{
if(retcode == SQL_SUCCESS_WITH_INFO)
{
//显示警告信息
}
elseif (retcode == SQL_ERROR)
{
//显示出错信息
break;
}
//函数调用成功
}
1.3.2 诊断记录
每个ODBC API函数都能产生一系列的反应操作信息的诊断记录,这些诊断记录都放在相关联的ODBC句柄中,直到下一个使用同一个句柄的函数调用,该诊断记录一直存在。诊断记录的大小没有限制。
诊断记录有两种:头记录(HeadRecord)和状态记录(Status Record)。头记录是第一版权记录(Record0),后面的记录为状态记录。诊断记录有许多的域组成,这些域在头记录和状态记录中是不同的。
可以用SQLGetDiagField函数获取诊断记录中的特定的域,另外,可以使用SQLGetDiagRec函数获取诊断记录中一些常用的域,如SQLSTATE、原始错误号等。
(1) 头记录
头记录的各个域中包含了一个函数执行的通用信息,无论函数执行成功与否,只要不返回SQL_INVALID_HANDLE,都会生成头记录。
(2) 状态记录
状态记录中的每个域包含了驱动管理器、ODBC驱动程序或数据源返回的特定的错误或警告信息,包括SQLSTATE、原始错误码、诊断信息、列号和行号等。只有函数执行返回SQL_ERROR、SQL_STILL_EXEUTING、SQL_SUCCESS_WITH_INFO、SQL_NEED_DATA或SQL_NO_DATA时,才会生成诊断记录。
(3) 使用SQLGetDiagRec和SQLGetDiagField函数
应用程序可以调用SQLGetDiagRec和SQLGetDiagField函数获取诊断信息。对于给定的句柄,这两个函数返回最近使用句柄的函数的诊断信息。当使用该句柄的函数执行时,句柄记录的原有的诊断信息被覆盖。如果函数执行后产生多个状态记录,程序必须多次调用这两个函数以获取信息。
例如,下面的代码示例用户使用并执行SQL语句。如果返回一段消息,就可以调用SQLGetDiagField函数来取得状态记录并调用SQLGetDiagRec函数来从这些记录中取得SQLSTATE、本地错误代码、诊断消息等信息:
SQLCHARsqlState[6], SQLStmt[100], Msg[SQL_MAX_MESSAGE_LENGTH];
SQLINTRGERNativeError;
SQLSMALLINTI,MsgLen;
SQLRETURNrc1, rc2;
SQLHSTMEHstmt;
//用户输入一条SQL语句
GetSQLStmt(SQLStmt);
//执行SQL语句并返回错误结果
rc1= SQLExecDirect(hstmt, SQLStmt, SQL_NTS);
if((rc1== SQL_SUCCESS_WITH_INFO) || (rc1 == SQL_ERROR))
{
//得到状态记录
i = 1;
while((rc2 =SQLGetDiagRec(SQL_HANDLE_STMT, hstmt, i, SqlState,
&NativeError, Msg,sizeof(Msg), &MsgLen)) != SQL_NO_DATA)
{
DisplayError(SqlState, NativeError, Msg, MsgLen);
i++;
}
}
if((rc1== SQL_SUCCESS) || (rc1 == SQL_SUCCESS_WITH_INFO))
{
//如果必要就处理结果
}
第2章 ODBC API编程模型概述
2.1 ODBC访问模式
查询引擎使用一种客户端/服务器的模式。一次查询引擎会话由下列相关的进程(程序)组成:
1、一个服务器进程,它管理各个节点,接受来自客户端应用与节点的连接,并且代表客户端在节点上执行操作。
2、那些需要执行节点操作的用户的客户端(前端)应用。客户端应用可能本身就是多种多样的:它们可以是一个字符界面的工具,也可以是一个图形界面的应用,或者是一个通过访问查询引擎来显示网页的 web 服务器,或者是一个特殊的管理工具。一些客户端应用是和查询引擎发布一起提供的,但绝大部分是用户开发的。
和典型的客户端/服务器应用(C/S应用)一样,这些客户端和服务器可以在不同的主机上。这时它们通过 TCP/IP 网络连接通讯。查询引擎可以处理来自客户端的多个并发请求。因此,它为每个请求启动一个新的进程。从这个时候开始,客户端和新服务器进程就不再经过最初的进程进行通讯。因此,主服务器总是在运行,等待连接,而客户端及其相关联的服务器进程则是起起停停。当然,用户是肯定看不到这些事情的。我们在这儿谈这些主要是为了完整。
2.2ODBC API编程步骤
通常使用ODBC API开发数据库应用程序需要经过如下步骤:
· 连接到数据源。
· 准备并执行SQL语句。
· 获取结果集。
· 提交事务。
· 断开数据源连接并释放环境句柄。
为了更清楚的阐述ODBC API的调用步骤以及涉及到的重要函数,总结了一个表来说明
ODBC API应用的结构:
2.2.1 连接到数据源
下面的函数用于连接到数据源:
(1)SQLAllocHandle:分配环境、连接、语句或者描述符句柄。
(2)SQLConnect:建立与驱动程序或者数据源的连接。访问数据源的连接句柄包含了包括状态、事务申明和错误信息的所有连接信息。
(3)SQLDriverConnect:与SQLConnect相似,用来连接到驱动程序或者数据源。但它比SQLConnect支持数据源更多的连接信息,它提 供了一个对话框来提示用户设置所有的连接信息以及系统信息表没有定义的数据源。
(4)SQLBrowseConnect:支持一种交互方法来检索或者列出连接数据源所需要的属性和属性值。每次调用函数可以获取一个连接属性字符串,当检索完所有的属性值,就建立起与数据源的连接,并且返回完整的连接字符串,否则提示缺少的连接属性信息,用户根据此信息重新输入连接属性值再次调用此函数进行连接。
#include<sql.h>
#include<sqltypes.h>
#include<sqlext.h>
SQLHENV henv; /*环境句柄*/
SQLHDBC hdbc; /*连接句柄*/
RETCODE retcode; /*返回码 */
voidmain(void)
{
/* 分配一个环境句柄 */
SQLAllocHandle(SQL_HANDLE_ENV,NULL, &henv);
/* 设置环境句柄的ODBC版本 */
SQLSetEnvAttr(henv,SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC3,
SQL_IS_INTEGER);
/* 分配一个连接句柄 */
SQLAllocHandle(SQL_HANDLE_DBC,henv, &hdbc);
/* 连接数据源 */
retcode= SQLConnect(hdbc, (UCHAR)pszSourceName, SQL_NTS,
(UCHAR)pszUserId, wLengthUID,
(UCHAR)pszPassword, wLengthPSW );
if(retcode== SQL_SUCCESS) // 数据源连接成功
{
// 执行其它操作
…………
}
/* 释放连接句柄*/
SQLFreeHandle(SQL_HANDLE_DBC,hdbc);
/* 释放环境句柄 */
SQLFreeHandle(SQL_HANDLE_ENV,henv);
}
2.2.2 准备并执行SQL语句
调用函数SQLAllocHandle分配语句句柄,通过语句句柄应用程序可以执行SQL语句进行相关的SQL操作。调用函数SQLPrepare对SQL语句和操作进行准备,然后调用SQLExecute执行SQL语句,实现相关的SQL操作。应用程序也可以调用函数SQLExecDirect直接执行SQL语句进行相关的SQL操作。
示例如下:
SQLHSTMT hstmt;
RETCODE retcode;
/* 分配一个语句句柄 */
retcode= SQLAllocHandle (SQL_HANDLE_ENV, hdbc, &hstmt );
if(retcode== SQL_SUCCESS) // 连接句柄创建成功
{
// 执行其它操作
…………
}
/*执行一个查询sql句柄 */
LPCSTRpszSQL;
strcpy(pszSQL,“SELECT * FROM EMPLOYEES”);
retcode=SQLExecDirect(hstmt, (unsigned char *)pszSQL, SQL_NTS );
if(retcode== SQL_SUCCESS) // SQL语句执行成功
{
// 执行其它操作
…………
}
调用SQLPrepare和SQLExecute函数的语法示例如下:
LPCSTRpszSQL;
strcpy(pszSQL,“SELECT * FROM EMPLOYEES”);
retcode=SQLPrepare( hstmt, (unsigned char *)pszSQL, SQL_NTS );
if(retcode== SQL_SUCCESS) // SQL语句准备成功
{
// 执行其它操作
…………
}
retcode= SQLExecute (hstmt, (unsigned char *)pszSQL, SQL_NTS );
if(retcode== SQL_SUCCESS) // SQL语句执行成功
{
// 执行其它操作
…………
}
2.2.3 获取结果集
应用程序首先调用SQLNumResultCols函数,获知每个记录里有多少列,调用SQLDescribeCol函数取得每列的属性,然后调用SQLBindCol函数将列数据绑定到指定的变量里,最后调用SQLFetch函数或者SQLGetData函数获取数据。
对于其它的SQL语句,应用程序重复这个过程。这个过程代码如下:
retcode=SQLNumResultCols( m_hstmt, &wColumnCount );
if(retcode!= SQL_SUCCESS ) // 列举结果集列的个数不成功
{
// 释放操作
…………
return;
}
LPSTR pszName;
retcode=SQLDescribeCol( m_hstmt,
wColumnIndex, // 列的索引
pszName, // 列的名称
256, // 存放列名称的缓冲区大小
& nRealLength, // 实际得到列名称的长度
&wColumnType, // 列的数据类型
&dwPrecision, // 精度
&wScale, // 小数点位数
&wNullable ); // 是否允许空值
if(retcode!= SQL_SUCCESS ) // 执行不成功
{
// 释放操作
…………
return;
}
retcode= SQLBindCol( hstmt,
uCounter, // 列索引
wColumnType, // 列数据类型
FieldValue, // 绑定的变量
dwBufferSize, // 变量内存大小
&BytesInBuffer); // 存放将来返回数据的大小的变量
if(retcode!= SQL_SUCCESS ) // 执行不成功
{
// 释放操作
…………
return;
}
SQLFetch(hstmt);
// 此后可以从绑定的变量里读取列的值。
…………
下面是通过SQLFetch和SQLGetData得到记录集的例子:
//假设 SQL = SELECT CUSTID,NAME, PHONE FROM CUSTOMERS
SQLINTEGER sCustID
SQLCHAR szName[50], szPhone[50];
SQLINTEGERcbName, cbAge, cbBirthday;//用来保存得到的数据的长度
while(TRUE) {//循环得到所有行
retcode =SQLFetch(hstmt);//移动光标
if (retcode == SQL_ERROR || retcode ==SQL_SUCCESS_WITH_INFO) {
printf(“error SQLFetch\n”);
}
if(retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) {
SQLGetData(hstmt, 1, SQL_C_ULONG,&sCustID, 0, &cbCustID);
//此处并没有指明BufferLength参数的值,是因为数据类型是定长的LONG型
SQLGetData(hstmt, 2, SQL_C_CHAR, szName, 50,&cbName);
SQLGetData(hstmt, 3, SQL_C_CHAR,szPhone, 50,&cbPhone);
printf(out, "%5d %s%s", sCustID, szName, szPhone);
}else {
break;
}
}
2.2.4 提交事务
当所有的SQL语句都被执行并接收了所有的数据以后,应用程序需要调用SQLEndTran()函数提交或者回退事务。如果提交方式为手工(应用程序设置)方式,则需要应用程序执行这个语句以提交或者回退事务,如果是自动方式,当SQL语句执行后,该命令自动执行。
调用SQLEndTran函数的语法如下:
SQLEndTran(SQL_HANDLE_DBC,hdbc, SQL_COMMIT); //提交事务
SQLEndTran(SQL_HANDLE_DBC,hdbc, SQL_ROLLBACK); //回滚事务
2.2.5 断开数据源连接并释放环境句柄
当应用程序使用完ODBC以后,需要使用SQLFreeHandle()函数释放所有语句句柄、连接句柄、环境句柄。这里需要注意操作的顺序,应该是先释放所有语句句柄,调用SQLDisconnect()函数解除与数据源的连接,然后释放所有连接句柄,最后释放环境句柄,使应用程序同ODBC管理器的连接彻底解除。
第3章 连接到数据源
连接到数据源包括初始化环境句柄、初始化连接句柄以及连接数据源等几个步骤。
3.1 初始化环境句柄
3.1.1 分配环境句柄
SQLAllocHandle函数的定义如下:
SQLRETURNSQLAllocHandle(
SQLSMALLINT HandleType,
SQLHANDLE InputHandle,
SQLHANDLE * OutputHandlePtr
);
第一个参数HandleType的取值可以为:SQL_HANDLE_ENV:申请环境句柄。SQL_HANDLE_DBC:申请数据库连接句柄。SQL_HANDLE_STMT:申请SQL语句句柄,每次执行SQL语句都申请语句句柄,并且在执行完成后释放。
第二个参数为输入句柄,第三个参数为输出句柄,也就是你在第一参数指定的需要申请的句柄。首先声明一个SQLHENV类型的变量,然后调用SQLAllocHandle函数,向其中传递分配的SQLHENV类型的变量地址和SQL_HANDLE_ENV选项。
分配环境句柄示例代码如下:
SQLHENV henv; /* 环境句柄 */
SQLHDBC hdbc; /* 连接句柄 */
RETCODE retcode; /* 返回码*/
retcode= SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv);
if((retcode!= SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO))
{
AfxMessageBox(“分配环境句柄失败”);
return FALSE;
}
执行该调用语句后,驱动程序分配一个结构,该结构中存放环境变量,然后返回对应于该环境的环境句柄。
一个应用程序对应于一个环境,但是同一个环境可以用于不同数据源的连接,可以使用多个线程,应用程序完成数据访问任务,应调用函数SQLFreeHandle函数释放当前分配的环境。
/* 释放环境句柄 */
SQLFreeHandle(SQL_HANDLE_ENV,henv);
3.1.2 设置环境属性
应用程序在完成环境的分配后,接着调用函数SQLSetEnvAttr设置环境属性,注册ODBC的版本号,说明应用程序所遵从的标准是ODBC 2.x还是ODBC 3.x,相关代码如下所示。使用不同的版本,相同的参数作用会不相同,具体的说明参见MSDN。
SQLSetEnvAttr的函数定义如下:
SQLRETURNSQLSetEnvAttr(
SQLHENV EnvironmentHandle,
SQLINTEGER Attribute,
SQLPOINTER ValuePtr,
SQLINTEGER StringLength
);
ConnectionHandle:提供环境句柄。
Attribute:指定需要设置的属性类型,这里设置为SQL_ATTR_ODBC_VERSION,其他属性类型请查看MSDN。
ValuePtr:提供参数值。
StringLength:指定参数的长度,当参数为字符串时设置为字符串长度或者为SQL_NTS,而对于普通的变量如SQLINTEGER,SQLFLOAT等设置为0就可以了。
设置ODBC版本的环境属性示例代码如下:
retcode=SQLSetEnvAttr(henv,SQL_ATTR_ODBC_VERSION,
(SQLPOINTER)SQL_OV_ODBC3,0);
if((retcode!= SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO))
{
ReportError(m_henv, SQL_HANDLE_ENV, “设置ODBC版本号时失败”);
return FALSE;
}
3.2 初始化连接句柄
3.2.1 分配连接句柄
分配环境句柄之后,在建立至数据源的连接之前,必须分配一个连接句柄,每一个到数据源的连接对应于一个连接句柄。
首先,程序定义一个SQLHDBC类型的变量,用于存放连接句柄,然后调用SQLAllocHandle函数分配句柄。
分配连接句柄示例代码如下:
SQLHENV henv; /* 环境句柄 */
SQLHDBC hdbc; /* 连接句柄 */
RETCODE retcode; /* 返回码 */
retcode= SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
if((retcode!= SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO))
{
ReportError(m_henv, SQL_HANDLE_ENV, “分配连接句柄失败!”);
return FALSE;
}
3.2.2 设置连接属性
连接属性表示了一个连接的特性,如登录等待时间等。所有的连接属性都有默认值,也可以通过调用函数SQLSetConnectAttr设置。
SQLSetConnectAttr的函数定义如下:
SQLRETURNSQLSetConnectAttr(
SQLHDBC ConnectionHandle,
SQLINTEGER Attribute,
SQLPOINTER ValuePtr,
SQLINTEGER StringLength);
ConnectionHandle:提供DBC连接句柄。
Attribute:指定需要设置的属性类型,在这里设置为值SQL_ATTR_AUTOCOMMIT,需要强制转换位指针类型。其他属性类型请查看MSDN。
ValuePtr:提供参数值。
StringLength:指定参数的长度,当参数为字符串时设置为字符串长度或者为SQL_NTS,而对于普通的变量如SQLINTEGER,SQLFLOAT等设置为0就可以了。
示例代码如下:
retcode= SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
if(retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) {
//设置登录等待时间为5秒
SQLSetConnectAttr(hdbc,SQL_LOGIN_TIMEOUT, (SQLPOINTER)5, 0);
// 连接到数据源
retcode= SQLConnect(hdbc, (SQLCHAR*) "SQL SWIFT", SQL_NTS,
(SQLCHAR*)NULL,0, NULL, 0);
}
3.3 连接数据源
完成连接属性的设置之后,就可以建立到数据源的连接了。对于不同的程序和用户接口,可以用不同的函数建立连接:SQLConnect、SQLDriverConnect、SQLBrowseConnect。
3.3.1 使用SQLConnect()函数
该函数提供了最为直接的程序控制方式,只要提供数据源名称、用户ID和口令,就可以进行连接了。其定义如下:
SQLRETURNSQLConnect(
SQLHDBC ConnectionHandle, //连接句柄
SQLCHAR *ServerName, //数据源名称
SQLSMALLINT NameLength1, //数据源名称长度
SQLCHAR *UserName, //用户ID
SQLSMALLINT NameLength2, //用户ID长度
SQLCHAR *Authentication, //用户口令
SQLSMALLINT NameLength3 //用户口令长度
);
使用SQLConnect函数连接数据源示例如下:
retcode=SQLConnect(hdbc, (SQLCHAR *)cpServerName, SQL_NTS,
(SQLCHAR*)cpUserName, SQL_NTS,(SQLCHAR *)cpPassword, SQL_NTS);
if((retcode!=SQL_SUCCESS) && (retcode!= SQL_SUCCESS_WITH_INFO))
{
ReportError(hdbc, SQL_HANDLE_DBC, “连接数据库失败!”);
return FALSE;
}
3.3.2 使用SQLDriverConnect()函数
该函数用一个连接字符串建立到数据源的连接,他提供比SQLConnect函数的参数更多的信息,可以让用户输入必要的连接信息,使用系统中还没有定义的数据源。
如果连接建立,该函数会返回完整的连接字符串,应用程序可以使用连接字符串建立额外的连接,其定义如下:
SQLRETURNSQLDriverConnect(
SQLHDBC ConnectionHandle; //连接句柄
SQLHWND WindowHandle; //窗口句柄,程序可以用父窗口的句柄
//或用NULL指针
SQLCHAR * InConnectionString; //一个指向连接字符串的指针
SQLSMALLINT StringLength1, //连接字符串长度
SQLCHAR * OutConnectionString; //一个指向连接字符串的指针
SQLSMALLINT BufferLength; //存放连接字符串的缓冲区的长度
SQLSMALLINT * StringLength2Ptr; //返回的连接字符串中的字符数
SQLSMALLINT DriverCompletion//额外的连接信息,可能的取值:
//SQL_DRIVER_PROMPT、
//SQL_DRIVER_COMPLETE、
//SQL_DRIVER_COMPLETE_REQUIRED、
//SQL_DRIVER_NOPROMPT
);
示例代码如下:
//分配连接句柄
if(retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) {
retcode = SQLAllocHandle(SQL_HANDLE_DBC,henv, &hdbc);
// 设置登陆等待5秒
if(retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) {
SQLSetConnectAttr(hdbc,SQL_LOGIN_TIMEOUT, (SQLPOINTER)5, 0);
retcode = SQLDriverConnect(hdbc,desktopHandle,
(SQLCHAR*)"driver=SQL SWIFT",_countof("driver=SQLSWIFT "),
OutConnStr,255,&OutConnStrLen,SQL_DRIVER_PROMPT );
if (retcode == SQL_SUCCESS || retcode ==SQL_SUCCESS_WITH_INFO){
//分配语句句柄
retcode = SQLAllocHandle(SQL_HANDLE_STMT,hdbc, &hstmt);
// 操作数据
}
3.3.3 使用SQLBrowseConnect()函数
函数SQLBrowseConnect支持以一种迭代的方式获取到数据源的连接,直到最后建立连接。它基于客户机/服务器体系结构。
一般,只提供部分连接信息,如果足以建立到数据源的连接,则成功建立连接,否则返回SQL_NEED_DATA,并在OutConnectionString参数中返回所需要的信息。其定义如下:
SQLRETURNSQLBrowseConnect(
SQLHDBC ConnectionHandle, //连接句柄
SQLCHAR *InConnectionString, //指向输入字符串的指针
SQLSMALLINT StringLength1, //输入字符串的指针长度
SQLCHAR *OutConnectionString, //指向输出字符串的指针
SQLSMALLINT BufferLength, //存放输出字符串的缓冲区的长度
SQLSMALLINT *StringLength2Ptr //实际返回的字符串的长度
);
使用SQLBrowseConnect函数连接数据源的示例代码如下:
retcode= SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
if(retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) {
strcpy_s((char*)szConnStrIn,_countof(szConnStrIn), "DSN=Sales");
do {
retcode= SQLBrowseConnect(hdbc, szConnStrIn,SQL_NTS,szConnStrOut,
BRWS_LEN,&cbConnStrOut);
if (retcode == SQL_NEED_DATA)
GetUserInput(szConnStrOut,szConnStrIn);
}while (retcode == SQL_NEED_DATA);
if(retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO){
//分配语句句柄
retcode= SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);
if (retcode ==SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO)
//连接成功后处理数据
SQLFreeHandle(SQL_HANDLE_STMT,hstmt);
SQLDisconnect(hdbc);
}
}
第4章 准备并执行SQL语句
4.1 构造SQL语句
可以通过3种方式构造SQL语句:
l 在程序开发阶段确定:具有易于实现且可在程序编码时进行测试的优点。
l 在运行时确定:提供了极大灵活性,但给程序带来了困难,却需要更多的处理时间。
l 由用户输入SQL语句:极大的增强了程序的功能,但是程序必须提供有好的程序界面,且对用户输入的语句执行一定程序的语法检查,能够报告用户错误。
4.2 执行SQL语句
4.2.1 分配语句句柄
语句句柄为ODBC驱动程序管理器提供数据结构,并跟踪SQL语句的执行机器返回的结果,语句句柄是通过调用SQLAllocHandle函数分配的。下面的代码声明了一个存放语句句柄的变量hstmt,在函数中提供该变量的地址指针、所使用的连接句柄hdbc以及选项SQL_HANDLE_STMT:
retcode=SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);
if((retcode!=SQL_SUCCESS) && (retcode!= SQL_SUCCESS_WITH_INFO))
{
ReportError(hdbc, SQL_HANDLE_DBC, “分配语句句柄失败,不能执行”);
return FALSE;
}
当一个语句句柄使用完成后,调用函数SQLFreeHandle释放句柄。
/* 释放语句句柄*/
retcode=SQLFreeHandle(SQL_HANDLE_ENV, hstmt);
4.2.2 使用SQLExecDirect()函数
使用SQLExecDirect函数直接执行SQL语句,对于只执行一次的SQL语句来说,该函数是执行最快的方法,其定义如下:
SQLRETURNSQLExecDirect(SQLHSTMT StatementHandle, //语句句柄
SQLCHAR*StatementText, //要执行的SQL语句
SQLINTEGER TextLength //SQL语句的长度
);
使用该函数执行SQL语句的代码:
retcode= SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);
retcode= SQLExecDirect(hstmt, (SQLCHAR *) "SELECT CustomerID, ContactName,
PhoneFROM CUSTOMERS ORDER BY 2, 1, 3", SQL_NTS);
4.2.3 使用SQLPrepare()和SQLExecute()函数
对于需要多次执行的SQL语句来说,除了使用SQLExecDirect函数之外,也可以在执行SQL语句之前,先调用函数SQLPrepare准备SQL语句的执行。对于使用参数的语句,这可大大提高程序的执行速度。而函数SQLExecute用来执行一个准备好的语句,当语句中有参数时,用当前绑定的参数变量的值。其定义如下:
SQLRETURNSQLPrepare(SQLHSTMT StatementHandle, //语句句柄
SQLCHAR*StatementText, //要执行的SQL语句
SQLINTEGERTextLength //SQL语句的长度
);
SQLRETURNSQLExecute(SQLHSTMT StatementHandle); //语句句柄
示例代码1:
retcode= SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);
retcode= SQLPrepare(hstmt, (SQLCHAR*)"{call SQLBindParameter(?)}",
SQL_NTS);
retcode= SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR,
SQL_CHAR,50, 0, szQuote, 0, &cbValue);
retcode= SQLGetStmtAttr(hstmt, SQL_ATTR_IMP_PARAM_DESC, &hIpd, 0, 0);
retcode= SQLSetDescField(hIpd, 1, SQL_DESC_NAME, "@quote", SQL_NTS);
retcode= SQLExecute(hstmt);
示例代码2:
retcode= SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);
retcode= SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR,
SQL_CHAR,EMPLOYEE_ID_LEN, 0, szEmployeeID, 0, &cbEmployeeID);
retcode= SQLBindParameter(hstmt, 2, SQL_PARAM_INPUT, SQL_C_SSHORT,
SQL_INTEGER,0, 0, &sCustID, 0, &cbCustID);
retcode= SQLBindParameter(hstmt, 3, SQL_PARAM_INPUT, SQL_C_TYPE_DATE,
SQL_TIMESTAMP,sizeof(dsOrderDate), 0, &dsOrderDate,
0,&cbOrderDate);
retcode= SQLPrepare(hstmt, (SQLCHAR*)"INSERT INTO Orders(CustomerID,
EmployeeID,OrderDate) VALUES (?, ?, ?)", SQL_NTS);
strcpy_s((char*)szEmployeeID,_countof(szEmployeeID), "BERGS");
sCustID= 5;
dsOrderDate.year= 2006;
dsOrderDate.month= 3;
dsOrderDate.day= 17;
retcode= SQLExecute(hstmt);
4.2.4 使用参数
使用参数可以使一条SQL语句多次执行,得到不同的结果,如下面的一条SQL语句:
INSERTINTO EMP(职员号,职工名称,工作) VALUES(?,?,?)
该语句将向表emp中加入一条记录,通过参数替换(将句中问号出现的地方以相应的参数值替换),可以在程序运行的时候动态的添加多条记录。
SQL语句执行时,参数标识符(“?”)绑定程序中的变量,通过参数就可以在程序执行时动态的将值传入SQL语句。
函数SQLBindParameter负责为参数定义变量,将一段SQL语句中的一个参数标识符(“?”)绑定在一起,实现参数值的传递,其定义如下:
SQLRETURNSQLBindParameter(
SQLHSTMTStatementHandle, //语句句柄
SQLUSMALLINTParameterNumber, //绑定的参数在SQL语句中的序号,在SQL中,
//所有参数从左到右依次编号(从1开始)。
//SQL语句执行之前,应该为每个参数调用函数
//SQLBindParameter绑定到某个程序变量。
SQLSMALLINTInputOutputType, //参数类型,可为SQL_PARA_INPUT、
//SQL_PARAM_INPUT_OUTPUT、
//SQL_PARAM_OUTPUT
SQLSMALLINTValueType, //参数的C数据类型
SQLSMALLINTParameterType, //参数的SQL数据类型
SQLUINTEGERColumnSize, //参数的大小
SQLSMALLINTDecimalDigits, //参数的精度
SQLPOINTERParameterValuePtr, //指向程序中存放参数值的缓冲区的指针
SQLINTEGERBufferLength, //程序中存放参数值的缓冲区的字节数
SQLINTEGER*StrLen_or_IndPtr //指向存放参数ParameterValuePtr的缓冲区指针
);
下面是一个实际应用的例子
#include<sqltypes.h>
#include<sqlext.h>
#defineEMPLOYEE_ID_LEN 10
SQLHENVhenv = NULL;
SQLHDBChdbc = NULL;
SQLRETURNretcode;
SQLHSTMThstmt = NULL;
SQLSMALLINTsCustID;
SQLCHARszEmployeeID[EMPLOYEE_ID_LEN];
SQL_DATE_STRUCTdsOrderDate;
SQLINTEGERcbCustID = 0, cbOrderDate = 0, cbEmployeeID = SQL_NTS;
intmain() {
retcode =SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv);
retcode =SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION,
(SQLPOINTER*)SQL_OVODBC3,0);
retcode= SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
retcode = SQLSetConnectAttr(hdbc,SQL_LOGIN_TIMEOUT, (SQLPOINTER)5, 0);
retcode = SQLConnect(hdbc, (SQLCHAR*)"Northwind", SQL_NTS,
(SQLCHAR*) NULL, 0, NULL,0);
retcode = SQLAllocHandle(SQL_HANDLE_STMT,hdbc, &hstmt);
retcode = SQLBindParameter(hstmt, 1,SQL_PARAM_INPUT, SQL_C_CHAR,
SQL_CHAR,EMPLOYEE_ID_LEN, 0, szEmployeeID,0, &cbEmployeeID);
retcode = SQLBindParameter(hstmt, 2,SQL_PARAM_INPUT, SQL_C_SSHORT,
SQL_INTEGER,0, 0, &sCustID, 0, &cbCustID);
retcode = SQLBindParameter(hstmt, 3,SQL_PARAM_INPUT,
SQL_C_TYPE_DATE,SQL_TIMESTAMP,sizeof(dsOrderDate), 0,&dsOrderDate, 0,
&cbOrderDate);
retcode = SQLPrepare(hstmt,(SQLCHAR*)"INSERT INTO Orders(CustomerID,
EmployeeID,OrderDate) VALUES (?, ?, ?)", SQL_NTS);
strcpy_s((char*)szEmployeeID,_countof(szEmployeeID), "BERGS");
sCustID = 5;
dsOrderDate.year = 2006;
dsOrderDate.month = 3;
dsOrderDate.day = 17;
retcode = SQLExecute(hstmt);
}
第5章 获取结果集
查询结果集是数据源上满足一定条件的数据记录的集合,在概念上,可以看成是一个临时存放返回结果的表。结果集可以是空。在用SQLExecDirect等函数执行SQL语句后,就可以从数据源中获取结果集。
5.1 使用SQLNumResultCols()函数
调用SQLNumResultCols函数,获知每个记录里有多少列,函数的定义如下:
SQLRETURNSQLNumResultCols(
SQLHSTMT StatementHandle, // 语句句柄
SQLSMALLINT * ColumnCountPtr //返回列数
);
5.2 使用SQLDescribeCol()函数
调用SQLDescribeCol函数取得每列的属性,函数定义如下:
SQLRETURNSQLDescribeCol(
SQLHSTMT StatementHandle,
SQLSMALLINT ColumnNumber,
SQLCHAR * ColumnName,
SQLSMALLINT BufferLength,
SQLSMALLINT * NameLengthPtr,
SQLSMALLINT * DataTypePtr,
SQLINTEGER * ColumnSizePtr,
SQLSMALLINT * DecimalDigitsPtr,
SQLSMALLINT * NullablePtr
);
StatementHandle:语句句柄。
ColumnNumber:需要得到的列的序号,从1开始计算。
ColumnName:得到列的名称。
BufferLength:指明ColumnName参数的最大长度。
NameLengthPtr:返回列名称的长度。
DataTypePtr:得到列的ODBC数据类型。
ColumnSizePtr:得到列的长度。
DecimalDigitsPtr:当该列为数字类型时返回小数点后数据的位数。
NullablePtr:指明该列是否允许为空值。
5.3 绑定列
从数据源取回的数据存放在应用程序定义的变量中,因此,必须首先分配与结果集中字段相对应的变量,然后通过函数SQLBindCol将记录字段同程序变量绑定在一起。对于长记录字段,可以通过调用函数SQLGetData直接取回数据。
绑定字段可以根据自己的需要全部绑定,也可以绑定其中的某几个字段。
通过调用函数SQLBindCol将其变量地址赋值为NULL,可以结束对一个记录字段的绑定;通过调用函数SQLFreeStmt,将其中的选项设为SQL_UNBIND,或者是直接释放句柄,都会结束所有记录字段的绑定。函数SQLBindCol的定义如下:
SQLRETURNSQLBindCol(
SQLHSTMT StatementHandle, //语句句柄
SQLSMALLINTColumnNumber, //标识要绑定的列号。数据列号从0开始升序排
//列,其中第0列用作书签,则列号从1开始
SQLSMALLINT TargetType, //数据类型
SQLPOINTER TargetValuePtr, //绑定到数据字段的缓冲区的地址
SQLINTEGER BufferLength, //缓冲区长度
SQLINTEGER *StrLen_or_IndPtr //指向绑定数据列使用的长度的指针
);
示例代码如下:
retcode= SQLExecDirect(hstmt, (SQLCHAR *) "SELECT CustomerID, ContactName,
PhoneFROM CUSTOMERS ORDER BY 2, 1, 3", SQL_NTS);
if(retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO) {
// 绑定1、2、3列
retcode= SQLBindCol(hstmt, 1, SQL_C_CHAR, &sCustID, 100, &cbCustID);
retcode= SQLBindCol(hstmt, 2, SQL_C_CHAR, szName, NAME_LEN, &cbName);
retcode= SQLBindCol(hstmt, 3, SQL_C_CHAR, szPhone, PHONE_LEN, &cbPhone);
// 获得并打印每一列的信息,如果有错误打印错误并推出
……
}
5.4 使用SQLFetch()函数
函数SQLFetch用于将记录集中的下一行变为当前行,并把所有捆绑过的数据字段的数据拷贝到相应的缓冲区,其定义如下:
SQLRETURNSQLFetch(SQLHSTMT StatementHandle); //StatementHandle:语句句柄
使用SQLFetch函数的示例代码如下:
//获得并打印每一列的信息,如果有错误打印错误并推出
for(i ; ; i++) {
retcode =SQLFetch(hstmt);
if (retcode ==SQL_ERROR || retcode == SQL_SUCCESS_WITH_INFO)
show_error();
if (retcode ==SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO)
wprintf(L"%d: %S%S %S\n", i + 1, sCustID, szName, szPhone);
else
break;
}
总结一下,要获取结果集一般先用函数SQLExecDirect执行SQL语句,接着用函数SQLBindCol绑定列,然后用函数SQLFetch遍历记录,获取记录的示例代码如下:
#defineNAME_LEN 50
#definePHONE_LEN 10
SQLCHARszName[NAME_LEN], szPhone[PHONE_LEN];
SQLINTEGERsCustID, cbName, cbCustID, cbPhone;
SQLHSTMThstmt;
SQLRETURNretcode;
//执行SQL语句
retcode= SQLExecDirect(hstmt,
“SELECTCUSTID, NAME, PHONE FROM CUSTOMERS ORDER BY 2,1,3”,
SQL_NTS);
if((retcode!= SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO)){
//执行成功
//绑定1,2,3列
SQLBindCol(hstmt, 1, SQL_C_ULONG,&sCustID, 0, &cbCustID);
SQLBindCol(hstmt, 2, SQL_C_CHAR, szName,NAME_LEN, &cbName);
SQLBindCol(hstmt, 3, SQL_C_CHAR, szPhone,PHONE_LEN, &cbPhone);
//遍历记录集,输出结果
while(TRUE){
retcode = SQLFetch(hstmt);
if(retcode == SQL_ERROR || retcode == SQL_SUCCESS_WITH_INFO){
//出错,输出错误,返回
show_error();
}
if(retcode== SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO){
fprintf(out, “%-*s%-5d %*s”,NAME_LEN-1, szName, sCustID,
PHONE_LEN-1,szPhone);
}else{
break;
}
}
}
5.5 使用SQLGetData()函数
SQLGetData可用于检索结果集数据,而无需绑定列值。可以对同一列连续调用SQLGetData函数以从text、ntext或image数据类型的列中检索大量数据。
SQLGetData函数的定义如下:
SQLRETURNSQLGetData(
SQLHSTMT StatementHandlem,// 语句句柄
SQLUSMALLINT ColumnNumber, // 列号,以1开始
SQLSMALLINT TargetType, // 数据缓冲区(TargetValuePtr)的C语言类
SQLPOINTER TargetValuePtr, // 数据缓冲区
SQLINTEGER BufferLength, // 数据缓冲区(TargetValuePtr)的长度
SQLINTEGER* StrLen_or_IndPtr // 返回当前字段得到的字节长度
);
使用SQLGetData函数的示例代码如下:
#defineNAME_LEN 50
#definePHONE_LEN 50
SQLCHAR szName[NAME_LEN], szPhone[PHONE_LEN];
SQLINTEGER sCustID, cbName, cbAge, cbBirthday;
SQLRETURN retcode;
SQLHSTMT hstmt;
retcode= SQLExecDirect(hstmt,
"SELECTCUSTID, NAME, PHONE FROM CUSTOMERS ORDER BY 2, 1, 3"
SQL_NTS);
if(retcode == SQL_SUCCESS) {
while(TRUE) {
retcode= SQLFetch(hstmt);
if (retcode == SQL_ERROR) {
show_error();
}
if (retcode == SQL_SUCCESS || retcode ==SQL_SUCCESS_WITH_INFO){
/*直接得到1,2,3列数据*/
SQLGetData(hstmt, 1, SQL_C_LONG,&sCustID, 0, &cbCustID);
SQLGetData(hstmt, 2, SQL_C_CHAR, szName,NAME_LEN,
&cbName);
SQLGetData(hstmt, 3, SQL_C_CHAR, szPhone,PHONE_LEN,
&cbPhone);
/* 打印每一列的数据 */
fprintf(out, "%-5d %-*s %*s",sCustID, NAME_LEN-1, szName,
PHONE_LEN-1, szPhone);
}else{
break;
}
}
}
5.6游标
应用程序获取数据是通过游标(Cursor)来实现的,执行语句和操纵结果集的 ODBC 函数使用游标执行它们的任务。应用程序每次执行SQLExecute 或 SQLExecDirect 函数时,都会隐式打开游标。
如果游标在结果集中只正向移动而不更新结果集,对于这种应用情况,游标行为相对比较简单。缺省情况下,ODBC 应用程序会请求此行为。ODBC 定义一个只读的单向游标。
单向游标只能向前移动,要返回记录集的开始位置,必须先关闭游标,再打开游标,它对与只需要浏览一次的应用非常有用,而且效率很高。有关单向游标的简单示例,请参见数据检索。
SQLRETURNSQLFetch(SQLHSTMT);
作用:在绑定好结果列后就可以获取结果集了,判断是否读取到结尾的方法有判断返回结果是否是正确值,或是否等于SQL_NO_DATA。
5.7 数据检索
若要从数据库检索行,可使用SQLExecute或 SQLExecDirect执行 SELECT 语句。这将为该语句打开一个游标。然后,使用 SQLFetch读取行,使用SQLBindCol 或 SQLGetData读取值。如果使用 SQLBindCol,每次读取时自动检索值。如果使用SQLGetData,必须在每次读取后为每一列调用它。最后使用 SQLFreeHandle 释放语句后,它将关闭游标。
为了使示例更容易读,这里省略了错误检查操作。
SQLINTEGERcbDeptID = 0, cbDeptName = SQL_NTS, cbManagerID = 0;
SQLCHARdeptName[ DEPT_NAME_LEN + 1 ];
SQLSMALLINTdeptID, managerID;
SQLHENV env;
SQLHDBC dbc;
SQLHSTMTstmt;
SQLRETURNretcode;
SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &env );
SQLSetEnvAttr(env, SQL_ATTR_ODBC_VERSION,
(SQLPOINTER)SQL_OV_ODBC3,0);
SQLAllocHandle(SQL_HANDLE_DBC, env, &dbc );
SQLConnect(dbc,(SQLCHAR*) "SQL SWIFT Demo", SQL_NTS,
(SQLCHAR*)"DBA", SQL_NTS, (SQLCHAR*) "sql", SQL_NTS );
SQLAllocHandle(SQL_HANDLE_STMT, dbc, &stmt );
SQLBindCol(stmt, 1, SQL_C_SSHORT, &deptID, 0, &cbDeptID);
SQLBindCol(stmt, 2, SQL_C_CHAR, deptName, sizeof(deptName),&cbDeptName);
SQLBindCol(stmt, 3, SQL_C_SSHORT, &managerID, 0, &cbManagerID);
SQLExecDirect(stmt, (SQLCHAR * )
"SELECTDepartmentID, DepartmentName, DepartmentHeadID FROM
DepartmentsORDER BY DepartmentID", SQL_NTS );
while(( retcode = SQLFetch( stmt ) ) != SQL_NO_DATA ){
printf("%d %20s %d\n", deptID, deptName, managerID );
}
SQLFreeHandle(SQL_HANDLE_STMT, stmt );
SQLDisconnect(dbc );
SQLFreeHandle(SQL_HANDLE_DBC, dbc );
SQLFreeHandle(SQL_HANDLE_ENV, env);
第6章 错误处理
每个ODBC API函数都能产生一系列的反应操作信息的诊断记录。可以用SQLGetDiagField函数获取诊断记录中的特定的域,另外,可以使用SQLGetDiagRec函数获取诊断记录中的一些常用的域,如SQLSTATE、原始错误号等。
SQLGetDiagRec函数的定义如下:
SQLRETURNSQLGetDiagRec(
SQLSMALLINT HandleType,
SQLHANDLE Handle,
SQLSMALLINT RecNumber,
SQLCHAR * Sqlstate,
SQLINTEGER * NativeErrorPtr,
SQLCHAR * MessageText,
SQLSMALLINT BufferLength,
SQLSMALLINT * TextLengthPtr
);
RecNumber:指明需要得到的错误状态行,从1开始逐次增大。
Sqlstate,NativeErrorPtr,MessageText:返回错误状态,错误代码和错误描述。
BufferLength:指定MessageText的最大长度。
TextLengthPtr:指定返回的MessageText中有效的字符数。
函数的返回值可能为:SQL_SUCCESS,SQL_SUCCESS_WITH_INFO,SQL_ERROR,SQL_INVALID_HANDLE,SQL_NO_DATA。在没有返回错误的情况下你需要反复调用此函数,并顺次增大RecNumber参数的值,直到函数返回SQL_NO_DATA,以得到所有的错误描述。
得到STMT句柄上的错误信息,示例代码如下:
SQLCHARSqlState[6],SQLStmt[100],Msg[SQL_MAX_MESSAGE_LENGTH];
SQLINTEGER NativeError;
SQLSMALLINT i, MsgLen;
inti = 1;
while((retcode = SQLGetDiagRec(SQL_HANDLE_STMT,hstmt, i, SqlState,
&NativeError, Msg, sizeof(Msg),&MsgLen)) != SQL_NO_DATA)
{
//显示错误的代码
i++;
}
第7章 事务处理
ODBC中实现对支持事务,在ODBC中支持自动提交模式和手工提交模式,当使用自动提交模式时每执行一次SQL语句ODBC会自动提交本次所进行的修改;当使用手工提交模式时,你必须显示的提交或回滚当前的操作才能够使修改生效。在ODBC 2.X中使用SQLTransact来开始和结束事务,但是在 3.X中不再使用,而使用SQLSetConnectAttr 来设置ODBC调用的事务特性,并使用SQLEndTran结束事务。
如果要设置为自动提交模式使用下面的方式调用:
SQLSetConnectAttr(hdbc, SQL_ATTR_AUTOCOMMIT,
(SQLPOINTER)SQL_AUTOCOMMIT_ON, SQL_IS_POINTER);
如果要设置为手工提交模式使用下面的方式调用:
SQLSetConnectAttr(hdbc,SQL_ATTR_AUTOCOMMIT,
(SQLPOINTER)SQL_AUTOCOMMIT_OFF, SQL_IS_POINTER);
在手工提交模式下,需要使用SQLEndTran来提交或回滚当前事务。
SQLRETURNSQLEndTran(
SQLSMALLINT HandleType,
SQLHANDLE Handle,
SQLSMALLINT CompletionType);
HandleType:指定句柄的类型,设置为值:SQL_HANDLE_DBC。
Handle:提供DBC连接句柄。
CompletionType:设置为SQL_COMMIT或者SQL_ROLLBACK表明提交或者回滚。
示例代码如下:
SQLEndTran(SQL_HANDLE_DBC,hdbc, SQL_COMMIT); // 提交事务
SQLEndTran(SQL_HANDLE_DBC,hdbc, SQL_ROLLBACK); // 回滚事务
第8章 断开数据源连接并释放环境句柄
当应用程序使用完ODBC以后,需调用SQLFreeHandle函数释放所有语句句柄、连接句柄、环境句柄(注意释放顺序为逆序)。先释放语句句柄、再调用SQLDisconnect函数解除与数据源的连接、再次调用SQLFreeHandle函数释放连接句柄、最后释放环境句柄。
SQLDisconnect函数定义:
SQLRETURNSQLDisconnect(
SQLHDBC ConnectionHandle //连接句柄
);
断开数据源连接并释放句柄示例代码如下:
intmain()
{
SQLHENV henv;
SQLHDBC hdbc;
SQLHSTMT hstmt;
SQLRETURN retcode;
SQLCHAR * OutConnStr = (SQLCHAR *)malloc(255);
SQLSMALLINT * OutConnStrLen = (SQLSMALLINT*)malloc(255);
// 分配环境句柄
retcode = SQLAllocHandle(SQL_HANDLE_ENV,SQL_NULL_HANDLE, &henv);
// 设置ODBC版本环境属性
if (retcode == SQL_SUCCESS || retcode ==SQL_SUCCESS_WITH_INFO)
{
retcode = SQLSetEnvAttr(henv,SQL_ATTR_ODBC_VERSION,
(void*)SQL_OV_ODBC3,0);
// 分配连接句柄
if (retcode == SQL_SUCCESS || retcode ==SQL_SUCCESS_WITH_INFO)
{
retcode =SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
// 设置登陆等待5秒
if (retcode == SQL_SUCCESS || retcode== SQL_SUCCESS_WITH_INFO)
{
SQLSetConnectAttr(hdbc,SQL_LOGIN_TIMEOUT, (SQLPOINTER)5, 0);
// 连接到数据源
retcode = SQLConnect(hdbc,(SQLCHAR*) "NorthWind", SQL_NTS,
(SQLCHAR*) NULL, 0,NULL, 0);
// 分配语句句柄
if (retcode == SQL_SUCCESS || retcode==
SQL_SUCCESS_WITH_INFO
{
retcode =SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);
// 处理数据
if (retcode == SQL_SUCCESS ||retcode ==
SQL_SUCCESS_WITH_INFO)
{
SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
}
SQLDisconnect(hdbc);
}
SQLFreeHandle(SQL_HANDLE_DBC,hdbc);
}
}
SQLFreeHandle(SQL_HANDLE_ENV, henv);
}
}
关于更详细的ODBC API的信息,可以参考MSDN ODBC Reference。可以在http://msdn.microsoft.com/en-us/library/ms713607(v=vs.85).aspx获得相关文档。
另外编写本文还参考了以下两个文档:
http://msdn.microsoft.com/zh-cn/library/1dwe8111.aspx
http://www.cnblogs.com/kzloser/archive/2012/11/29/2794815.html