EternalWindows
ODBC / データの取得

テーブルからデータを取得する際に使用するSQLはSELECT文です。 これをSQLExecDirectに指定した場合、取得した一連のレコードが結果セットとして内部的に格納されることになります。 この結果セットから1つのレコードを参照するには、SQLFetchを呼び出します。

SQLRETURN SQLFetch(
  SQLHSTMT StatementHandle
);

StatementHandleは、ステートメントハンドルを指定します。

SQLFetchを呼び出すと、ODBCドライバ内部に存在するカーソルが1つのレコードを指すようになります。 このレコードからデータを取得するには、SQLGetDataを呼び出します。

SQLRETURN SQLGetData(
  SQLHSTMT StatementHandle,
  SQLUSMALLINT ColumnNumber,
  SQLSMALLINT TargetType,
  SQLPOINTER TargetValuePtr,
  SQLLEN BufferLength,
  SQLLEN *StrLen_or_IndPtr
);

StatementHandleは、ステートメントハンドルを指定します。 ColumnNumberは、データを取得する列のインデックスを指定します。 このインデックスは1から始まります。 TargetTypeは、データをどのような形式で受け取るかを示す定数を指定します。 TargetValuePtrは、データを受け取るバッファを指定します。 BufferLengthは、TargetValuePtrのサイズを指定します。 StrLen_or_IndPtrは、TargetValuePtrに格納されたデータのサイズを受け取る変数のアドレスを指定します。

次に、SQLFetchとSQLGetDataを呼び出す例を示します。

TCHAR  szBuf[1024];
TCHAR  szCol1[256];
TCHAR  szCol2[256];
SQLLEN nColLen1;
SQLLEN nColLen2;

for (;;) {
	nResult = SQLFetch(hstmt);
	if (nResult == SQL_SUCCESS || nResult == SQL_SUCCESS_WITH_INFO) {
		SQLGetData(hstmt, 1, SQL_C_TCHAR, szCol1, sizeof(szCol1), &nColLen1);
		SQLGetData(hstmt, 2, SQL_C_TCHAR, szCol2, sizeof(szCol2), &nColLen2);
		wsprintf(szBuf, TEXT("%s, %s"), szCol1, szCol2);
		MessageBox(NULL, szBuf, TEXT("OK"), MB_OK);
	}
	else
		break;
}

SQLFetchを繰り返す呼び出すことによって、結果セットの全てのレコードを参照できるようになります。 関数が成功した場合は、SQLGetDataにデータを取得したい列のインデックスを指定します。 1回目の呼び出しでは1列目のデータを取得するため第2引数に1を指定し、 2回目の呼び出しでは2列目のデータを取得するため第2引数に2を指定しています。 第3引数のSQL_C_TCHARは、データを文字列として取得することを意味します。 SQLFetchが失敗した場合は、もうこれ以上参照するレコードが存在しないことを意味するため、 ループ文を抜けることになります。

上記のようなデータを取得する度にSQLGetDataを呼び出す設計は、実行速度という面であまり好ましいことではありません。 単純に考えて、レコードの数が増えれば増えるほど、SQLGetDataの呼び出しも増えるからです。 そもそも、SQLFetchを呼び出すのは、レコードからデータを取得するのが目的なわけですから、 この関数を呼び出した時点でデータを取得できる方法が存在してもよいはずです。 実は、次に示すSQLBindColを呼び出して変数のアドレスをバインドしておけば、 SQLFetchが成功した際にその変数へデータが格納されることになっています。 つまり、SQLGetDataのように明示的にデータを取得する必要がなくなります。

SQLRETURN SQLBindCol(
  SQLHSTMT StatementHandle,
  SQLUSMALLINT ColumnNumber,
  SQLSMALLINT TargetType,
  SQLPOINTER TargetValuePtr,
  SQLLEN BufferLength,
  SQLLEN *StrLen_or_Ind
);

StatementHandleは、ステートメントハンドルを指定します。 ColumnNumberは、データを取得する列のインデックスを指定します。 このインデックスは1から始まります。 TargetTypeは、データをどのような形式で受け取るかを示す定数を指定します。 TargetValuePtrは、データを受け取るバッファを指定します。 BufferLengthは、TargetValuePtrのサイズを指定します。 StrLen_or_IndPtrは、TargetValuePtrに格納されたデータのサイズを受け取る変数のアドレスを指定します。

今回のプログラムは、前節で作成したfruitというテーブルのレコードを表示します。

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

BOOL ExecuteStatement(SQLHSTMT hstmt);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	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 = SQLConnect(hdbc, (SQLTCHAR *)TEXT("sample_dsn"), SQL_NTS, (SQLTCHAR *)TEXT(""), SQL_NTS, (SQLTCHAR *)TEXT(""), SQL_NTS);
	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)) {
		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(hstmt);
	SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
	SQLFreeHandle(SQL_HANDLE_ENV, henv);

	return 0;
}

