使用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,以此来熟悉数据库的驾驭方式。

posted @ 2012-09-21 17:39  Geminiv  阅读(1052)  评论(1编辑  收藏  举报