2013-09-03 44 views
11

簡潔バージョンのように、変数、およびクエリをバインド数字の配列(配列のサイズは異なります)を非PL/SQL SELECT文にバインドし、結果の配列をWHERE ID IN (...)スタイル・チェックで使用します。OracleのOCI、ID IN(1、2、3)

http://rafudb.blogspot.com/2011/10/variable-inlist.html

オリジナル質問:

私たちは、OCI経由でOracleに話しC++アプリを持っています。テキストを連結してSQLクエリを生成する古いコードを修正しようとしています。代わりに、可能な限りバインド変数を使用したいと考えています。私たちが良い解決策を持っていないという特別なケースが出てきました。 (1, 4, 10, 30, 93)部分はvector<int>またはデータのいくつかの他の柔軟なサイズの容器から来る

SELECT * FROM MyTable WHERE ID IN (1, 4, 10, 30, 93) 

しかし、それは1つのエントリでも、10個の場合もあれば、ゼロの場合もあります。明らかに、文字列としてクエリを構築する場合、必要なだけ多くの数値を追加することができますが、可能な場合はそれを回避し、変数をバインドするだけです。

これを行うには良い方法がありますか?たとえば、OCIでは、配列をバインドしてサブセレクションを選択できますか?

SELECT * FROM MyTable WHERE ID IN (SELECT * FROM :1) 

ここで、:1はOCI配列ですか? (おそらく構文は異なるでしょう)誰もこれを経験していますか?私が生のOCIを書くのに苦労する傾向があるので、サンプルコードは神の贈りものになるでしょう。ありがとう:)

EDIT:可能であれば、PL/SQLプロシージャによって解析された文字列にバインドするよりもやりたいです。私は、多くの場合4000文字の制限を吹き飛ばすと確信しています。また、私が快適であるような文字列操作の1つを取引しているように感じます。簡単にデバッグ)。可能であれば、値の配列(または何らかの形式のデータセット)を1つの標準SQL文にバインドしたいと思います。

EDIT 2:いくつかの調査は、私が欲しいものだけでやっているように見える次のリンクを上げますが、Javaで:http://rafudb.blogspot.com/2011/10/variable-inlist.html誰もがC++ OCIにこのアプローチを適応させる方法を知っていますか?

+0

http://asktom.oracle.com/pls/apex/f?p=100:11:0::::P11_QUESTION_ID:110612348061 asktomに精通していない場合は、リソース。 – OldProgrammer

+0

これは簡単ではありませんが、OCIで実行可能です。これらのコレクション・レベル操作は、OCI参照では十分に文書化されていません。 Oracleサーバー全体(デモを含む)をインストールし、OCIデモ・ソースを読み込みました。しかし、とにかくOCI Collectionを 'TABLE'に最初にキャストしなければなりません。 Oracleのクエリはコレクションから直接読み取ることはできません。 – ibre5041

+1

Ivan、有望なサウンド - 特定のデモに向けて私を指揮したり、コードスニペットを提供したりできますか?ありがとう:) – StilesCrisis

答えて

12

この例では、データベースで定義されたコレクション型を使用してパラメータのリストを渡す方法を示しています。
SYS.ODCINumberListは、すべてのユーザーが利用できる標準コレクションタイプです。 クエリは、最初の100個の整数(test)を選択してから、この整数をIN(...)節のリストでフィルタリングします。

#include "stdafx.h" 
#include <iostream> 
#include <occi.h> 

using namespace oracle::occi; 
using namespace std; 

// Vector type to pass as parameter list 
typedef vector<Number> ValueList; 

int _tmain(int argc, _TCHAR* argv[]) 
{ 
    Environment *env; 
    Connection *con; 

    // Note that Environment must be initialized in OBJECT mode 
    // to use collection mapping features. 
    env = Environment::createEnvironment(Environment::OBJECT); 

    con = env->createConnection ("test_user", "test_password", "ORACLE_TNS_NAME"); 

    try { 

    Statement *stmt = con->createStatement(
       "select * from " 
       " (select level as col from dual connect by level <= 100)" 
       "where " 
       " col in (select column_value from table(:key_list))" 
       ); 

    cout << endl << endl << "Executing the block :" << endl 
     << stmt->getSQL() << endl << endl; 

    // Create instance of vector trype defined above 
    // and populate it with numbers. 
    ValueList value_list; 
    value_list.push_back(Number(10)); 
    value_list.push_back(Number(20)); 
    value_list.push_back(Number(30)); 
    value_list.push_back(Number(40)); 

    // Bind vector to parameter #1 in query and treat it as SYS.ODCINumberList type. 
    setVector(stmt, 1, value_list, "SYS", "ODCINUMBERLIST"); 

    ResultSet *rs = stmt->executeQuery(); 

    while(rs->next()) 
     std::cout << "value: " << rs->getInt(1) << std::endl; 

    stmt->closeResultSet(rs); 
    con->terminateStatement (stmt); 

    } catch(SQLException ex) { 
    cout << ex.what(); 
    } 


    env->terminateConnection (con); 
    Environment::terminateEnvironment (env); 

    return 0; 
} 

