登录  
 加关注
查看详情
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

Gamebaby Rock Sun的博客

我只知道一件事情,就是我一无所知。

 
 
 

日志

 
 
关于我

曾经拥有的,不要忘记, 已经得到的,更要珍惜, 属于自己的,不要放弃, 已经失去的,留着回忆, 想要得到的,必须努力, 但最重要的,是好好爱惜自己!

VC++实战OLEDB编程(十一)——参数化查询和存储过程调用  

2010-03-15 13:56:46|  分类: OLEDB编程 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

 

在一些面向业务的应用程序中,很多业务的查询往往是固定的,其中有些甚至只是根据不同的值改变几个固定的条件值而已。比如查询用户信息,通常都是根据用户编号或者用户名称等固定信息查询用户表得到一个结果集。比如查询语句:Select * From 用户表 Where 用户号=’xxxx’。往往在一些程序中,这个语句都是先被组成字符串,然后被执行。之前文章中的例子也是这样做的,最终的SQL语句都是一个字符串,当这个查询要反复被执行的时候,实际上也就是用不同值取代‘xxxx’处的内容然后调用Execute。因为SQL语句是一种即时解释即时运行的语句,所以这样做假如DBMS不优化的话,解析语句的过程就是一个非常大的浪费,因为类似的语句被翻译了n遍(n>=2)。现代的DBMS通常都是把SQL语句翻译成一种叫做“执行计划”的伪机器码的形式,然后交由虚拟机或直接由硬件去执行,有时候翻译的过程是一个比较耗时的过程。

索性的是,现代的DBMS都有一种缓冲执行计划的能力,在翻译前先查找有没有类似语句的执行计划可以利用,通常常量的值就不去比对了,这时DBMS会发现如下的两条语句完全可以用同一个执行计划来完成,这样就省去了重新翻译一条语句的过程:

Select * From 用户表 Where 用户号=’A’

Select * From 用户表 Where 用户号=’B’

从语言的角度讲,这两句中的‘A’和‘B’完全可以看做一个参数,语句本身则可以被认为是完全相同的,在DBMS内部只需缓冲一个Select * From 用户表 Where 用户号=未知常量 语句的等价执行计划,每次用不同的实际常量值代替未知常量这个占位符就可以直接运行得到结果,这节约大量的翻译SQL语句的时间,尤其是查询再复杂些的时候。

在OLEDB中也提供了类似功能的接口,只不过语句中的常量占位符需要用一个?(问号)来明确指出,当然能不能这样使用占位符最终还是要数据提供者来支持,当前大多数DBMS的OLDDB提供程序都支持这个标准的占位符。比如上面的语句就可以利用这个占位符来改写成:

Select * From 用户表 Where 用户号=?

把这个语句直接传递给OLEDB的ICommandText接口即可。但是执行的时候就需要传入真实的参数值。这时具备的好处就是这个命令只需要指定一次,随后可以Execude很多次,同时在很多DBMS内部,这种命令也直接强制打开了缓冲执行计划的能力,最终的好处就是整个系统的性能提升,尤其是对一些固定的查询。这样的查询就被称作“参数化查询”(可以将一个语句想想成一个函数,常量用参变量来代替)。当然这种占位符的功能不能被无限扩大化,占位符不能代替列名表名等语句要素,它只能用来代替常量,但它可以用来代替Insert语句的VALUES子句中的常量。

要想利用这种能力,就需要用到几个新的接口ICommandPrepare和ICommandWithParameters,关于这些接口的定义以及方法就不赘述了,可以查阅OLEDB的帮助或者查阅MSDN即可。下面就给出调用的示意代码,演示如何实现一个参数化查询:

ICommandPrepare*        pICommandPrepare        = NULL;

ICommandWithParameters* pICommandWithParameters = NULL;

TCHAR* pSQL             = _T("Select * From 用户表 Where 用户号=?");

DBPARAMS   dbParam  = {};

...........

GRS_COM_CHECK(pICommandText->SetCommandText(DBGUID_DEFAULT,pSQL));

GRS_COM_CHECK(pICommandText->QueryInterface(IID_ICommandPrepare

,(void**)&pICommandPrepare));

GRS_COM_CHECK(pICommandPrepare->Prepare(0));

GRS_COM_RELEASE(pICommandPrepare);

GRS_COM_CHECK(pICommandText->QueryInterface(IID_ICommandWithParameters

,(void**)&pICommandWithParameters));

GRS_COM_CHECK(pICommandWithParameters->GetParameterInfo(&iParamCnt

,&pParamInfo,&pStringBuffer));

GRS_COM_RELEASE(pICommandWithParameters);

 

if(iParamCnt > 0 )

{

GRS_COM_CHECK(pICommandText->QueryInterface(IID_IAccessor

,(void**)&pIAccessor));

rgBindings = (DBBINDING*)GRS_ALLOC(iParamCnt * sizeof(DBBINDING));

    for(ULONG i = 0;i<iParamCnt;i++)

    {

        rgBindings[i].iOrdinal   = pParamInfo[i].iOrdinal;

        rgBindings[i].dwPart    = DBPART_VALUE;

        rgBindings[i].obStatus   = 0;

        rgBindings[i].obLength   = 0;

        rgBindings[i].obValue    = dwOffset;

        rgBindings[i].dwMemOwner = DBMEMOWNER_CLIENTOWNED;

        rgBindings[i].eParamIO   = DBPARAMIO_INPUT;

        rgBindings[i].bPrecision = pParamInfo[i].bPrecision;

        rgBindings[i].bScale     = pParamInfo[i].bScale;

        rgBindings[i].wType      = pParamInfo[i].wType;

        rgBindings[i].cbMaxLen   = pParamInfo[i].ulParamSize;

 

        dwOffset = rgBindings[i].cbMaxLen + rgBindings[i].obValue;

        dwOffset = GRS_ROUNDUP(dwOffset);

}       

GRS_COM_CHECK(pIAccessor->

CreateAccessor(DBACCESSOR_PARAMETERDATA, 1,

            rgBindings, iParamCnt * sizeof(DBBINDING),&phAccessor, NULL));

    GRS_COM_RELEASE(pIAccessor);

dbParam.cParamSets = 1;

    dbParam.hAccessor = phAccessor;

    dbParam.pData = GRS_ALLOC(dwOffset);

 

    _tcscpy((TCHAR*)dbParam.pData,_T("aaa"));

}

