<>使用c语言对sql server进行增删改查操作

众所周知,自从sql server
2000以后,便不再支持c语言的嵌入式语言操作,因此需要使用odbc连接的方式来进行操作。我在完成实验的时候一直找不到较为完整的操作代码,走了不少弯路,在这里分享给大家我的一些总结。

环境
windows 10
sql server 2019
vs2019
注意:请使用多字节字符集

* ODBC连接
这一部分我就不做详细的赘述,在论坛中很容易搜索的到。
* 连接数据库 #include<stdio.h> #include<windows.h> #include<sql.h> #include
<sqlext.h> #include<sqltypes.h> void Connect(SQLRETURN ret, SQLHENV& henv,
SQLHDBC& hdbc, SQLHSTMT& hstmt)//设置连接参数的句柄 { const char* SY1 = "DOST"; unsigned
char* SY = (unsigned char*)SY1; const char* db21 = "sa";//数据库ODBC连接的账户 unsigned
char* db2 = (unsigned char*)db21; const char* pass1 = "12345678";//数据库ODBC连接的密码
unsigned char* pass = (unsigned char*)pass1; ret = SQLAllocHandle(SQL_HANDLE_ENV
, SQL_NULL_HANDLE, &henv);//申请环境句柄 ret = SQLSetEnvAttr(henv,
SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, SQL_IS_INTEGER); ret =
SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);//申请数据库连接句柄 ret = SQLConnect(hdbc,
SY, SQL_NTS, db2, SQL_NTS, pass, SQL_NTS); /*db2为配置的ODBC数据源名称,这里根据自己的配置进行修改*/ if
(!(ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO)) { printf("连接数据库失败!\n");
return; } ret = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);//用于分配句柄 }
3.增加功能
void insert(SQLRETURN ret, SQLHENV& henv, SQLHDBC& hdbc, SQLHSTMT& hstmt) { int
i= 0,j = 0,k = 0; unsigned char tablename[60]; do { printf("请输入你想要插入数据的表的序号\n")
; printf("1.学生表S 2.课程表C 3.学生成绩表SC\n"); scanf("%d", &i); switch (i) { case 1:
strcpy((char*)tablename, "S(sclass,sno,sname,ssex,Sdept,sage) values
(?,?,?,?,?,?)");printf(
"请根据S(sclass,sno,sname,ssex,Sdept,sage)依次输入数据,数据之间带回车或空格\n"); break;
//非常明显这里采用了动态sql的方式,问号的地方即为我们后期需要输入的地方 case 2:strcpy((char*)tablename, "C
values (?,?,?,?)"); printf("请根据C(cno,cname,cpno,ccredit)依次输入数据,数据之间带回车或空格\n");
break; case 3:strcpy((char*)tablename, "SC values (?,?,?,?)"); printf(
"请根据SC(sclass,sno,cno,grade)依次输入数据,数据之间带回车或空格\n"); break; default:printf(
"输入错误,重新输入\n"); } } while (i < 1 || i>3); char s[5][10], s1[10] = "", s2[10] =
"", s3[10] = "", s4[10] = "", s5[10] = ""; int num; /*long conlumnlen;*/
SQLAllocStmt(hdbc, &hstmt); SQLCHAR sql2[100] = "insert into ";
//要注意,sqlchar实际上就是unsigned char,这在字符串操作语句当中是不能够直接操作的,需要强转为char型后再进行操作 strcat((
char*)sql2, (char*)tablename); //printf("%s\n", sql2); SQLPrepare(hstmt, (
SQLCHAR*)sql2, strlen((char*)sql2));//这里是对sql语句进行prepare操作,这一步有助于防注入,提高安全性 if (i
== 1) j = 5; else if (i == 2|| i==3) j = 3; for (k = 0; k < j; k++) { scanf("%s"
, s[k]); SQLBindParameter(hstmt, k+1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, 10,
0, &s[k], 100, NULL);
//sqlbindparameter函数操作用于绑定刚才的问号的位置,注意,上方的“k+1”的数值即为问号在sql语句中的位置,顺序必须一致,而且如果sql在绑定后再修改,这里的绑定将会出错。
} scanf("%d", &num); SQLBindParameter(hstmt, k+1, SQL_PARAM_INPUT, SQL_C_SLONG,
SQL_INTEGER, 8, 0, &num, sizeof(int), NULL); SQLExecute(hstmt); if (ret ==
SQL_SUCCESS|| ret == SQL_SUCCESS_WITH_INFO) { printf("操作成功!\n"); } else { UCHAR
errmsg[100]; printf("操作失败!\n"); SQLError(henv, hdbc, hstmt, NULL, NULL, errmsg,
sizeof(errmsg), NULL); printf("%s", errmsg); } }
4.删除功能
void deleteD(SQLRETURN ret, SQLHENV& henv, SQLHDBC& hdbc, SQLHSTMT& hstmt) {
int i = 0; unsigned char tablename[60]; do { printf("请输入你想要删除数据的表的序号\n"); printf
("1.学生表S 2.课程表C 3.学生成绩表SC\n"); scanf("%d", &i); switch (i) { case 1:strcpy((char
*)tablename, "S where sclass = ? and sno= ? "); printf("请输入班号和学号,之间用空格隔开\n");
break; case 2:strcpy((char*)tablename, "C where cno= ? "); printf("请输入课程号\n");
break; case 3:strcpy((char*)tablename, "SC where sclass =? and sno=? and cno=? "
); printf("请输入班号学号和课程号,之间用空格隔开\n"); break; default:printf("输入错误,重新输入\n"); } }
while (i < 1 || i>3); unsigned char s[3][10]; SQLCHAR sql[120] = "delete from ";
/*long conlumnlen;*/ SQLAllocStmt(hdbc, &hstmt); strcat((char*)sql, (char*)
tablename); SQLPrepare(hstmt, (SQLCHAR*)sql, strlen((char*)sql)); if (i == 2) {
scanf("%s", s[0]); SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR,
SQL_CHAR, 10, 0, &s[0], 100, NULL); } else if (i == 1) { scanf("%s", s[0]);
scanf("%s", s[1]); SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR,
SQL_CHAR, 10, 0, &s[0], 100, NULL); SQLBindParameter(hstmt, 2, SQL_PARAM_INPUT,
SQL_C_CHAR, SQL_CHAR, 10, 0, &s[1], 100, NULL); } else if (i == 3) { scanf("%s",
s[0]); scanf("%s", s[1]); scanf("%s", s[2]); SQLBindParameter(hstmt, 1,
SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, 10, 0, &s[0], 100, NULL);
SQLBindParameter(hstmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, 10, 0, &s[1],
100, NULL); SQLBindParameter(hstmt, 3, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, 10
, 0, &s[2], 100, NULL); } SQLExecute(hstmt);//sql执行 if (ret == SQL_SUCCESS ||
ret== SQL_SUCCESS_WITH_INFO) { printf("操作成功!\n"); } else { UCHAR errmsg[100];
printf("操作失败!\n"); SQLError(henv, hdbc, hstmt, NULL, NULL, errmsg, sizeof(errmsg
), NULL); printf("%s", errmsg); } }