あなたはOCI経由でOracleに数値、日付または文字列のリストを渡したり、DBで独自の型を定義するためにvarious ODCIxxxList typesを使用することができます。

Visual Studio 10 Expressとthis version of OCI librariesでコンパイルされた例。 Oracle 11.2.0.3.0に対してテスト済みです。

更新以下

は同じことを行いますが、プレーンC OCIxxx機能を備えたアプリケーション例です。

// 
// OCI collection parameters binding - example application 
// 

#include "stdafx.h" 
#include <iostream> 
#include <oci.h> 
#include <oro.h> 

using namespace std; 

// connection parameters 
const char *db_alias   = "ORACLE_DB_ALIAS"; 
const char *db_user_name  = "test_user"; 
const char *db_user_password = "test_password"; 

// helper error checking procedure to shorten main code, returns true if critical error detected 
// and prints out error information 
bool check_oci_error(char *error_point, OCIError *errhp, sword status, OCIEnv *envhp); 

int _tmain(int argc, _TCHAR* argv[]) { 

    //----- CONNECTION INITIALIZATION PART ------------------------------------------------------ 

    sword rc; 
    OCIEnv *myenvhp;  /* the environment handle */ 
    OCIServer *mysrvhp; /* the server handle */ 
    OCIError *myerrhp;  /* the error handle */ 
    OCISession *myusrhp; /* user session handle */ 
    OCISvcCtx *mysvchp; /* the service handle */ 

    /* initialize the mode to be the threaded and object environment */ 
    /* NOTE: OCI_OBJECT must be present to work with object/collection types */ 
    rc = OCIEnvCreate(&myenvhp, OCI_THREADED|OCI_OBJECT, (dvoid *)0, 0, 0, 0, (size_t) 0, (dvoid **)0); 

    if(check_oci_error("OCIEnvCreate", NULL, rc, NULL)) { 
    return -1; 
    } 

    /* allocate a server handle */ 
    rc = OCIHandleAlloc ((dvoid *)myenvhp, (dvoid **)&mysrvhp, OCI_HTYPE_SERVER, 0, (dvoid **) 0); 
    if(check_oci_error("OCIHandleAlloc(OCI_HTYPE_SERVER)", NULL, rc, myenvhp)) return -1; 

    /* allocate an error handle */ 
    rc = OCIHandleAlloc ((dvoid *)myenvhp, (dvoid **)&myerrhp, OCI_HTYPE_ERROR, 0, (dvoid **) 0); 
    if(check_oci_error("OCIHandleAlloc(OCI_HTYPE_ERROR)", NULL, rc, myenvhp)) return -1; 

    /* create a server context */ 
    rc = OCIServerAttach(mysrvhp, myerrhp, (text *)db_alias, strlen (db_alias), OCI_DEFAULT); 
    if(check_oci_error("OCIServerAttach()", myerrhp, rc, myenvhp)) return -1; 

    /* allocate a service handle */ 
    rc = OCIHandleAlloc ((dvoid *)myenvhp, (dvoid **)&mysvchp, OCI_HTYPE_SVCCTX, 0, (dvoid **) 0); 
    if(check_oci_error("OCIHandleAlloc(OCI_HTYPE_SVCCTX)", myerrhp, rc, myenvhp)) return -1; 

    /* set the server attribute in the service context handle*/ 
    rc = OCIAttrSet((dvoid *)mysvchp, OCI_HTYPE_SVCCTX, (dvoid *)mysrvhp, (ub4) 0, OCI_ATTR_SERVER, myerrhp); 
    if(check_oci_error("OCIAttrSet(OCI_HTYPE_SVCCTX,OCI_ATTR_SERVER)", myerrhp, rc, myenvhp)) return -1; 

    /* allocate a user session handle */ 
    rc = OCIHandleAlloc((dvoid *)myenvhp, (dvoid **)&myusrhp, OCI_HTYPE_SESSION, 0, (dvoid **) 0); 
    if(check_oci_error("OCIHandleAlloc(OCI_HTYPE_SESSION)", myerrhp, rc, myenvhp)) return -1; 

    /* set user name attribute in user session handle */ 
    rc = OCIAttrSet((dvoid *)myusrhp, OCI_HTYPE_SESSION, (dvoid *)db_user_name, strlen(db_user_name), OCI_ATTR_USERNAME, myerrhp); 
    if(check_oci_error("OCIAttrSet(OCI_HTYPE_SESSION,OCI_ATTR_USERNAME)", myerrhp, rc, myenvhp)) return -1; 

    /* set password attribute in user session handle */ 
    rc = OCIAttrSet((dvoid *)myusrhp, OCI_HTYPE_SESSION, (dvoid *)db_user_password, strlen(db_user_password), OCI_ATTR_PASSWORD, myerrhp); 
    if(check_oci_error("OCIAttrSet(OCI_HTYPE_SESSION,OCI_ATTR_PASSWORD)", myerrhp, rc, myenvhp)) return -1; 

    rc = OCISessionBegin(mysvchp, myerrhp, myusrhp, OCI_CRED_RDBMS, OCI_DEFAULT); 
    if(check_oci_error("OCISessionBegin()", myerrhp, rc, myenvhp)) return -1; 

    /* set the user session attribute in the service context handle*/ 
    rc = OCIAttrSet((dvoid *)mysvchp, OCI_HTYPE_SVCCTX, (dvoid *)myusrhp, (ub4) 0, OCI_ATTR_SESSION, myerrhp); 
    if(check_oci_error("OCIAttrSet(OCI_HTYPE_SVCCTX,OCI_ATTR_SESSION)", myerrhp, rc, myenvhp)) return -1; 

    cout << endl << "Initialization done." << endl; 

    //----- REGISTER TYPE INFORMATION ------------------------------------------------------ 

    // This section can be invoked once per session to minimize server roundtrips. 

    char *type_owner_name = "SYS";    
    char *type_name  = "ODCINUMBERLIST"; 
    OCIType *type_tdo  = NULL; 

    rc= OCITypeByName(
     myenvhp, myerrhp, mysvchp, 
     (CONST text *)type_owner_name, strlen(type_owner_name), 
     (CONST text *) type_name, strlen(type_name), 
     NULL, 0, 
     OCI_DURATION_SESSION, OCI_TYPEGET_HEADER, 
     &type_tdo 
    ); 
    if(check_oci_error("OCITypeByName()", myerrhp, rc, myenvhp)) return -1; 

    //----- PREPARE PARAMETER INSTANCE --------------------------------------------- 

    OCIArray *array_param = NULL; 

    rc = OCIObjectNew(
     myenvhp, myerrhp, mysvchp, 
     OCI_TYPECODE_VARRAY, 
     type_tdo, NULL, OCI_DURATION_SESSION, TRUE, 
     (void**) &array_param 
     ); 
    if(check_oci_error("OCITypeByName()", myerrhp, rc, myenvhp)) return -1; 

    //----- FILL PARAMETER --------------------------------------------------------- 

    OCINumber num_val; 
    int  int_val; 

    for(int i = 1; i <= 3; i++) { 
    int_val = i*10; 

    rc = OCINumberFromInt(myerrhp, &int_val, sizeof(int_val), OCI_NUMBER_SIGNED, &num_val); 
    if(check_oci_error("OCINumberFromInt()", myerrhp, rc, myenvhp)) return -1; 

    rc = OCICollAppend(myenvhp, myerrhp, &num_val, NULL, array_param); 
    if(check_oci_error("OCICollAppend()", myerrhp, rc, myenvhp)) return -1; 
    } 


    //----- BIND PARAMETER VALUE AND EXECUTE STATEMENT ------------------------------ 

    OCIStmt *mystmthp = NULL; 
    OCIDefine *col1defp = NULL; 
    double col1value; 
    OCIBind *bndp  = NULL; 

    char  *query_text = "select * from " 
          " (select level as col from dual connect by level < 100)" 
          "where " 
          " col in (select column_value from table(:key_list))"; 

    rc = OCIHandleAlloc(myenvhp, (void **)&mystmthp, OCI_HTYPE_STMT, 0, NULL); 
    if(check_oci_error("OCIHandleAlloc(OCI_HTYPE_STMT)", myerrhp, rc, myenvhp)) return -1; 

    rc = OCIStmtPrepare( 
     mystmthp, myerrhp, 
     (const OraText *)query_text, strlen(query_text), 
     OCI_NTV_SYNTAX, OCI_DEFAULT 
     ); 
    if(check_oci_error("OCIStmtPrepare()", myerrhp, rc, myenvhp)) return -1; 

    // result column 
    rc = OCIDefineByPos(mystmthp, &col1defp, myerrhp, 1, &col1value, sizeof(col1value), SQLT_BDOUBLE, NULL, NULL, NULL, OCI_DEFAULT); 
    if(check_oci_error("OCIDefineByPos()", myerrhp, rc, myenvhp)) return -1; 

    // parameter collection 
    rc = OCIBindByName(
     mystmthp, &bndp, myerrhp, 
     (text *)":key_list", strlen(":key_list"), 
     NULL, 0, 
     SQLT_NTY, NULL, 0, 0, 0, 0, 
     OCI_DEFAULT 
     ); 
    if(check_oci_error("OCIBindByName()", myerrhp, rc, myenvhp)) return -1; 

    rc = OCIBindObject(
     bndp, myerrhp, 
     type_tdo, (dvoid **) &array_param, 
     NULL, NULL, NULL 
     ); 
    if(check_oci_error("OCIBindByName()", myerrhp, rc, myenvhp)) return -1; 

    // execute and fetch 
    rc = OCIStmtExecute(mysvchp, mystmthp, myerrhp, 0, 0, NULL, NULL, OCI_DEFAULT); 
    if(check_oci_error("OCIBindByName()", myerrhp, rc, myenvhp)) return -1; 

    rc = OCIStmtFetch2(mystmthp, myerrhp, 1, OCI_FETCH_NEXT, 0, OCI_DEFAULT); 

    while(rc != OCI_NO_DATA) { 
    if(check_oci_error("OCIStmtFetch2()", myerrhp, rc, myenvhp)) return -1; 
    cout << "value: " << col1value << endl; 
    rc = OCIStmtFetch2(mystmthp, myerrhp, 1, OCI_FETCH_NEXT, 0, OCI_DEFAULT); 
    } 

    // free collection object parameter 
    rc = OCIObjectFree(myenvhp, myerrhp, array_param, OCI_OBJECTFREE_FORCE); 
    if(check_oci_error("OCIObjectFree()", myerrhp, rc, myenvhp)) return -1; 

    cout << endl << "Main test done." << endl; 

    //------- FINALIZATION ----------------------------------------------------------- 
    rc= OCISessionEnd(mysvchp, myerrhp, myusrhp, OCI_DEFAULT); 
    if(check_oci_error("OCISessionEnd()", myerrhp, rc, myenvhp)) return -1; 

    rc = OCIServerDetach(mysrvhp, myerrhp, OCI_DEFAULT); 
    if(check_oci_error("OCIServerDetach()", myerrhp, rc, myenvhp)) return -1; 

    OCIHandleFree(myenvhp, OCI_HTYPE_ENV); 

    cout << endl << "Finalization done." << endl; 

    return 0; 
} 

