Gunakan Oracle clob dalam predikat yang dibuat dari String> 4k

11

Saya mencoba membuat clob dari string> 4000 chars (disediakan dalam variabel bind file_data) untuk digunakan dalam predikat Oracle SELECT di bawah ini:

myQuery=
select *
from dcr_mols
WHERE flexmatch(ctab,:file_data,'MATCH=ALL')=1;

Jika saya menambahkan TO_CLOB () round file_data ia gagal membatasi Oracle 4k yang terkenal untuk varchar (tidak apa-apa untuk string <4k). Kesalahan (dalam SQL Developer) adalah:

ORA-01460: unimplemented or unreasonable conversion requested
01460. 00000 -  "unimplemented or unreasonable conversion requested"

FYI Fungsi flexmatch digunakan untuk mencari molekul, dan dijelaskan di sini: http://help.accelrysonline.com/ulm/onelab/1.0/content/ulm_pdfs/direct/developers/direct_2016_developersguide.pdf

Fungsi itu sendiri agak rumit tetapi intinya adalah parameter ke-2 harus menjadi clob. Jadi pertanyaan saya adalah bagaimana cara mengonversi Java String bind_variable lebih dari 4000 chars ke clob di sql (atau Java).

Saya mencoba metode di bawah ini (yang berfungsi saat memasukkan gumpalan) di Java (Spring boot 2) menggunakan:

