使用ODBC API读取Decimal或者Numeric
前言
前些天成功的连接并简单操作了一下数据库,谁知在第二天做些小动作的时候却碰到了不小的麻烦,就是数据库中标记为Decimal或者Numeric的数据都读取不了,这可就麻烦大了,先后在SQLGetData中换了SQL_C_DOUBLE、SQL_C_FLOAT两种种类型都无功而返,直到后来发现了,还有SQL_C_NUMERIC这个东西,一瞬间就很兴奋,结果照着MSDN的介绍使用了,还是不大行。
改变
后来就继续查资料了,结果在stackflow上面找到了一篇很好的文章(牛人果然多呀),正好是介绍这一点的,MSDN也还是非常强大的:
http://support.microsoft.com/kb/222831
简单来说,核心的地方就是在执行完SQL语句后,要设置数据的属性,具体的代码如下:
/* 这一部分和上面一样 */
SQLRETURN l_uiReturn = SQLAllocHandle(SQL_HANDLE_STMT,m_hDatabaseConnection,&m_hStatement); if (l_uiReturn != SQL_SUCCESS && l_uiReturn != SQL_SUCCESS_WITH_INFO) { return l_oRetval; } CString l_cstrSql; l_cstrSql.Format(_T("SELECT [float] FROM [数据源名称].[所属者].[data] ORDER BY [float] DESC")); l_uiReturn = SQLExecDirect(m_hStatement,l_cstrSql.GetBuffer(),SQL_NTS); if (l_uiReturn != SQL_SUCCESS && l_uiReturn != SQL_SUCCESS_WITH_INFO) { return l_oRetval; } /* 该代码段用于配置当前语句相关数据列的数据格式 */ //这里开始进入重点 #pragma region SQLHDESC l_hDesc; //第四种句柄出现了 l_uiReturn = SQLGetStmtAttr(m_hStatement, SQL_ATTR_APP_ROW_DESC,&l_hDesc, 0, NULL);//获得SQL语句的属性 if (l_uiReturn != SQL_SUCCESS && l_uiReturn != SQL_SUCCESS_WITH_INFO) { return l_oRetval; } /* Float数据格式,对应数据库中设置格式为DECIMAL(5,2) */ l_uiReturn = SQLSetDescField (l_hDesc,1,SQL_DESC_TYPE,(VOID*)SQL_C_NUMERIC,0); l_uiReturn = SQLSetDescField (l_hDesc,1,SQL_DESC_PRECISION,(VOID*) 5,0); l_uiReturn = SQLSetDescField (l_hDesc,1,SQL_DESC_SCALE,(VOID*) 2,0); #pragma endregion l_uiReturn = SQLFetch(m_hStatement); if (l_uiReturn != SQL_SUCCESS && l_uiReturn != SQL_SUCCESS_WITH_INFO) { return l_oRetval; } SQL_NUMERIC_STRUCT l_tFloat; SQLINTEGER l_siLength = 0; /* 获得相应的浮点数形数据 */ SQLGetData(m_hStatement,1,SQL_ARD_TYPE,&l_tFloat,sizeof(l_tFloat),&l_siLength);
//这里一定要使用SQL_ARD_TYPE,意思是要使用我们上面设置好的数据格式 SQLFreeHandle(SQL_HANDLE_DESC,l_hDesc); SQLFreeHandle(SQL_HANDLE_STMT,m_hStatement);
然后调试,开始看l_tFloat中的数据,结果发现基本上不懂,所以还是继续看文章吧,在其下方建立了一个函数,负责数据转化,所以重写了一个自己的版本并理解了一下,简单的来说,这是一个16进制浮点数据转化的过程:
double GetDoubleFromHexStruct(SQL_NUMERIC_STRUCT & p_rtNumeric) { long l_lValue =0; int l_iLastVal = 1,l_iCurrentVal = 0; int l_iLsd = 0 ,l_iMsd = 0; for(int i = 0;i < 16 != 0;i++) { l_iCurrentVal = (int) p_rtNumeric.val[i]; l_iLsd = l_iCurrentVal % 16; l_iMsd = l_iCurrentVal / 16; l_lValue += l_iLastVal * l_iLsd; l_iLastVal = l_iLastVal * 16; l_lValue += l_iLastVal * l_iMsd; l_iLastVal = l_iLastVal * 16; } long l_lDivisor = 1; for (int i =0;i < p_rtNumeric.scale;i++) { l_lDivisor = l_lDivisor * 10; } return (double)(l_lValue/(double)l_lDivisor); }
将l_tFloat经过这个函数就可以顺利得到我们想要的值了,虽然在数值上会有差距,但基本上是由于计算机表达浮点数的误差导致的,这个就谁也没办法了。
思考
可以看到,其实上面的代码还是挺复杂的,尤其是当面对不停的用户需求变化,这种方式简直就有点像在自杀,每个固定的SQL语句的地方都要进行修改,虽然现在维护的程序需要数据库的方面较少,但是抱着未雨绸缪的思想,还是提前准备一下比较好,结果就是,没准备出来。按说ODBC API已经被MFC给面向对象过了,理论上是肯定可以实现的,但奈何自己还是没那个本事,当时是因为看上了SQLGetDescField这个函数,我就想看一看到底能否获得数据格式,按说由于数据库这些设计虽然不一定让改,但是查询数据格式的功能应该是向外提供的,但是弄了半天,这个函数一直返回一个SQL_NO_DATA的错误,整整一个下午的时间也没弄明白。
总的来说,自己还是对数据库的知识不扎实,MSDN上的长篇描述也是让自己很头疼,希望如果有了解这方面的人可以告诉我一下,我也想简单的封装一下API,以此来熟悉数据库的驾驭方式。