2013-09-03 66 views
11

Version ngắn gọn:Oracle OCI, ràng buộc các biến, và các truy vấn như ID IN (1, 2, 3)

Tôi đang tìm kiếm một sự thích nghi C++ OCI của kỹ thuật Java sau, nơi mã có khả năng liên kết một mảng các số (kích thước mảng có thể thay đổi) thành câu lệnh không PL/SQL SELECT và sau đó sử dụng mảng kết quả trong kiểm tra kiểu WHERE ID IN (...).

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

Câu hỏi gốc:

Chúng tôi có một ứng dụng C++ mà nói chuyện với Oracle qua OCI. Chúng tôi đang cố gắng sửa chữa mã cũ tạo ra các truy vấn SQL bằng cách ghép nối văn bản; thay vào đó, chúng tôi muốn sử dụng các biến liên kết càng nhiều càng tốt. Một trường hợp cụ thể đã đưa ra rằng chúng tôi không có một giải pháp tốt cho.

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

Trường hợp phần (1, 4, 10, 30, 93) xuất phát từ một hoặc một số vector<int> chứa linh hoạt có kích thước dữ liệu khác. Nếu chúng ta biết nó sẽ luôn là năm giá trị, chúng ta có thể làm:

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

Nhưng nó có thể là một bài dự thi, hoặc mười, hoặc thậm chí bằng không. Rõ ràng, nếu chúng ta xây dựng truy vấn dưới dạng một chuỗi, chúng ta có thể thêm nhiều số như chúng ta cần, nhưng mục đích là tránh điều đó nếu có thể và chỉ dính vào các biến liên kết.

Có cách nào tốt để thực hiện việc này không? Ví dụ, trong OCI, tôi có thể ràng buộc một mảng và sau đó chọn ra khỏi nó không?

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

Trường hợp :1 là mảng OCI? (Có lẽ cú pháp sẽ khác.) Có ai có kinh nghiệm với điều này không? Mã mẫu sẽ là một ơn trời vì tôi có xu hướng đấu tranh với việc viết OCI thô. Cảm ơn :)

EDIT: Tôi muốn làm tốt hơn việc liên kết trong chuỗi được phân tích bằng thủ tục PL/SQL, nếu có thể. Tôi tin tưởng rằng chúng tôi sẽ thổi ra giới hạn 4000 ký tự trong nhiều trường hợp, và tôi cũng cảm thấy như chỉ giao dịch một loại thao tác chuỗi mà tôi cảm thấy thoải mái, cho một loại khác mà tôi không (và tôi không thể gỡ lỗi dễ dàng). Nếu có thể tôi muốn kết buộc một mảng các giá trị (hoặc một số dạng tập dữ liệu) vào một câu lệnh SQL chuẩn.

EDIT 2: Một số điều tra bật lên liên kết sau đó có vẻ là làm chỉ là những gì tôi muốn, nhưng trong Java: http://rafudb.blogspot.com/2011/10/variable-inlist.html Có ai biết làm thế nào để thích nghi với phương pháp này để C++ OCI?

+0

Đọc phần này: http://asktom.oracle.com/pls/apex/f?p=100:11:0::::P11_QUESTION_ID:110612348061 Nếu bạn không quen thuộc với asktom, bạn đang thiếu một điều tốt nguồn. – OldProgrammer

+0

Có thể thực hiện được trong OCI, mặc dù không dễ dàng. Các hoạt động ở mức thu thập này không được ghi lại trong tài liệu tham khảo OCI. Bạn đã cài đặt toàn bộ máy chủ Oracle (bao gồm cả Demos) và đọc nguồn demo OCI. Nhưng dù sao thì bạn cũng phải có 1 bộ sưu tập 'cast' OCI vào' TABLE'. Oracle truy vấn không thể đọc trực tiếp từ bộ sưu tập. – ibre5041

+1

Ivan, âm thanh đầy hứa hẹn - bạn có thể chỉ cho tôi hướng tới một bản trình diễn cụ thể hoặc cung cấp đoạn mã không? Thanks :) – StilesCrisis

Trả lời

12

Ví dụ này minh họa cách tiếp cận với việc sử dụng loại thu thập, được xác định trong cơ sở dữ liệu để chuyển danh sách tham số.
SYS.ODCINumberList là loại bộ sưu tập tiêu chuẩn có sẵn cho tất cả người dùng. Truy vấn, được sử dụng trong mẫu chỉ cần chọn 100 số nguyên đầu tiên (test) và sau đó lọc số nguyên này bằng danh sách trong mệnh đề 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; 
} 

