使用 select 语句获取数据,有两种种结果,第一种,得到的结果只有一行,我们只需要用指定的变量来接收它就可以了,但第二种情况则是有多行数据,每一行数据,处理这种多行返回的数据也有两种方法,一个是使用一个二维宿主数组来接收这些结果(如果不知道结果有多少,宿主数组也不知道该定义多大,所以这种方法不太灵活),另外一个是使用游标的方式来遍历数据,游标又分单向的遍历游标和滚动游标。本文就介绍这些所有的方法。
接收一行数据
#include <stdio.h> #include <string.h> #include "sqlca.h" EXEC SQL BEGIN DECLARE SECTION; char *serversid = "scott/tiger@orcl"; // 宿主变量 int deptno; char dname[20]; char loc[20]; EXEC SQL END DECLARE SECTION; int main(void) { int ret = 0; // 连接服务器 EXEC SQL CONNECT :serversid; if (sqlca.sqlcode != 0) { ret = sqlca.sqlcode; printf("connect error: %d\n", ret); return ret; } printf("connect ok...\n"); // 执行查询语句,将查询结果存放到之前声明的宿主变量中 EXEC SQL select deptno, dname, loc into :deptno, :dname, :loc from dept where deptno=20; if (sqlca.sqlcode != 0) { ret = sqlca.sqlcode; printf("select error: %d\n", ret); return ret; } // 打印执行完成后存放到宿主变量中的结果 printf("dname:%s, loc:%s, deptno:%d\n", dname, loc, deptno); // 提交(commit)并关闭(release)连接 EXEC SQL commit release; printf("commit release ok...\n"); return 0; }
以上程序执行后会得到dname,loc,deptno三个数据,结果如下图:
使用这种方式接收数据我们会发现一个问题,就是每一个读取出来的数据后面都多出了很多空格,这些空格是由于我们数组定义了长度为20,如果数据不满足20个那么会自动把后面的空位补为空格。想避免这种问题可以看下面几种处理方法。
使用varchar和string接收数据
#include <stdio.h> #include <string.h> #include "sqlca.h" /* typedef int int32_t; int a = 10; int32_t a; int a; -> typedef int a; a从原来的变量名,变成了int类型。 char dnameType[20]; 定义一个字符数组 typedef char dnameType[20]; dnameType 不在是一个变量,而变成类型了。 */ typedef char dnameType[20]; //定义了一个有20字符的数组数据类型 EXEC SQL BEGIN DECLARE SECTION; char *serversid = "scott/tiger@orcl"; int deptno1; char dname1[20]; char loc1[20]; int deptno2; varchar dname2[20]; varchar loc2[20]; // 将c语言的变量转化为外部变量 EXEC SQL TYPE dnameType is string(20); int deptno3; dnameType dname3; //char *dname char dname[20]; dnameType loc3; EXEC SQL END DECLARE SECTION; int main(void) { int ret = 0; // 连接服务器 EXEC SQL CONNECT :serversid; if (sqlca.sqlcode != 0) { ret = sqlca.sqlcode; printf("connect error: %d\n", ret); return ret; } printf("connect ok...\n"); // 执行查询语句,注意使用宿主变量的方法,使用默认的char类型来接收查询出来的数据 EXEC SQL select deptno, dname, loc into :deptno1, :dname1, :loc1 from dept where deptno=20; if (sqlca.sqlcode != 0) { ret = sqlca.sqlcode; printf("select error: %d\n", ret); return ret; } printf("--------------1 char------------\n"); printf("dname:%s, loc:%s, deptno:%d\n", dname1, loc1, deptno1); // 使用 varchar 类型来接收查询出来的数据 EXEC SQL select deptno, dname, loc into :deptno2, :dname2, :loc2 from dept where deptno=20; if (sqlca.sqlcode != 0) { ret = sqlca.sqlcode; printf("select error: %d\n", ret); return ret; } printf("--------------2 varchar------------\n"); printf("dname:%s, loc:%s, deptno:%d\n", dname2.arr, loc2.arr, deptno2); // 使用转换后的 string 类型来接收查询的数据 EXEC SQL select deptno, dname, loc into :deptno3, :dname3, :loc3 from dept where deptno=20; if (sqlca.sqlcode != 0) { ret = sqlca.sqlcode; printf("select error: %d\n", ret); return ret; } printf("--------------3 string------------\n"); printf("dname:%s, loc:%s, deptno:%d\n", dname3, loc3, deptno3); EXEC SQL commit release; printf("commit release ok...\n"); return 0; }
可以看出,第一种与上面的例子没什么区别,都是用 char 类型来接收数据,而第二种则是使用了一个新的宿主类型 varchar,这种类型在内部实现实际是将 varchar 转换为一个结构体,结构体中有一个成员为 arr 和 len,arr 记录了返回的字符串内容,len 记录了返回的字符串长度,打印时使用 xxx.arr 就可以取到没有被自动补上空格的字符串了。最后的第三种是在宿主变量声明时就将其转换成为外部变量,这种方式避免了第二种方法中需要调用成员的方法。三种方法各有优略,在平时使用时需要看自己的需求。以下是处理结果返回的数据:
使用二维宿主数组接收一组数据
#include <stdio.h> #include <string.h> #include <stdlib.h> #include "sqlca.h" EXEC SQL BEGIN DECLARE SECTION; char *serversid = "scott/tiger@orcl"; int deptno; char dname[20]; char loc[20]; int count; // 保存查询出来的记录个数rows int deptno2[10]; // 10个编号,称为宿主数组 varchar dname2[10][20]; // 10个名字,每个名字最长20字符 varchar loc2[10][20]; // 10个区域,每个区域最长20字符 short loc_ind[10]; // 10个指示变量 EXEC SQL END DECLARE SECTION; void sqlerr(void) { EXEC SQL WHENEVER SQLERROR CONTINUE; printf("Error Reason: %.*s\n", sqlca.sqlerrm.sqlerrml, sqlca.sqlerrm.sqlerrmc); EXEC SQL ROLLBACK RELEASE; exit(1); } int main(void) { // 错误处理,代替if(sqlca.sqlcode != 0) EXEC SQL WHENEVER SQLERROR DO sqlerr(); // 链接数据库 EXEC SQL CONNECT :serversid; // 查询dept表所有内容,并将数据储存到前面声明的宿主数组中 EXEC SQL select deptno, dname, loc into :deptno2, :dname2, :loc2:loc_ind from dept; // 获取通讯区的sqlerrd[2]保存了SQL语句处理的行数。 count = sqlca.sqlerrd[2]; printf("------SQL rows = %d\n", count); // 根据查询出来的行数遍历宿主数组内容 int i; for (i = 0; i < count; i++) { //dname2[i]取二维数组的行 printf("%d\t%s\t%s\n", deptno2[i], dname2[i].arr, loc2[i].arr); } printf("Enter any key to create dept2 \n"); getchar(); // 使用SQL创建一张表dept2,格式同dept EXEC SQL create table dept2 as select * from dept where 1=2; printf("Enter any key to insert into dept2 ----lots of rows\n"); getchar(); // 向表中插入刚才查询出来的多行数据: EXEC SQL For :count insert into dept2(deptno, dname, loc) values(:deptno2, :dname2, :loc2:loc_ind); printf("----insert into dept2 finish...\n"); // 提交事物并断开连接 EXEC SQL COMMIT RELEASE; return 0; }
这段代码我们可以成功的处理 select 返回的一组数据,但是这组数据的个数如果超过了我们定义的数组的大小,那么就无法接收更多的数据了。所以这种方法并不是特别的通用,但是有的时候还是用的到。
使用游标来处理一组数据
#include <stdio.h> #include <string.h> #include <stdlib.h> #include "sqlca.h" typedef char dnameType[20]; typedef char locType[20]; EXEC SQL BEGIN DECLARE SECTION; char *serversid = "scott/11@orcl"; EXEC SQL TYPE dnameType is string(20); EXEC SQL TYPE locType is string(20); int deptno; dnameType dname; locType loc; short loc_ind; //指示变量不能省略。以防出现NULL值。 EXEC SQL END DECLARE SECTION; void sqlerr(void) { EXEC SQL WHENEVER SQLERROR CONTINUE; printf("Error Reason: %.*s\n", sqlca.sqlerrm.sqlerrml, sqlca.sqlerrm.sqlerrmc); EXEC SQL ROLLBACK RELEASE; exit(1); } int main(void) { //错误处理,代替if(sqlca.sqlcode != 0) EXEC SQL WHENEVER SQLERROR DO sqlerr(); //链接数据库 EXEC SQL CONNECT :serversid; //1. 定义游标 EXEC SQL DECLARE dept_cursor CURSOR For select * from dept; //2. 打开游标 EXEC SQL OPEN dept_cursor; //3. 提取数据 while (1) { // fetch 从游标中抽取数据,游标会自动向后移动,依次抽取直到 EXEC SQL FETCH dept_cursor INTO :deptno, :dname, :loc:loc_ind; // 错误处理 if (sqlca.sqlcode == 100 || sqlca.sqlcode == 1403) { break; } printf("%d\t%s\t%s\n", deptno, dname, loc); } /* 官方文档中的错误处理机制 // 如果出现 NOT FOUND 错误,则执行 break 跳出循环 EXEC SQL WHENEVER NOT FOUND DO break; for (;;) { // 一直取数据,如果出错那么相应上面的出错处理,直接 break; EXEC SQL FETCH dept_cursor INTO :deptno, :dname, :loc:loc_ind; printf("%d\t%s\t%s\n", deptno, dname, loc); } */ //4. 关闭游标 EXEC SQL CLOSE dept_cursor; //提交事物并断开连接 EXEC SQL COMMIT RELEASE; return 0; }
使用游标来处理数据我们就不需要担心那么多因为数据定义过小的小问题了,我们可以一行一行的读取数据进行处理,而这种方法也存在部分缺陷,那就是依次遍历整个结果集,却不能定向的指定要取哪部分数据,所以呢,下面的滚动游标应运而生。
使用滚动游标来处理一组数据
#include <stdio.h> #include <string.h> #include <stdlib.h> #include "sqlca.h" typedef char dnameType[20]; typedef char locType[20]; EXEC SQL BEGIN DECLARE SECTION; char *usrname = "scott"; char *passwd = "11"; char *serverid = "orcl"; EXEC SQL TYPE dnameType is string(20); EXEC SQL TYPE locType is string(20); int deptno; dnameType dname; //string 数据类型 short dname_ind; locType loc; short loc_ind; EXEC SQL END DECLARE SECTION; void connet(void) { int ret = 0; //连接数据库 EXEC SQL CONNECT :usrname IDENTIFIED BY :passwd USING :serverid ; if (sqlca.sqlcode != 0) { ret = sqlca.sqlcode; printf("sqlca.sqlcode: err:%d \n", sqlca.sqlcode); return ; } printf("connect ok...\n"); } void sqlerr(void) { EXEC SQL WHENEVER SQLERROR CONTINUE; printf("Error Reason: %.*s\n", sqlca.sqlerrm.sqlerrml, sqlca.sqlerrm.sqlerrmc); EXEC SQL ROLLBACK RELEASE; exit(1); } //滚动游标查询数据 int main(void) { int ret = 0; EXEC SQL WHENEVER SQLERROR DO sqlerr(); connet(); //1 定义游标 declare cursor 在为某一次查询 EXEC SQL DECLARE c SCROLL CURSOR FOR select deptno, dname, loc from dept; //2 打开游标 open cursor EXEC SQL OPEN c; //3 获取数据 fetch data //查询最后一条数据 EXEC SQL FETCH LAST c INTO :deptno, :dname:dname_ind, :loc:loc_ind; printf("1-- %d\t %s\t %s \n", deptno, dname, loc ); //查询第一条数据 EXEC SQL FETCH FIRST c INTO :deptno, :dname:dname_ind, :loc:loc_ind; printf("2-- %d\t %s\t %s \n", deptno, dname, loc ); //查询第2条数据(绝对) EXEC SQL FETCH ABSOLUTE 2 c INTO :deptno, :dname:dname_ind, :loc:loc_ind; printf("3-- %d\t %s\t %s \n", deptno, dname, loc ); //查询相对第2条数据 也就是第4条 EXEC SQL FETCH RELATIVE 2 c INTO :deptno, :dname:dname_ind, :loc:loc_ind; printf("4-- %d\t %s\t %s \n", deptno, dname, loc ); //查询下一条(相对) EXEC SQL FETCH NEXT c INTO :deptno, :dname:dname_ind, :loc:loc_ind; printf("5-- %d\t %s\t %s \n", deptno, dname, loc ); //查询前一条(相对) EXEC SQL FETCH PRIOR c INTO :deptno, :dname:dname_ind, :loc:loc_ind; printf("6-- %d\t %s\t %s \n", deptno, dname, loc ); //4 关闭游标 close cursor EXEC SQL CLOSE c; EXEC SQL COMMIT RELEASE; return ret ; }
使用滚动游标可以看出,我们可以自由的使用6种方式来获取我们需要的数据,而不像之前的普通游标只能一行一行的读取了。
以上便是我们介绍的 proc 编程中处理 select 返回数据的几种方法,每一种方法都各有取舍,所以在使用的时候要根据自己的情况来决定到底要使用哪个方法更适合自己。