很明显删除操作与刚才的增加操作有非常多相似的地方,只不过是在sql语句上有所不同,无论是sql语句连接,sqlprepare还是sqlexecute顺序基本一致,同样采用了动态sql的方式。

5.修改功能
void edit(SQLRETURN ret, SQLHENV& henv, SQLHDBC& hdbc, SQLHSTMT& hstmt) { int i
= 0; unsigned char tablename1[60],tablename2[60]; unsigned char s[3][10]; do {
printf("请输入你想要修改数据的表的序号\n"); printf("1.学生表S 2.课程表C 3.学生成绩表SC\n"); scanf("%d", &i
); switch (i) { case 1:strcpy((char*)tablename1, "S set "); strcpy((char*)
tablename2, "where sclass=? and sno=?");break; case 2:strcpy((char*)tablename1,
"C set "); strcpy((char*)tablename2, "where cno=? ");break; case 3:strcpy((char*
)tablename1, "SC set "); strcpy((char*)tablename2, "where sclass=? and sno=?
and cno=?"); break; default:printf("输入错误,重新输入\n"); } } while (i < 1 || i>3);
unsigned char s1[10]; char part[14][10]; strcpy(part[0], "sage=? "); strcpy(part
[1], "sclass=? "); strcpy(part[2], "sno=? "); strcpy(part[3], "sname=? ");
strcpy(part[4], "ssex=? "); strcpy(part[5], "Sdept=? "); strcpy(part[6], "cno=?
"); strcpy(part[7], "cname=? "); strcpy(part[8], "cpno=? "); strcpy(part[9],
"ccredit=? "); strcpy(part[10], "grade=? "); int j = 0,num; do { printf(
"选择修改的元素名\n"); printf("1.sage 2.sclass 3.sno 4.sname 5.ssex 6.Sdept 7.cno
8.cname 9.cpno 10.ccredit 11.grade\n"); scanf("%d", &j); } while (j < 1 || j>11)
; SQLCHAR sql[100] = "update "; /*long conlumnlen;*/ SQLAllocStmt(hdbc, &hstmt);
strcat((char*)sql, (char*)tablename1); strcat((char*)sql, part[j-1]); strcat((
char*)sql, (char*)tablename2); printf("%s\n", sql); SQLPrepare(hstmt, (SQLCHAR*)
sql, strlen((char*)sql)); if (i == 2) { printf("请输入课程号:"); scanf("%s", s[0]);
SQLBindParameter(hstmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, 10, 0, &s[0],
100, NULL); } else if (i == 1) { printf("请输入班别学号:"); scanf("%s", s[0]); scanf(
"%s", s[1]); SQLBindParameter(hstmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR,
10, 0, &s[0], 100, NULL); SQLBindParameter(hstmt, 3, SQL_PARAM_INPUT, SQL_C_CHAR
, SQL_CHAR, 10, 0, &s[1], 100, NULL); } else if (i == 3) { printf("请输入班别学号和课程号:"
); scanf("%s", s[0]); scanf("%s", s[1]); scanf("%s", s[2]); SQLBindParameter(
hstmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, 10, 0, &s[0], 100, NULL);
SQLBindParameter(hstmt, 3, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, 10, 0, &s[1],
100, NULL); SQLBindParameter(hstmt, 4, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, 10
, 0, &s[2], 100, NULL); } printf("输入修改元素值:"); if (j == 1||j==11) { scanf("%d", &
num); SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_SLONG, SQL_INTEGER, 8, 0
, &num, sizeof(int), NULL); } else { scanf("%s", s1); SQLBindParameter(hstmt, 1,
SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, 10, 0, &s, 100, NULL); } ret= SQLExecute
(hstmt); if (ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO) { printf(
"操作成功!\n"); } else { UCHAR errmsg[100]; printf("操作失败!\n"); SQLError(henv, hdbc,
hstmt, NULL, NULL, errmsg, sizeof(errmsg), NULL); printf("%s", errmsg); } }
这里同样与上方采用的方法是一致的。
6.查询功能

查询功能分为精确查询和模糊查询,精确查询可以采用动态sql的方式,但是由于动态sql的?传进去后将被直接识别为一个元素,因此模糊查询只能用字符串连接的方式来完成。
void Search(SQLRETURN ret, SQLHENV& henv, SQLHDBC& hdbc, SQLHSTMT& hstmt) {
SQLAllocStmt(hdbc, &hstmt); unsigned char sno[10], sclass[10]; printf("请输入班级
学号:(之间用空格分隔)"); scanf("%s %s", sclass, sno); int i; printf(
"请输入你想查询的功能:1.查询你的学生信息 2.查询你的所有成绩\n"); scanf("%d", &i); SQLCHAR sql[150]; if(i==
1) strcpy((char*)sql,"select * from S where sclass= ? and sno= ? "); else if(i==
2) strcpy((char*)sql, "select a.sname,b.*,c.cname from S a, SC b, C c where
b.sclass = ? and b.sno= ? and a.sno=b.sno and a.sclass=b.sclass and c.cno=b.cno"
); SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR, 10, 0, &
sclass, 100, NULL); SQLBindParameter(hstmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR,
SQL_CHAR, 10, 0, &sno, 100, NULL); char S[6][10]; long conlumnlen; int snum=0;
if (i == 1) { SQLPrepare(hstmt, (SQLCHAR*)sql, strlen((char*)sql)); SQLBindCol(
hstmt, 1, SQL_CHAR, S[0], 10, &conlumnlen); SQLBindCol(hstmt, 2, SQL_CHAR, S[1],
10, &conlumnlen); SQLBindCol(hstmt, 3, SQL_CHAR, S[2], 10, &conlumnlen);
SQLBindCol(hstmt, 4, SQL_CHAR, S[3], 10, &conlumnlen); SQLBindCol(hstmt, 6,
SQL_CHAR, S[4], 10, &conlumnlen); SQLBindCol(hstmt, 5, SQL_INTEGER, &snum,
sizeof(int), &conlumnlen); printf("sclass\tsno\tsname\tssex\tSdept\tsage\n");
SQLExecute(hstmt); } else if(i==2) { SQLPrepare(hstmt, (SQLCHAR*)sql, strlen((
char*)sql)); SQLBindCol(hstmt, 1, SQL_CHAR, S[0], 10, &conlumnlen); SQLBindCol(
hstmt, 2, SQL_CHAR, S[1], 10, &conlumnlen); SQLBindCol(hstmt, 3, SQL_CHAR, S[2],
10, &conlumnlen); SQLBindCol(hstmt, 4, SQL_CHAR, S[3], 10, &conlumnlen);
SQLBindCol(hstmt, 6, SQL_CHAR, S[4], 10, &conlumnlen); SQLBindCol(hstmt, 5,
SQL_INTEGER, &snum, sizeof(int), &conlumnlen); printf(
"sname\tsclass\tsno\tcname\tcno\tgrade\n"); SQLExecute(hstmt); } ret = SQLFetch(
hstmt); if (ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO) { printf(
"操作成功!\n"); while (ret == SQL_SUCCESS || ret == SQL_SUCCESS_WITH_INFO) { if (i
== 1) printf("%s%s%s%s%s%d\n", S[0], S[1], S[2], S[3], S[4], snum); else printf(
"%s%s%s%s%s%d\n", S[0], S[1], S[2], S[4], S[3], snum); ret = SQLFetch(hstmt); }
} else { UCHAR errmsg[100]; printf("操作失败!\n"); SQLError(henv, hdbc, hstmt, NULL,
NULL, errmsg, sizeof(errmsg), NULL); printf("%s", errmsg); } }

上方出现了sqlbindcol函数,是用于查询完后绑定c语言中定义的变量当中,嵌入式sql也是这样的原理,只不过操作更为的直接一些。同样的,绑定的顺序也需要与数据库中你所查询的列相匹配。
着重谈谈模糊查询的连接操作,由于直接采用执行sql语句的方式,模糊查询的模板是下方。
select ··· from ··· like %···%
因此我们操作代码为:
char input[100]; char sno[10] = "", sname[10] = "", ssex[4] = "", sage[4] = "",
saddr[20] = ""; printf("输入地址查询对应数据\n"); scanf("%s", input); long conlumnlen;
SQLAllocStmt(hdbc, &hstmt); SQLCHAR sql2[100] = "select * from dbo.S1 where
SADDR like '%";//注意这里的百分号后面千万不要加上空格,否则你几乎是不可能看出来不同的 strcat((char*)sql2, input);
strcat((char*)sql2, "%'"); SQLPrepare(hstmt, (SQLCHAR*)sql2, strlen((char*)sql2)
); /*ret = SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR, SQL_CHAR,
100, 0, &input, 100, NULL);*/ SQLBindCol(hstmt, 1, SQL_CHAR, sno, 10, &
conlumnlen); SQLBindCol(hstmt, 2, SQL_CHAR, sname, 10, &conlumnlen); SQLBindCol(
hstmt, 3, SQL_CHAR, ssex, 4, &conlumnlen); SQLBindCol(hstmt, 4, SQL_CHAR, sage,
4, &conlumnlen); SQLBindCol(hstmt, 5, SQL_CHAR, saddr, 20, &conlumnlen);
SQLExecute(hstmt); ret = SQLFetch(hstmt); if (ret == SQL_SUCCESS || ret ==
SQL_SUCCESS_WITH_INFO) { printf("操作成功!\n"); while (ret == SQL_SUCCESS || ret ==
SQL_SUCCESS_WITH_INFO) { printf("%s\t%s\t%s\t%s\t%s\n", sno, sname, ssex, sage,
saddr); ret = SQLFetch(hstmt); } } else { UCHAR errmsg[100]; printf("操作失败!\n");
SQLError(henv, hdbc, hstmt, NULL, NULL, errmsg, sizeof(errmsg), NULL); printf(
"%s", errmsg); } SQLFreeConnect(hdbc); SQLFreeEnv(henv);
采用这样的字符串连接方式是存在弊端的,可能会被sql注入攻击。sqlfetch是游标,用作将所有结果输出,相信大家应该都学过了,就不做分析了。
SQLCHAR sql2[100] = "select * from dbo.S1 where SADDR like '%";
//注意这里的百分号后面千万不要加上空格,否则你几乎是不可能看出来不同的 strcat((char*)sql2, input); strcat((char*)
sql2, "%'");
7.总结

现在语言这么的丰富,用这种底层函数来连接数据库实在是有点麻烦了,那么多的库函数库函数不用用这个。不过如果是在学校当中的话,在更为底层的方面开始了解对以后的工作和学习都有所好处。

8.感想

非常明显,这篇文章无论从语言还是结构上来说都非常的松散随意,因此如果存在任何的错误,欢迎留言,我会做出修改。
ps:这是我的数据库实验作业,如果后来者有参考的,千万不要是跟我一个学校的,张淼老师会看出来的哦,毕竟没几个懒人连PB都懒得下

技术
下载桌面版
GitHub
Gitee
SourceForge
百度网盘(提取码:draw)
云服务器优惠
华为云优惠券
腾讯云优惠券
阿里云优惠券
Vultr优惠券
站点信息
问题反馈
邮箱:[email protected]
吐槽一下
QQ群:766591547
关注微信