学习|使用c语言对sql server进行增删改查操作

使用c语言对sql server进行增删改查操作 众所周知,自从sql server 2000以后,便不再支持c语言的嵌入式语言操作,因此需要使用odbc连接的方式来进行操作。我在完成实验的时候一直找不到较为完整的操作代码,走了不少弯路,在这里分享给大家我的一些总结。
环境
windows 10
sql server 2019
vs2019
注意:请使用多字节字符集

  1. ODBC连接
    这一部分我就不做详细的赘述,在论坛中很容易搜索的到。
  2. 连接数据库
#include #include #include #include #includevoid 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);

【学习|使用c语言对sql server进行增删改查操作】采用这样的字符串连接方式是存在弊端的,可能会被sql注入攻击。sqlfetch是游标,用作将所有结果输出,相信大家应该都学过了,就不做分析了。
SQLCHAR sql2[100] = "select * from dbo.S1 where SADDR like '%"; //注意这里的百分号后面千万不要加上空格,否则你几乎是不可能看出来不同的 strcat((char*)sql2, input); strcat((char*)sql2, "%'");

7.总结
现在语言这么的丰富,用这种底层函数来连接数据库实在是有点麻烦了,那么多的库函数库函数不用用这个。不过如果是在学校当中的话,在更为底层的方面开始了解对以后的工作和学习都有所好处。
8.感想
非常明显,这篇文章无论从语言还是结构上来说都非常的松散随意,因此如果存在任何的错误,欢迎留言,我会做出修改。
ps:这是我的数据库实验作业,如果后来者有参考的,千万不要是跟我一个学校的,张淼老师会看出来的哦,毕竟没几个懒人连PB都懒得下

    推荐阅读