// helper error checking procedure to shorten main code, returns true if critical error detected 
// and prints out error information 
bool check_oci_error(char *error_point, OCIError *errhp, sword status, OCIEnv *envhp) { 

    text errbuf[1024]; 
    sb4 errcode; 
    bool ret_code = true; 

    switch (status) { 
    case OCI_SUCCESS: 
     ret_code = false; 
     break; 
    case OCI_SUCCESS_WITH_INFO: 
     OCIErrorGet ((dvoid *) errhp, (ub4) 1, (text *) NULL, &errcode, errbuf, (ub4) sizeof(errbuf), (ub4) OCI_HTYPE_ERROR); 
     cout << error_point << " Error: OCI_SUCCESS_WITH_INFO; Info: " << errbuf << endl; 
     ret_code = (errcode == 436 || errcode == 437 || errcode == 438 || errcode == 439); 
     break; 
    case OCI_NEED_DATA: 
     cout << error_point << " Error: OCI_NEED_DATA"<< endl; 
     break; 
    case OCI_NO_DATA: 
     cout << error_point << " Error: OCI_NO_DATA"<< endl; 
     break; 
    case OCI_ERROR: 
     OCIErrorGet ((dvoid *) errhp, (ub4) 1, (text *) NULL, &errcode, errbuf, (ub4) sizeof(errbuf), (ub4) OCI_HTYPE_ERROR); 
     cout << error_point << " Error: " << errbuf << endl; 
     break; 
    case OCI_INVALID_HANDLE: 
     cout << error_point << " Error: OCI_INVALID_HANDLE" << endl; 
     break; 
    case OCI_STILL_EXECUTING: 
     cout << error_point << " Error: OCI_STILL_EXECUTE"<< endl; 
     break; 
    case OCI_CONTINUE: 
     cout << error_point << " Error: OCI_CONTINUE" << endl; 
     break; 
    default: 
     cout << error_point << " Error: UNKNOWN(" << status << ")" << endl; 
     break; 
    } 

    if(ret_code && (envhp != NULL)) OCIHandleFree(envhp, OCI_HTYPE_ENV); 

    return ret_code; 

} 

