简体   繁体   English

Oracle OCI,绑定变量和ID IN(1,2,3)之类的查询

[英]Oracle OCI, bind variables, and queries like ID IN (1, 2, 3)

Succinct Version: 简洁版:

I'm looking for a C++ OCI adaptation of the following Java technique, where code is able to bind an array of numbers (the array size can vary) into a non-PL/SQL SELECT statement and then use the resulting array in a WHERE ID IN (...) style check. 我正在寻找以下Java技术的C ++ OCI改编,其中代码能够将数组数组(数组大小可以变化)绑定到非PL / SQL SELECT语句中,然后在WHERE ID IN (...)使用结果数组WHERE ID IN (...)样式检查。

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

Original Question: 原始问题:

We have a C++ app which talks to Oracle via OCI. 我们有一个C ++应用程序通过OCI与Oracle通信。 We're trying to fix old code which generates SQL queries by concatenating text; 我们正在尝试修复通过连接文本生成SQL查询的旧代码; instead we want to use bind variables as much as possible. 相反,我们希望尽可能多地使用绑定变量。 One particular case has come up that we don't have a good solution for. 我们提出了一个特殊的案例,即我们没有一个好的解决方案。

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

Where the (1, 4, 10, 30, 93) part comes from a vector<int> or some other flexibly-sized container of data. 其中(1, 4, 10, 30, 93)部分来自vector<int>或其他一些灵活大小的数据容器。 If we knew it would always be five values, we could do: 如果我们知道它总是五个值,我们可以这样做:

SELECT * FROM MyTable WHERE ID IN (:1, :2, :3, :4, :5)

But it might be one entry, or ten, or maybe even zero. 但它可能是一个条目,或十个,甚至可能是零。 Obviously, if we are building up the query as a string, we can just append as many numbers as we need, but the goal is to avoid that if possible and stick to just bind variables. 显然,如果我们将查询构建为字符串,我们可以根据需要添加尽可能多的数字,但目标是尽可能避免这种情况并坚持只绑定变量。

Is there a good way to accomplish this? 有没有一个很好的方法来实现这一目标? For instance, in OCI, can I bind an array and then sub-select out of it? 例如,在OCI中,我可以绑定一个数组然后从中进行子选择吗?

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

Where :1 is an OCI array? 其中:1是OCI阵列? (Probably the syntax would differ.) Does anyone have experience with this? (可能语法不同。)有没有人有这方面的经验? Sample code would be a godsend as I tend to struggle with writing raw OCI. 示例代码将是天赐之物,因为我倾向于编写原始OCI。 Thanks :) 谢谢 :)

EDIT: I'd like to do better than binding in a string which is parsed by a PL/SQL procedure, if at all possible. 编辑:我想比在PL / SQL过程解析的字符串中绑定更好,如果可能的话。 I am confident that we would blow out the 4000 character limit in many cases, and I also feel like that's just trading one kind of string manipulation that I'm comfortable with, for another kind that I'm not (and I can't debug as easily). 我相信在很多情况下我们会吹掉4000个字符的限制,而且我觉得这只是交易一种我很舒服的字符串操作,另一种我不是(我不能)调试很容易)。 If possible I'd like to bind an array of values (or some form of dataset) into one standard SQL statement. 如果可能的话,我想将一个值数组(或某种形式的数据集)绑定到一个标准SQL语句中。

EDIT 2: Some investigation turned up the following link which seems to be doing just what I want, but in Java: http://rafudb.blogspot.com/2011/10/variable-inlist.html Does anyone know how to adapt this approach to C++ OCI? 编辑2:一些调查发现以下链接似乎正在做我想要的,但在Java: http: //rafudb.blogspot.com/2011/10/variable-inlist.html有谁知道如何适应这个处理C ++ OCI?

This example demonstrates approach with using collection type, defined in database to pass list of parameters. 此示例演示了使用集合类型的方法,在数据库中定义以传递参数列表。
SYS.ODCINumberList is standard collection type available for all users. SYS.ODCINumberList是可供所有用户使用的标准集合类型。 Query, used in sample just select first 100 integers ( test ) and then filter this integers with list in IN(...) clause. 查询,在示例中使用只选择前100个整数( 测试 ),然后使用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;
}

You can use various ODCIxxxList types to pass list of numbers, dates or strings to Oracle via OCI or even define your own type in DB. 您可以使用各种ODCIxxxList类型通过OCI将数字,日期或字符串列表传递给Oracle,甚至可以在DB中定义您自己的类型。

Example compiled with Visual Studio 10 Express and this version of OCI libraries . 使用Visual Studio 10 Express和此版本的OCI库编译的示例。 Tested against Oracle 11.2.0.3.0 . 针对Oracle 11.2.0.3.0进行了测试。

Update 更新

Below is example application which does same thing but with plain C OCIxxx functions. 下面是使用普通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;

}

PS You can get info from Oracle documentation and this example code . PS您可以从Oracle文档和此示例代码中获取信息。

This is certainly possible and there's no need to use PL/SQL. 这当然是可能的,不需要使用PL / SQL。 Assuming that you're passing numbers as you've suggested you'll first need to create an object within the database that you can use: 假设您按照建议传递数字,首先需要在数据库中创建一个可以使用的对象

create or replace type t_num_array as table of number;

You can then query your table using the table as follows: 然后,您可以使用表格查询您的表格,如下所示:

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

You're still left with the same problem; 你还有同样的问题; how do you bind an unknown number of variables to a statement? 如何将未知数量的变量绑定到语句? But you now have a bindable structure in which to put them. 但是你现在有了一个可绑定的结构来放置它们。

Ivan's certainly right that the docs are a little confusing and my knowledge of C++ is execrable so I'm sorry but I'm short of example code. Ivan肯定是正确的,文档有点令人困惑,我对C ++的了解很可怕,所以我很抱歉,但我缺少示例代码。 There are a few things that would be more than worth reading though. 虽然有一些东西值得一读。 Chapter 12 of the OCI Programmers Guide on Object Relational Datatypes . 关于对象关系数据类型的OCI程序员指南的第12章。 It would probably be useful to know about the Object Type Translator Utility , which: 了解对象类型转换器实用程序可能很有 ,其中:

is used to map database object types, LOB types, and named collection types to C++ class declarations 用于将数据库对象类型,LOB类型和命名集合类型映射到C ++类声明

Example 8-12 (the declaration of my_table ) in the many_types class would imply that you can declare it as a vector<int> . many_types类中的示例8-12( my_table的声明)意味着您可以将其声明为vector<int>

Instead of dynamically building a SQL statement to use in your IN clause, try using a global temporary table to insert the values you want in your IN clause. 不要动态构建要在IN子句中使用的SQL语句,而是尝试使用全局临时表在IN子句中插入所需的值。 For this to work, you'll need to make sure your table is declared as "on commit preserve rows" and truncate your table on entry into you code block. 为此,您需要确保将表声明为“on commit preserve rows”并在进入代码块时截断表。

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;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM