EternalWindows
ODBC / プリペアドステートメントの実行

プリペアドステートメントとは、SQLを実行できるように準備したステートメントのことを意味します。 SQLExecDirectを呼び出した場合、SQLステートメントを解析する作業と実際に実行する作業が行われるわけですが、 この解析する作業を事前に行っておくのです。 このようにすれば、同じSQLを何度も実行するような場合でも解析作業は1回で済むことになり、 パフォーマンスの低下を防ぐことができます。 SQLを実行できるように準備するには、SQLPrepareを呼び出します。

SQLRETURN SQLPrepare(
  SQLHSTMT StatementHandle,
  SQLCHAR *StatementText,
  SQLINTEGER TextLength
);

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

プリペアドステートメントの利点の1つとして、 SQLのパラメータを動的に指定できる点が挙げられます。 たとえば、特定の値を持つレコードのみを取得したい場合、 その値をハードコードしてしまっては柔軟性がありませんから、 値だけを別個指定する方法が用意されています。 これには、SQLBindParameterを呼び出します。

SQLRETURN SQLBindParameter(
  SQLHSTMT StatementHandle,
  SQLUSMALLINT ParameterNumber,
  SQLSMALLINT InputOutputType,
  SQLSMALLINT ValueType,
  SQLSMALLINT ParameterType,
  SQLULEN ColumnSize,
  SQLSMALLINT DecimalDigits,
  SQLPOINTER ParameterValuePtr,
  SQLINTEGER BufferLength,
  SQLLEN *StrLen_or_IndPtr
);

StatementHandleは、ステートメントハンドルを指定します。 ParameterNumberは、バインドしたいパラメータのインデックスを指定します。 これは、1から始まります。 InputOutputTypeは、パラメータをどのように使用するかを表す定数を指定します。 入力目的で使用する場合は、SQL_PARAM_INPUTを指定します。 ValueTypeは、パラメータのCデータタイプを指定します。 ParameterTypeは、パラメータのSQLデータタイプを指定します。 ColumnSizeは、ParameterTypeの値によって意味が異なります。 SQL_CHARのような文字列型を指定した場合は文字列の長さを指定しますが、 SQL_INTEGERのような整数型を指定した場合は0で問題ありません。 DecimalDigitsは、ParameterTypeにSQL_DECIMALやSQL_NUMERICのような小数点型を指定した場合に意味を持ちます。 ParameterValuePtrは、パラメータに対する入力値を格納した変数、 または出力値を受け取る変数のアドレスを指定します。 BufferLengthは、ParameterValuePtrのサイズを指定しますが、0でも問題ないように思えます。 StrLen_or_IndPtrは、初期化済みの変数のアドレスを指定します。 ParameterTypeに整数型を指定した場合は0を格納し、 文字列型を指定した場合はSQL_NTSを格納しておきます。

プリペアドステートメントを実行するには、SQLExecuteを呼び出します。

SQLRETURN SQLExecute(
  SQLHSTMT StatementHandle
);

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

今回のプログラムは、プリペアドステートメントを使用する例を示しています。

#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      nCol1;
	SQLLEN      nCol2;
	SQLRETURN   nResult;
	SQLSMALLINT nParameter;
	SQLINTEGER  nParameterSize;
	
	nResult = SQLPrepare(hstmt, (SQLTCHAR *)TEXT("SELECT * FROM fruit WHERE price = ?"), SQL_NTS);
	if (nResult != SQL_SUCCESS && nResult != SQL_SUCCESS_WITH_INFO)
		return FALSE;
	
	nParameter = 100;
	SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_SSHORT, SQL_INTEGER, 0, 0, &nParameter, 0, &nParameterSize);

	nResult = SQLExecute(hstmt);
	if (nResult != SQL_SUCCESS && nResult != SQL_SUCCESS_WITH_INFO)
		return FALSE;
	
	SQLBindCol(hstmt, 1, SQL_C_TCHAR, szCol1, sizeof(szCol1), &nCol1);
	SQLBindCol(hstmt, 2, SQL_C_TCHAR, szCol2, sizeof(szCol2), &nCol2);

	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;
}

SQLPrepareの第2引数には、準備したいSQLステートメントを指定します。 今回はSELECT文にWHERE句を指定することで、特定の条件を満たすレコードのみを取得しようとしています。 price = 100となっていれば、priceという列が100であるレコードが取得されることになりますが、 ?の場合は一体どうなるのでしょうか。 実は、これが動的に指定できるパラメータの指標です。 つまり、SQLBindParameterを呼び出すことによって、 この?に値を指定することができます。

nParameter = 100;
SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_SSHORT, SQL_INTEGER, 0, 0, &nParameter, 0, &nParameterSize);

第2引数の1は、1番目の?にパラメータを指定することを意味します。 第3引数は、パラメータをどのような目的で使用するかどうかです。 今回は、パラメータを指定するという入力目的で使用するため、 SQL_PARAM_INPUTを指定します。 第4引数はC言語におけるデータタイプです。 第8引数に指定しているパラメータはSQLSMALLINT型(16バイト)であるため、 C言語における16バイトの型を表すSQL_C_SSHORTを指定します。 第5引数は、SQLにおけるデータタイプであり、 パラメータの型がSQLSMALLINTであることから、SQL_SMALLINTを指定します。

SQLBindParameterでパラメータのバインドを終えたら、 SQLExecuteでプリペアドステートメントを実行します。 この際には?にnParameterの100が指定されて実行されますから、 priceが100であるレコードのみを取得できるようになります。

ストアドプロシージャについて

ストアドプロシージャとは、SQLにおける一連の処理に名前を付けてDBMSに保存したものです。 この名前を指定すれば、複雑なSQL文を記述することなく目的の処理を実行できるため、 よく使用する処理などはストアドプロシージャとして保存しておくとよいでしょう。 次に、MySQLにおけるストアドプロシージャの作成例を示します。

nResult = SQLExecDirect(hstmt, (SQLTCHAR *)TEXT(
	"CREATE PROCEDURE proc(n int) BEGIN SELECT * FROM fruit WHERE price = n; END"
	), SQL_NTS);

// nResult = SQLExecDirect(hstmt, (SQLTCHAR *)TEXT("DROP PROCEDURE proc"), SQL_NTS);

CREATE PROCEDUREという語句のあとには、プロシージャの名前を指定します。 その後の括弧の中は、n intとなっていますが、これはint型のnという引数です。 実は、ストアドプロシージャはC言語の関数のように引数を要求できるため、 SQL文の実行結果は引数の値によって変化することになります。 実行したいSQL文は、BEGINとENDの値に指定するようにします。 上記したストアドプロシージャは、次のように実行することができます。

nResult = SQLExecDirect(hstmt, (SQLTCHAR *)TEXT("CALL proc(100)"), SQL_NTS);

CALLという語句の後に作成したストアドプロシージャの名前を指定し、括弧の中に引数を指定します。 引数を外部から取得する場合は、今回取り上げたプリペアドステートメントを使用します。



戻る