BOOL ExecuteStatement(SQLHSTMT hstmt)
{
	TCHAR     szBuf[1024];
	TCHAR     szCol1[256];
	TCHAR     szCol2[256];
	SQLLEN    nColLen1;
	SQLLEN    nColLen2;
	SQLRETURN nResult;
	
	nResult = SQLExecDirect(hstmt, (SQLTCHAR *)TEXT("SELECT * FROM fruit"), 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_TCHAR, szCol2, sizeof(szCol2), &nColLen2);

	for (;;) {
		nResult = SQLFetch(hstmt);
		if (nResult == SQL_SUCCESS || nResult == SQL_SUCCESS_WITH_INFO) {
			wsprintf(szBuf, TEXT("%s, %s"), szCol1, szCol2);
			MessageBox(NULL, szBuf, TEXT("OK"), MB_OK);
		}
		else
			break;
	}
	
	return TRUE;
}

前節で述べたように、SQLの実行はSQLExecDirectで行うことになります。 今回は、データを取得することが目的であるため、第2引数にはSELECT文を指定しています。 SELECTという文字列の後の*は、全ての列のデータを取得することを意味し、 FROMの後の文字列は実行対象となるテーブルの名前です。 SELECT文によって取得したレコードの数は、SQLRowCountを呼び出すことで取得できます。 SQLBindColとSQLFetchの呼び出しは、次のようになっています。

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

for (;;) {
	nResult = SQLFetch(hstmt);
	if (nResult == SQL_SUCCESS || nResult == SQL_SUCCESS_WITH_INFO) {
		wsprintf(szBuf, TEXT("%s, %s"), szCol1, szCol2);
		MessageBox(NULL, szBuf, TEXT("OK"), MB_OK);
	}
	else
		break;
}

SELECT文で取得したレコードは結果セットとしてODBCドライバ内部に格納され、SQLFetchでこれを参照することができます。 このとき、事前にSQLBindColを呼び出しておけば、SQLFetchを呼び出した時点でデータを取得することができます。 SQLBindColの引数の意味はSQLGetDataとほぼ同じですが、 第4引数に指定したバッファが初期化されるのは、SQLFetchが成功した際となります。

列情報の取得

SQLFetchを呼び出すことで、デーブル内のデータを取得できることが分かりましたが、 それではテーブルの列情報を取得するにはどうすればよいのでしょうか。 これには、SQLDescribeColを呼び出すことになります。 次に、例を示します。

BOOL ExecuteStatement(SQLHSTMT hstmt)
{
	SQLSMALLINT i;
	SQLSMALLINT nColCount;
	SQLSMALLINT nSqlType;
	SQLSMALLINT nNullable;
	SQLSMALLINT nScale;
	SQLSMALLINT nBufferSize;
	SQLULEN     nColSize;
	SQLRETURN   nResult;
	TCHAR       szColName[256];
	TCHAR       szBuf[256];
	
	nResult = SQLExecDirect(hstmt, (SQLTCHAR *)TEXT("SELECT * FROM fruit"), SQL_NTS);
	if (nResult != SQL_SUCCESS && nResult != SQL_SUCCESS_WITH_INFO)
		return FALSE;
	
	SQLNumResultCols(hstmt, &nColCount);
	
	for (i = 0; i < nColCount; i++) {
		SQLDescribeCol(hstmt, i + 1, szColName, sizeof(szColName) / sizeof(TCHAR), &nBufferSize, &nSqlType, &nColSize, &nScale, &nNullable); 
		wsprintf(szBuf, TEXT("%s, %d, %d, %d, %d"), szColName, nSqlType, nColSize, nScale, nNullable);
		MessageBox(NULL, szBuf, TEXT("OK"), MB_OK);
	}

	return TRUE;
}

最初に、SELECT文を実行して結果セットを作成しておきます。 結果セットにおけるレコードの列数は、SQLNumResultColsを取得できるため、 この数だけSQLDescribeColを呼び出すことになります。 第2引数は、取得する列のインデックスであり、これは1からカウントされます。 第3引数は、列名を受け取るバッファであり、第4引数はそのサイズ、 第5引数はバッファに格納されたサイズを受け取るアドレスです。 第6引数はSQLにおけるデータ型であり、たとえばint型であればSQL_INTEGERが格納されます。 第7引数は、列名のサイズを受けとるアドレスであり、 たとえばデータがvarchar(20)である場合は、20が格納されます。 第8引数は小数点情報が格納され、第9引数は列にNULLを指定できるかどうかを示す定数が格納されます。 NULLを指定できる場合はSQL_NULLABLEとなり、NULLを指定できない場合はSQL_NO_NULLSとなります。



戻る