P.S.オラクルのドキュメントおよびthis example codeから情報を入手できます。

+0

これは非常に有望であり、私が月曜日に仕事に戻るときにそれを試してみます。今のところupvoteを持っています。 私たちのコードベースはC++ですが、OCIの既存の使用法は、 ''のバニラCインタフェースを使用しています。 'setVector(stmt、1、value_list、" SYS "、" ODCINUMBERLIST ")のC相当の呼び出しは何ですか? – StilesCrisis

+1

Old-plain-Cインタフェースは、より多くのコーディングを必要とするため、同等のコードで例を作るのに時間がかかります。私は木曜日までこのような例を示すのに十分な自由時間があることを願っています。 – ThinkJet

+0

ええ、それのように見えます。私はBFLOAT_ARRAYを使用する別の例を見つけました...そのコードを再利用する機会はありますか? – StilesCrisis

-1

IN句で使用するSQL文を動的に構築するのではなく、グローバル一時表を使用してIN句に必要な値を挿入してみてください。これが機能するには、テーブルが "コミット時に行を保持する"と宣言されていることを確認し、テーブルへのエントリをコードブロックに切り捨てる必要があります。

start database transaction; 

truncate temporary_table; 

for each value in array 
    insert into temporary_table; 
end for each 

open cursor 'select * from mytable where id in (select id from temporary_table)'; 

