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