程序开发中,经常需要读取excel文件数据或将数据写入excel,当前主要的操作方式有COM和ODBC方式两种。COM方式功能强大,但读写效率较低。ODBC方式读写速度快,但功能相对较少,如无法获取excel文件内所有sheet的名称,无法写入公式等。
本文主要记录使用ODBC对excel进行操作中的主要步骤:
一、头文件
#include <afxdb.h>
#include <odbcinst.h>
二、查看系统中已安装的Excel ODBC驱动
1、系统中查看已安装ODBC数据源
可通过系统中odbc数据源查看当前已安装的odbc驱动,需要注意区分32位及64位ODBC驱动,要对应所开发的程序是32位应用还是64位应用。
2、程序获取ODBC中Excel驱动
程序获取ODBC Excel Driver可以通过读取读取注册表及ODBC API两种方式获得。
读取注册表方式
已安装的odbc driver均保存在注册表中HKEY_CURRENT_USER\Software\ODBC\ODBC.INI\ODBC Data Sources键下:
实现代码如下:
CString GetExcelOdbcDriver()
{
HKEY hKey;
long lResult;
int i = 1;
if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_CURRENT_USER, _T("Software\\ODBC\\ODBC.INI\\ODBC Data Sources"), 0, KEY_READ, &hKey))
{
TCHAR szKeyValue[256] = { 0 };
DWORD cbValue = 256 * sizeof(TCHAR);
if (ERROR_SUCCESS == RegQueryValueEx(hKey, _T("Excel Files"), NULL, NULL, (LPBYTE)szKeyValue, &cbValue))
{
CString Keydata(szKeyValue);
return Keydata;
}
else
{
RegCloseKey(hKey);
return _T("");
}
}
return _T("");
}
ODBC API方式
需要用到ODBC API函数SQLGetInstalledDrivers,得到所有已安装ODBC Driver,实现如下:
CString GetExcelDriver()
{
TCHAR szBuf[2001];
WORD cbBufMax = 2000;
WORD cbBufOut;
TCHAR *pszBuf = NULL;
CString sDriver;
//获取已安装驱动的名称
if (!SQLGetInstalledDrivers(szBuf, cbBufMax, &cbBufOut))
return _T("");
pszBuf = szBuf;
//检索已安装的驱动是否有Excel...
do
{
if (StrStr(pszBuf, _T("Excel")) != 0 && StrStr(pszBuf, _T("xlsx")) != 0) //csv
{
//发现excel驱动
sDriver = CString(pszBuf);
break;
}
int i = 0;
while (pszBuf - szBuf <= cbBufMax - i && pszBuf[i] != _T('\0'))
i++;
if (pszBuf - szBuf > cbBufMax - i)
return _T("");
pszBuf += i + 1;
} while (pszBuf[0] != _T('\0'));
return sDriver;
}
三、读取Excel文件数据
1、打开Excel文件
首先需要创建连接字符串,然后调用CDatabase::OpenEx方法。
CDatabase database;
CString sDsn;
CString sFilePath= _T("d:\\test.xlsx");
CString sDriver = GetExcelDriver();
//创建连接字符串
sDsn.Format(_T("DRIVER={%s};DSN='Excel nFiles';DBQ=%s"), sDriver, sFilePath/*excel文件路径*/);
// 打开数据库(Excel文件)
if (database.OpenEx(sDsn, CDatabase::noOdbcDialog))
{
...
}
database.Close();
2、读取字段信息
将Sheet当做数据表进行读取,将第一行作为标题,也就是数据表字段。需要用到结构体CODBCFieldInfo,其定义如下:
struct CODBCFieldInfo
{
// meta data from ODBC
CString m_strName;
SWORD m_nSQLType;
SQLULEN m_nPrecision;
SWORD m_nScale;
SWORD m_nNullability;
};
读取字段信息代码如下:
CRecordset recset(&database);
CString sSql=_T("SELECT * FROM [Sheet1$]");
// 执行查询语句
if (recset.Open(CRecordset::forwardOnly, sSql, CRecordset::readOnly))
{
//获取字段总数
int nColCount = recset.GetODBCFieldCount();
CODBCFieldInfo fieldInfo;
for (int k = 0; k < nColCount ; k++)
{
recset.GetODBCFieldInfo(k, fieldInfo);
std::cout << k << ":" << fieldInfo.m_strName.Trim() << std::endl;
}
}
3、读取表中数据
读取数据使用CRecordset::GetFieldValue,该函数重载四种参数组合,可以以CString及CDBVariant类型输入数据。
void GetFieldValue(LPCTSTR lpszName, CDBVariant& varValue,
short nFieldType = DEFAULT_FIELD_TYPE);
void GetFieldValue(short nIndex, CDBVariant& varValue,
short nFieldType = DEFAULT_FIELD_TYPE);
void GetFieldValue(LPCTSTR lpszName, CString &strValue);
void GetFieldValue(short nIndex, CString &strValue);
可通过字段(列)名称获取当前行中对应字段的值,也可通过字段(列)序号获取字段值。
CRecordset recset(&database);
CString sSql=_T("SELECT * FROM [Sheet1$]");
CString sItem;// 执行查询语句
if (recset.Open(CRecordset::forwardOnly, sSql, CRecordset::readOnly))
{
int nRow = 0;
while (!recset.IsEOF())
{
for(int i = 0 ; i < nColCount ; i++)
{
//通过字段(列)序号获取字段值
recset.GetFieldValue(i, sItem);
std::cout << "第" <<nRow + 1 << "行第" << i + 1 << "列的值为:" << sItem << std::endl;
}
//通过字段(列)名称获取当前行中对应字段的值
recset.GetFieldValue(_T("Id"),sItem);
std::cout << "第" <<nRow + 1 << "行字段Id的值为:" << sItem << std::endl;
nRow++;
recset.MoveNext();
}
recset.Close();
}
四、写Excel文件
1、创建文件
与
打开Excel文件
一致,将sDsn连接字符串改为
sDsn.Format(_T("DRIVER={%s};DSN='Excel nFiles';READONLY=FALSE;DBQ=%s"), sDriver, sFilePath/*保存Excel文件路径*/)
2、创建Sheet即数据表
使用SQL语言表创建语句创建Sheet及列名,其中列名最好以反单引号括住,以免与SQL关键字混淆,造成运行错误
CString sSql = _T("CREATE TABLE Sheet1 (`Id` NUMBER,`name` TEXT,`age` NUMBER)");
database.ExecuteSQL(sSql);
3、写入数据
使用SQL语言INSERT语句进行数据写入,可带字段名对特定字段写入,也可对所有字段写入,代码如下:
//对指定字段写入
sSql = _T("INSERT INTO Sheet1 (`Id`,`name`) VALUES (1,'daivy')");
database.ExecuteSQL(sSql);
//对所有字段写入
sSql = _T("INSERT INTO Sheet1 VALUES (2,'tony',25)");
database.ExecuteSQL(sSql);
完整代码
CDatabase database;
CString sDsn;
CString sFilePath= _T("d:\\test.xlsx");
CString sDriver = GetExcelDriver();
//创建连接字符串
sDsn.Format(_T("DRIVER={%s};DSN='Excel nFiles';READONLY=FALSE;DBQ=%s"), sDriver, sFilePath/*excel文件保存路径*/);
// 创建或打开文件
if (database.OpenEx(sDsn, CDatabase::noOdbcDialog))
{
CString sSql = _T("CREATE TABLE Sheet1 (`Id` NUMBER,`name` TEXT,`age` NUMBER)");
database.ExecuteSQL(sSql);
//对指定字段写入
sSql = _T("INSERT INTO Sheet1 (`Id`,`name`) VALUES (1,'daivy')");
database.ExecuteSQL(sSql);
//对所有字段写入
sSql = _T("INSERT INTO Sheet1 VALUES (2,'tony',25)");
database.ExecuteSQL(sSql);
database.Close();
std::cout << "导出数据成功" << std::endl;
}