EternalWindows
ODBC / ODBCとExcel

ODBCはときとして、Excelファイルの読み書きに使用されることがあります。 これは、Excelファイルにおける表と、データベースにおけるテーブルが同じようなものであり、 アプリケーションから送られたSQL文をExcelにも適応できると考えた結果といえるでしょう。 つまり、Excel用のODBCドライバを用意しておくことで、 Excelファイルに対する要求(SQL文)をアプリケーションから受け取り、 その要求を基にODBCドライバがExcelファイルにアクセスするのです。 これにより、Excelファイルの内部構造がアプリケーションから隠蔽され、 SQL文さえ理解しておけばExcelファイルを読み書きできる利点が生じます。

システムにはデフォルトで、Microsoft Excel Driver (*.xls)というExcel用のドライバが用意されています。 よって、Excelファイルさえ用意しておけば、 いつでもODBCでExcelファイルを読み書きできるようになります。 例として、次のようなデータを持つExcelファイルを作成しておきます。

一行目に列名を記述し、二行目以降にデータを記述します。 Sheet1にこのテーブルを記述したのであれば、テーブル名は[Sheet1$]で表すことができます。 また、特定の範囲のみをテーブルと見なしたい場合は、[Sheet1$A1:B2]のように表すことができます。

ODBCによるExcelへのアクセス手順は、通常のデータベースと特に変わることはありません。 ただし、データベースのときのように、事前にデータソースを作成しておくことはあまりないため、 ドライバの接続に使用する関数は異なることになります。 具体的には、データソース名を受け取るSQLConnectではなく、 属性文字列を受け取るSQLDriverConnectを呼び出します。

SQLRETURN SQLDriverConnect(
  SQLHDBC ConnectionHandle,
  SQLHWND WindowHandle,
  SQLCHAR *InConnectionString,
  SQLSMALLINT StringLength1,
  SQLCHAR *OutConnectionString,
  SQLSMALLINT BufferLength,
  SQLSMALLINT *StringLength2Ptr,
  SQLUSMALLINT DriverCompletion
);

ConnectionHandleは、接続ハンドルを指定します。 WindowHandleは、ウインドウハンドルまたはNULLを指定します。 InConnectionStringは、接続に使用する属性文字列を指定します。 StringLength1は、SQL_NTSを指定します。 OutConnectionStringは、実際にドライバ内で処理された属性文字列を受け取るバッファを指定します。 BufferLengthは、OutConnectionStringのサイズを指定します。 StringLength2Ptrは、OutConnectionStringに格納された文字列のサイズを受け取る変数のアドレスを指定します。 DriverCompletionは、ドライバマネージャまたはドライバの動作に関する定数を指定します。 SQL_DRIVER_PROMPTを指定して、第2引数にウインドウハンドルを指定した場合はダイアログが表示されます。 ダイアログが不要な場合は、SQL_DRIVER_NOPROMPTを指定します。

今回のプログラムは、Excelファイルからデータを取得する処理とデータを追加する処理が含まれています。

#include <windows.h>
#include <sqlext.h>