GRS_COM_CHECK(pICommandText->Execute(NULL

,IID_IRowset,&dbParam,(DBROWCOUNT*)&iRow,(IUnknown**)&pIRowset));

       .............

       以上代码中省略的部分可以参阅《七》文章中例子的代码。

       在上面的例子中先得到了一个ICommandPrepare接口,接着就调用了Prepare方法,Prepare方法的只有一个无符号长整形参数,表示预期的执行次数,当不知道的时候传入0即可,0的含义就是执行多少次由调用者来决定,提供者尽量缓冲这个执行计划。如果不在需要DBMS缓冲这个查询的执行计划了那么就调用该接口的Unprepare方法显式的释放这个查询的执行计划。最好在不用的时候都调用下这个释放操作,以便于DBMS系统释放相关的资源,不然它就会傻乎乎的一直缓冲着这个执行计划,如果有很多这样的执行计划被缓冲着,最终你会发现数据库服务器内存会不堪重负。

       Prepare之后就是利用ICommandWithParameters的GetParameterInfo方法得到这个参数化查询的参数信息,这个与IColumnsInfo的GetColumnInfo方法是异曲同工。得到参数信息后,紧接着就是利用这个信息新建一个绑定结构(又见绑定,OLEDB绑定大法是使用OLEDB的不二法门),然后利用这个绑定结构创建一个访问器HACCESSOR,最后就是准备一个DBPARAMS结构体的数组,放入参数,并调用Execute方法得到IRowset结果集。剩下的操作就是绑定读取数据了。

以上的操作方式,只是几种方式中的一种,并且并不是所有的OLEDB提供者都能支持

GetParameterInfo函数,如果不支持的时候,调用者如果知道参数的个数、类型、顺序等等的时候就可以自己使用ICommandWithParameters接口的另一个方法SetParameterInfo,用这个方法可以告诉提供者,参数化查询中有多少个参数,每个参数的类型、顺序等等是什么情况,之后任然需要创建一个参数的绑定结构,并创建HACCESSOR访问器,当然二者的参数顺序必须保持一致。下面的例子演示了使用SetParameterInfo方式进行参数化查询:

......

GRS_COM_CHECK(pICommandText->QueryInterface(IID_ICommandWithParameters

,(void**)&pICommandWithParameters));

    DBPARAMBINDINFO ParamBindInfo[1];

    ULONG           ParamOrdinals[1];

    ZeroMemory(ParamBindInfo,sizeof(DBPARAMBINDINFO));

    ParamBindInfo[0].pwszDataSourceType = _T("DBTYPE_STR");

    ParamBindInfo[0].pwszName = NULL;

    ParamBindInfo[0].ulParamSize = 50 * sizeof(char); //VARCHAR(50)

    ParamBindInfo[0].dwFlags = DBPARAMFLAGS_ISINPUT;

ParamOrdinals[0] = 1;

GRS_COM_CHECK(pICommandWithParameters->SetParameterInfo(1

,ParamOrdinals,ParamBindInfo));

       ......

       这样就设置好了参数,这样调用的好处是效率高,数据提供者不用深度去解析参数的类型等信息,只需要按照提供的顺序安排参数的类型即可。一般参数化查询时,都是事先知道参数个数、顺序、类型等信息的,这样调用之后,就可以直接创建HACCESSOR,然后调用Execute传入参数得到结果了。

有些时候,主要用参数化查询的方式来调用存储过程,尤其是存储过程输出参数的时候更需要使用参数化查询来得到输出参数。如果存储过程有输出参数时,使用SetParameterInfo时需要注意明确指定DBPARAMBINDINFO 的dwFlags 成员为DBPARAMFLAGS_ISOUTPUT,同时在创建绑定结构时,还需要明确指定DBBINDING的eParamIO参数为DBPARAMIO_OUTPUT,如果参数同时用作输入和输出时,那么绑定的时候就要指定成DBPARAMIO_INPUT | DBPARAMIO_OUTPUT。 最后Execute的成功之后,输出参数就被放在了对应的参数缓冲中。在编写参数化查询需要调用的存储过程SQL语句字符串时,如果存储过程有几个参数,就需要明确的使用几个?占位符来标明参数的位置,这样才可以调用GetParameterInfo来得到参数的具体信息。

最后一种设定参数信息的方式就是ICommandWithParameters接口的MapParameterNames,这个方法的调用方式与IColumnsInfo的MapColumnIDs方式是比较类似的,都是用于不清楚参数顺序,而比较明确参数名称的情况下,在调用存储过程时,这是一种非常有用的方式,因为存储过程的参数都是显式命名过的参数,此时可以不去关心参数的顺序,而只需要按照参数的名称来设置参数的其他相关信息即可,主要就是参数的类型信息。

至此参数化查询以及以参数化查询存储过程的方式就介绍完了,但是参数化查询存储过程的方式没有给出例子,我想这个留给大家自己去练习吧,有问题的话就可以跟帖讨论。

(未完待续)

  评论这张
 
阅读(2869)| 评论(6)

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2018