Bạn có thể sử dụng various ODCIxxxList types để chuyển danh sách số, ngày hoặc chuỗi tới Oracle qua OCI hoặc thậm chí xác định loại của riêng bạn trong DB.

Ví dụ được biên dịch bằng Visual Studio 10 Express và this version of OCI libraries. Được thử nghiệm đối với Oracle 11.2.0.3.0.

Cập nhật

Dưới đây là ví dụ ứng dụng mà làm điều tương tự nhưng với đồng bằng chức năng 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. Bạn có thể lấy thông tin từ tài liệu Oracle và this example code.

+0

Điều này có vẻ rất hứa hẹn và tôi sẽ thử nghiệm nó khi tôi trở lại làm việc vào thứ Hai. Bây giờ có một upvote. Một câu hỏi quan trọng: codebase của chúng tôi là C++, nhưng việc sử dụng OCI hiện tại của chúng tôi đang sử dụng giao diện vani C từ ''. Cuộc gọi tương đương C cho 'setVector (stmt, 1, value_list," SYS "," ODCINUMBERLIST ")' là gì? – StilesCrisis

+1

Giao diện Old-plain-C liên quan đến việc viết mã nhiều hơn vì vậy phải mất một thời gian để tạo một ví dụ với mã tương đương. Tôi hy vọng rằng tôi có đủ thời gian rảnh để trình bày ví dụ đó cho đến thứ Năm. – ThinkJet

+0

Vâng, có vẻ như vậy. Tôi tìm thấy một ví dụ khác sử dụng BFLOAT_ARRAY ... Có cơ hội nào để sử dụng lại mã đó hoặc nó quá khác nhau không? – StilesCrisis

-1

Thay vì tự động xây dựng câu lệnh SQL để sử dụng trong mệnh đề IN của bạn, hãy thử sử dụng bảng tạm thời toàn cục để chèn các giá trị bạn muốn trong mệnh đề IN của bạn. Để làm việc này, bạn sẽ cần phải chắc chắn rằng bảng của bạn được khai báo là "trên cam kết bảo tồn hàng" và cắt bớt bảng của bạn khi nhập vào khối mã của bạn.

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

Tôi không thể tưởng tượng đây là một cải tiến trên bất kỳ trục nào. – StilesCrisis

+0

Nó đáp ứng tất cả các tiêu chí của bạn, trong khi sử dụng các phần cốt lõi của ngôn ngữ. Nó sử dụng các biến liên kết. Nó sử dụng SQL nguyên gốc để trả về câu trả lời. Tôi đã viết mã này trước trong các hệ thống sản xuất. Nó hoạt động. Đơn giản của nó. – Nick

+1

Nó vòng nhiều lần vào DB nhiều lần để xây dựng một bảng tạm thời. Đó sẽ là một hit hiệu suất lớn. Mục đích là một chuyến đi vòng vào DB với một truy vấn * không thay đổi *, trong đó tôi liên kết trong một mảng ID. – StilesCrisis

0

Điều này chắc chắn có thể và không cần sử dụng PL/SQL.Giả sử rằng bạn đang đi qua con số như bạn đã đề nghị đầu tiên bạn sẽ cần phải tạo an object trong cơ sở dữ liệu mà bạn có thể sử dụng:

create or replace type t_num_array as table of number; 

Sau đó bạn có thể truy vấn bảng của bạn sử dụng bảng như sau:

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

Bạn vẫn còn lại cùng một vấn đề; làm cách nào để bạn liên kết một số biến không xác định với một câu lệnh? Nhưng bây giờ bạn có một cấu trúc ràng buộc trong đó để đặt chúng.

Chắc chắn Ivan là các tài liệu hơi khó hiểu và kiến ​​thức về C++ của tôi có thể thực hiện được nên tôi xin lỗi nhưng tôi thiếu mã ví dụ. Có một vài điều mà sẽ có nhiều hơn giá trị đọc mặc dù. Chương 12 của Hướng dẫn lập trình viên OCI trên Object Relational Datatypes. Nó có lẽ sẽ hữu ích để biết về Object Type Translator Utility, trong đó:

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

Ví dụ 8-12 (tuyên bố my_table) trong lớp many_types sẽ ngụ ý rằng bạn có thể khai báo nó như là một vector<int>.

Các vấn đề liên quan