BOOL ExecuteStatement(SQLHSTMT hstmt, BOOL bSelect);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR       szConnectionIn[] = TEXT("Driver={Microsoft Excel Driver (*.xls)};dbq=C:\\sample.xls;ReadOnly=0");
	TCHAR       szConnectionOut[1024];
	SQLHENV     henv;
	SQLHDBC     hdbc;
	SQLHSTMT    hstmt;
	SQLRETURN   nResult;
	SQLSMALLINT nSize;
	TCHAR       szState[6];
	TCHAR       szErrorMsg[1024];
	SQLINTEGER  nErrorCode;

	nResult = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv);
	if (nResult != SQL_SUCCESS && nResult != SQL_SUCCESS_WITH_INFO)
		return 0;
	
	nResult = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (void *)SQL_OV_ODBC3, 0);
	if (nResult != SQL_SUCCESS && nResult != SQL_SUCCESS_WITH_INFO) {
		SQLFreeHandle(SQL_HANDLE_ENV, henv);
		return 0;
	}

	nResult = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
	if (nResult != SQL_SUCCESS && nResult != SQL_SUCCESS_WITH_INFO) {
		SQLFreeHandle(SQL_HANDLE_ENV, henv);
		return 0;
	}

	nResult = SQLDriverConnect(hdbc, NULL, (SQLTCHAR *)szConnectionIn, SQL_NTS, (SQLTCHAR *)szConnectionOut, sizeof(szConnectionOut) / sizeof(TCHAR), &nSize, SQL_DRIVER_NOPROMPT);
	if (nResult != SQL_SUCCESS && nResult != SQL_SUCCESS_WITH_INFO) {
		SQLGetDiagRec(SQL_HANDLE_DBC, hdbc, 1, (SQLTCHAR *)szState, &nErrorCode, (SQLTCHAR *)szErrorMsg, sizeof(szErrorMsg) / sizeof(TCHAR), &nSize);
		MessageBox(NULL, szErrorMsg, NULL, MB_ICONWARNING);
		SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
		SQLFreeHandle(SQL_HANDLE_ENV, henv);
		return 0;
	}

	nResult = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);
	if (nResult != SQL_SUCCESS && nResult != SQL_SUCCESS_WITH_INFO) {
		SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
		SQLFreeHandle(SQL_HANDLE_ENV, henv);
		return 0;
	}

	if (!ExecuteStatement(hstmt, TRUE)) {
		SQLGetDiagRec(SQL_HANDLE_STMT, hstmt, 1, (SQLTCHAR *)szState, &nErrorCode, (SQLTCHAR *)szErrorMsg, sizeof(szErrorMsg) / sizeof(TCHAR), &nSize);
		MessageBox(NULL, szErrorMsg, NULL, MB_ICONWARNING);
	}
	else
		MessageBox(NULL, TEXT("終了します。"), TEXT("OK"), MB_OK);

	SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
	SQLDisconnect(hdbc);
	SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
	SQLFreeHandle(SQL_HANDLE_ENV, henv);

	return 0;
}

BOOL ExecuteStatement(SQLHSTMT hstmt, BOOL bSelect)
{
	TCHAR       szBuf[1024];
	TCHAR       szCol1[256];
	SQLSMALLINT nCol2;
	SQLLEN      nColLen1;
	SQLLEN      nColLen2;
	SQLRETURN   nResult;

	if (bSelect) {
		nResult = SQLExecDirect(hstmt, (SQLTCHAR *)TEXT("SELECT * FROM [Sheet1$]"), SQL_NTS);
		if (nResult != SQL_SUCCESS && nResult != SQL_SUCCESS_WITH_INFO)
			return FALSE;

		SQLBindCol(hstmt, 1, SQL_C_TCHAR, szCol1, sizeof(szCol1), &nColLen1);
		SQLBindCol(hstmt, 2, SQL_C_SSHORT, &nCol2, sizeof(nCol2), &nColLen2);

		for (;;) {
			nResult = SQLFetch(hstmt);
			if (nResult == SQL_SUCCESS || nResult == SQL_SUCCESS_WITH_INFO) {
				wsprintf(szBuf, TEXT("%s, %d"), szCol1, nCol2);
				MessageBox(NULL, szBuf, TEXT("OK"), MB_OK);
			}
			else
				break;
		}
	}
	else {
		nResult = SQLExecDirect(hstmt, (SQLTCHAR *)TEXT("INSERT INTO [Sheet1$] VALUES('melon', 300)"), SQL_NTS);
		if (nResult != SQL_SUCCESS && nResult != SQL_SUCCESS_WITH_INFO)
			return FALSE;
	}
	
	return TRUE;
}

SQLDriverConnectの第3引数に指定する文字列は、各属性を;で連結したものになります。 DriverにはExcel用のODBCドライバの名前を指定し、dbqにはExcelファイルのフルパスを指定します。 ReadOnlyに0を指定した場合は、データの追加や更新が可能になるため、 こうした動作を行う場合はReadOnlyを0にする必要があります。

ExecuteStatementの第2引数がTRUEの場合は、SELECT文でデータを取得し、 FALSEの場合はINSERT文でデータを追加します。 テーブル名を指定する部分には、既に述べたように[Sheet1$]のように指定します。 このテーブルの2列目は数値が格納されることになっていたため、 2列目に対するSQLBindColの呼び出しでは、第3引数にSQL_C_SSHORTという数値型を指定します。 SQL_C_TCHARを指定して文字列として取得することも可能ではありますが、 この場合は小数点が文字列に含まれることになります。


戻る