MapSqlParameterSource parameters = new MapSqlParameterSource();
parameters.addValue("file_data", fileDataStr,Types.CLOB);
jdbcNamedParameterTemplate.query(myQuery,parameters,…

Metode ini harus berfungsi tetapi gagal dengan kesalahan flexmatch terkonversi yang FYI adalah:

SQL state [99999]; error code [29902]; ORA-29902: error in executing ODCIIndexStart() routine\nORA-20100: 
MDL-0203: Unable to read from CLOB (csfrm=1, csid=873): 
ORA-22922: nonexistent LOB value\nMDL-0021: Unable to copy LOB to string\nMDL-1051: Molstructure search query is not a valid molecule\nMDL-0976: 
Molecule index search initialization failed\nORA-06512: at \"C$MDLICHEM80.MDL_MXIXMDL\", line 329\nORA-06512: at \"C$MDLICHEM80.MDL_MXIXMDL\", line 309\n; nested exception is java.sql.SQLException: 
ORA-29902: error in executing ODCIIndexStart() routine\nORA-20100: MDL-0203: Unable to read from CLOB (csfrm=1, csid=873): 
ORA-22922: nonexistent LOB value\nMDL-0021: Unable to copy LOB to string\nMDL-1051: Molstructure search query is not a valid molecule\nMDL-0976: 
Molecule index search initialization failed\nORA-06512: at \"C$MDLICHEM80.MDL_MXIXMDL\", line 329\nORA-06512: at \"C$MDLICHEM80.MDL_MXIXMDL\", line 309\n"

Catatan saya menggunakan SpringBoot 2 tapi saya tidak bisa mendapatkan metode apa pun menggunakan OracleConnection (diperoleh dari objek Spring NamedParametersJdbcTemplate) saya untuk bekerja (bahkan pada clobs <4k), jadi saya curiga saya telah melakukan sesuatu yang bodoh. Saya sudah mencoba:

 @Autowired
 NamedParameterJdbcTemplate  jdbcNamedParameterTemplate;
OracleConnection conn =  this.jdbcNamedParameterTemplate.getJdbcTemplate().getDataSource().getConnection().unwrap(OracleConnection.class);
Clob myClob =  conn.createClob();
myClob.setString( 1, fileDataStr);
MapSqlParameterSource parameters = new MapSqlParameterSource();
parameters.addValue("file_data", myClob,Types.CLOB);

properti aplikasi:

spring.datasource.url=jdbc:oracle:thin:@//${ORA_HOST}:${ORA_PORT}/${ORA_SID}
spring.datasource.username=${ORA_USER}
spring.datasource.password=${ORA_PASS}

Catatan itu berfungsi dengan baik jika saya pergi ke sekolah lama dan menggunakan koneksi non spring plus PreparedStatement, yang memiliki metode setClob ():

OracleDataSource ods = new OracleDataSource();
String url ="jdbc:oracle:thin:@//" + ORA_HOST +":"+ORA_PORT +"/"+ORA_SID;
ods.setURL(url);
ods.setUser(user);
ods.setPassword(passwd);
Connection conn = ods.getConnection();
Clob myClob=conn.createClob();
PreparedStatement ps = conn.prepareStatement("select dcr_number from dcr_mols WHERE flexmatch(ctab,?,'MATCH=ALL')=1");
myClob.setString(1,myMol);
ps.setClob(1,myClob);
ResultSet rs =ps.executeQuery();

Tapi saya lebih suka solusi Spring 2 di Java atau Sql. Setiap bantuan, saran sangat dihargai.

DS.
sumber
Ini pertanyaan yang cukup bagus +1, berbeda dari apa yang telah saya lakukan sejauh ini. Bisakah Anda menunjukkan dokumen API untuk flexmatch()fungsi ini? Saya ingin melihat perlunya hal ini. Jujur, saya tidak pernah menggunakan nilai besar sebagai parameter dalam WHEREklausa. Saya telah menggunakannya INSERTdan, saya mengambilnya menggunakan SELECT. Kasing Anda berbeda.
The Impaler
@The_impaler ada tautan ke dokumen di pertanyaan. Ini bukan api im takut, tapi hanya itu yang kita miliki. Ini adalah fungsi yang sangat khusus. Kebutuhannya adalah saya sedang mencari representasi digital dari sebuah molekul, dan Anda memerlukan fungsi spesialis untuk melakukan ini. yaitu apakah molekul saya sudah ada di tabel dcr_mols.
DS.
Apa versi Oracle yang Anda gunakan?
areus
@areaus ojdbc6-11.2.1.0.1
DS.

Jawaban:

5

Streaming itu. Anda tidak bisa hanya menempelkan nilai besar ke dalam pernyataan SQL.

Anda harus:

  • Masukkan BLOB kosong dalam INSERTpernyataan (menggunakan EMPTY_BLOB ()? ... tidak begitu ingat).
  • Dapatkan aliran output untuk gumpalan kosong.
  • Kemudian dapatkan aliran input dari file. Tolong jangan memuat seluruh file dalam memori.
  • Kemudian transfer blok dari aliran input ke aliran output menggunakan buffering. Sebuah buffer 16 KB harus dilakukan.
  • Tutup kedua aliran.

Ini adalah cara standar untuk menangani data masif di Oracle. Banyak contoh di luar sana.

Mengambil data besar ( BLOBdan CLOBtipe) bekerja dengan cara yang sama. Cukup gunakan InputStreams dalam kasus itu.

Impaler
sumber
@The_impaler saya tidak menyisipkan gumpalan. Saya menyediakan clob ke fungsi yang disebut dalam predikat select
DS.
1

Membaca dokumentasi API "BIOVIA Direct", ada contoh yang menarik di halaman 27, kutipan yang ditunjukkan di bawah ini:

select ...
from ...
where flexmatch(
ctab,
(select ctab from nostruct_table),
'all'
)=1

Ini menggunakan CLOB yang sudah dimuat . Oleh karena itu saya kira solusi yang cukup layak adalah dengan memuat CLOB ke dalam tabel Anda (atau pra-muat semuanya di muka), dan kemudian gunakan saja.

Langkah # 1 - Muat CLOB Anda ke dalam tabel:

create table mol_file (
  id number(12) primary key not null,
  content clob
);

insert into mol_file (id, content) values (:id, :content);

Dan gunakan kode Java Anda untuk melakukan penyisipan CLOB (mungkin menggunakan stream) seperti yang ditunjukkan dalam jawaban lain (banyak contoh di Internet). Misalnya, masukkan konten data mol Anda dengan ID = 123.

Langkah # 2 - Jalankan permintaan Anda, menggunakan file mol yang sudah dimuat:

select *
from dcr_mols
WHERE flexmatch(
        ctab,
        (select content from mol_file where id = :id),
        'MATCH=ALL'
      ) = 1;

Anda dapat mengatur :idparameter untuk 123menggunakan file yang Anda muat sebelumnya (atau yang lainnya).

Impaler
sumber
@The_impaler Terima kasih, tapi itu sedikit godam untuk memecahkan kacang. Kami memiliki banyak pertanyaan untuk dijalankan dan ini akan menyulitkan kode dan memperlambatnya. Saya telah memperbarui pertanyaan saya, dengan jawaban jadul, yang dengan enggan saya gunakan jika tidak ada yang lebih baik muncul.
DS.
1

Anda dapat memanggil fungsi mode lama Anda seperti ini. buat Kelas untuk menangani ResultSet

 class MyPreparedStatementCallback implements PreparedStatementCallback {
    public Object doInPreparedStatement(PreparedStatement preparedStatement)
            throws SQLException, DataAccessException {
        ResultSet rs = preparedStatement.executeQuery();
        List result = new LinkedList();
        rs.close();
        return result;
    }
}

dan panggil permintaan Anda menggunakan JdbcTemplate dalam metode Anda

 jdbcTemplate.execute(new PreparedStatementCreator() {
        @Override
        public PreparedStatement createPreparedStatement(Connection connection)

                throws SQLException, DataAccessException {

            PreparedStatement ps = connection.prepareStatement("select dcr_number from dcr_mols WHERE flexmatch(ctab,?,'MATCH=ALL')=1");
            Clob myClob =  connection.createClob();
            myClob.setString( 1, fileDataStr);
            MapSqlParameterSource parameters = new MapSqlParameterSource();
            parameters.addValue("file_data", myClob, Types.CLOB);
            ps.setClob(1,myClob);
            return ps;

        };
    }, new MyPreparedStatementCallback());
Mukhtiar Ahmed
sumber
1

Saya harus kembali menggunakan PreparedStatement, tetapi saya telah sedikit meningkatkan implementasi normal dengan mendapatkan koneksi dari Spring dan menggunakan apache commons BeanListHandler untuk memetakan ResultSet ke daftar objek

import org.apache.commons.dbutils.ResultSetHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;

@Autowired
NamedParameterJdbcTemplate  jdbcTemplate;

List<MyDao> myMethod(String fileData){
    String myQuery="select * from dcr_mols WHERE flexmatch(ctab,?,'MATCH=ALL')=1";

try {
    Connection conn =  this.jdbcTemplate.getJdbcTemplate().getDataSource().getConnection().unwrap(OracleConnection.class);   // Get connection from spring

    Clob myClob =  conn.createClob();   // Open a dB clob 
    myClob.setString( 1, fileData);     // add data to clob
    PreparedStatement ps = conn.prepareStatement(myQuery);
    ps.setClob(1,myClob);              // Add a clob into the PreparedStatement
    ResultSet rs =ps.executeQuery();   // Execute the prepared statement

    //ResultSetHandler<List<MyDao>> handler = new BeanListHandler<MyDao>(MyDao.class);   // Define the ResultSet handler
    ResultSetHandler<List<MyDao>> handler = new BeanListHandler<MyDao>(MyDao.class, new BasicRowProcessor(new GenerousBeanProcessor()));  // This is better than the above handler , because GenerousBeanProcessor removes the requirement for the column names to exactly match the java variables

    List<MyDao> myDaoList = handler.handle(rs);   // Map ResultSet to List of MyDao objects
    }catch (Exception e) {
        e.printStackTrace();
    }

return myDaoList;
}
DS.
sumber
0

Anda dapat mendeklarasikan fileDataStr sebagai CLOB menggunakan con yang merupakan koneksi

java.sql.Clob fileDataStr = oracle.sql.CLOB.createTemporary
(con, false, oracle.sql.CLOB.DURATION_SESSION);

dan kemudian gunakan seperti di bawah ini

 parameters.addValue("file_data", fileDataStr,Types.CLOB);

Juga jika Anda menggunakan SID alih-alih nama Layanan di string koneksi Anda, coba ubah file properti Anda seperti di bawah ini

spring.datasource.url=jdbc:oracle:thin:@//${ORA_HOST}:${ORA_PORT}:${ORA_SID}
psaraj12
sumber
Terima kasih, tapi itu sangat aneh. Data dapat berupa ukuran apa pun jadi saya harus menggunakan sql dinamis untuk membuat potongan, juga, saya tidak yakin Anda dapat memecah gumpalan menjadi bagian-bagian seperti ini.
DS.
apa jenis fileDataStr
psaraj12
Ini adalah string java
DS.
dapatkah Anda mendeklarasikannya sebagai CLOB dan seperti java.sql.Clob fileDataStr = oracle.sql.CLOB.createTemporary (con, false, oracle.sql.CLOB.DURATION_SESSION);
psaraj12
lihat contoh saya di komentar tentang cara mendeklarasikan fileDataStr sebagai CLOB di mana con adalah koneksi
psaraj12