end database transaction; 
+0

これはどの軸でも改善されているとは思いません。 – StilesCrisis

+0

言語のコア部分を使用しながら、すべての基準を満たしています。これはバインド変数を使用します。ネイティブSQLを使用して答えを返します。私は本番前にこのコードを書きました。できます。それは簡単です。 – Nick

+1

DBに複数回ラウンドトリップして一時テーブルを構築します。それは大きなパフォーマンスヒットになるでしょう。目標はDBへの1回のラウンドトリップで、IDの配列にバインドする*変更されない*クエリを使用します。 – StilesCrisis

0

これは確かに可能であり、PL/SQLを使用する必要はありません。あなたが最初にあなたが使用できるデータベース内an objectを作成する必要があります示唆してきたように、あなたが数字を渡していると仮定すると:

create or replace type t_num_array as table of number; 

次のようにあなたは、テーブルを使用して、テーブルを照会することができます

select * 
    from my_table 
where id in (select * from table(t_num_array(1,2,3))) 

あなたはまだ同じ問題が残っています。どのようにステートメントに未知数の変数をバインドしますか?しかし、あなたは今、それらを置くために束縛された構造を持っています。

アイバンは確かにドキュメントがちょっと混乱していて、C++の知識は執拗ですので、申し訳ありませんが、私はサンプルコードが足りません。しかし、読む価値がある以上のものがいくつかあります。 Object Relational DatatypesのOCIプログラマ・ガイドの第12章を参照してください。あなたはvector<int>としてそれを宣言することができることを意味しますmany_typesクラスの

is used to map database object types, LOB types, and named collection types to C++ class declarations

例8-12(my_tableの宣言):おそらくObject Type Translator Utility、知っていることは有用であろう。