Utilizzare un clob Oracle in un predicato creato da una stringa> 4k


11

Sto cercando di creare un clob da una stringa di> 4000 caratteri (forniti nella variabile bind file_data) da utilizzare in un predicato Oracle SELECT di seguito:

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

Se aggiungo TO_CLOB () intorno a file_data fallisce il famigerato limite di Oracle 4k per un varchar (va bene per <4k stringhe). L'errore (in SQL Developer) è:

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

Cordiali saluti La funzione flexmatch viene utilizzata per la ricerca di molecole ed è descritta qui: http://help.accelrysonline.com/ulm/onelab/1.0/content/ulm_pdfs/direct/developers/direct_2016_developersguide.pdf

La stessa funzione è un po 'complicata, ma l'essenza è che il secondo parametro deve essere un clob. Quindi la mia domanda è come posso convertire una stringa Java bind_variable di oltre 4000 caratteri in un clob in sql (o Java).

Ho provato il metodo seguente (che funziona quando si inseriscono clobs) in Java (Spring boot 2) utilizzando:

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

Questo metodo dovrebbe funzionare ma fallisce con un errore di flexmatch converto che FYI è:

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"

Nota che sto usando SpringBoot 2 ma non riesco a ottenere alcun metodo utilizzando un OracleConnection (ottenuto dal mio oggetto Spring NamedParametersJdbcTemplate) per funzionare (anche su clobs <4k), quindi sospetto di aver fatto qualcosa di stupido. Ho provato:

 @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);

application.properties:

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

Nota che funziona bene se vado alla vecchia scuola e utilizzo una connessione non a molla più un PreparedStatement, che ha un metodo 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();

Preferirei una soluzione Spring 2 in Java o Sql. Qualsiasi aiuto, suggerimenti apprezzati.


Questa è una buona domanda +1, diversa da quella che ho fatto finora. Puoi indicare i documenti API per la flexmatch()funzione? Mi piacerebbe vedere la necessità di questo. Onestamente, non ho mai usato grandi valori come parametri nella WHEREclausola. Li ho usati INSERTe li ho recuperati usando SELECT. Il tuo caso è diverso.
The Impaler

@The_impaler c'è un link ai documenti nella domanda. Non è un api im paura, ma è tutto ciò che abbiamo. Questa è una funzione di nicchia. La necessità è che sto cercando una rappresentazione digitale di una molecola e per farlo è necessaria una funzione specialistica. cioè la molecola che ho già esiste nella tabella dcr_mols.
DS.

Quale versione di Oracle stai usando?
areus

@areaus ojdbc6-11.2.1.0.1
DS.

Risposte:


5

Streaming. Non puoi semplicemente incollare un valore enorme in un'istruzione SQL.

Dovrai:

  • Inserisci un BLOB vuoto INSERTnell'istruzione (usando EMPTY_BLOB ()? ... non ricordi del tutto).
  • Ottieni il flusso di output per il BLOB vuoto.
  • Quindi ottenere un flusso di input dal file. Si prega di non caricare l'intero file in memoria.
  • Quindi trasferire i blocchi dal flusso di input nel flusso di output utilizzando il buffering. Un buffer da 16 KB dovrebbe fare.
  • Chiudi entrambi i flussi.

Questo è il modo standard di gestire enormi quantità di dati in Oracle. Molti esempi là fuori.

Il recupero di dati ( BLOBe CLOBtipi) di grandi dimensioni funziona allo stesso modo. Basta usare InputStreams in quel caso.


@The_impaler non sto inserendo un clob. Sto fornendo un clob a una funzione che viene chiamata nel predicato di un select
DS.

1

Leggendo la documentazione dell'API "BIOVIA Direct", c'è un esempio interessante a pagina 27, estratto mostrato di seguito:

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

Utilizza un CLOB già caricato . Quindi suppongo che una soluzione abbastanza decente sarebbe quella di caricare il CLOB in una tabella (o pre-caricarli tutti in anticipo), e poi semplicemente usarli.

Passaggio 1: carica il tuo CLOB in una tabella:

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

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

E usa il tuo codice Java per eseguire l'inserimento di CLOB (probabilmente usando i flussi) come mostrato in un'altra risposta (molti esempi in Internet). Ad esempio, inserisci il contenuto dei tuoi dati mol con ID = 123.

Passaggio 2: esegui la query, utilizzando il file mol già caricato:

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

È possibile impostare il :idparametro su 123per utilizzare il file caricato in precedenza (o qualsiasi altro).


@The_impaler Grazie, ma è un po 'un martello per rompere un dado. Abbiamo molte domande da eseguire e questo complicherebbe il codice e rallenterebbe le cose. Ho aggiornato la mia domanda, con una risposta della vecchia scuola, che userò con riluttanza se non dovesse apparire nulla di meglio.
DS.

1

Puoi chiamare la tua vecchia funzione di moda in questo modo. creare una classe per gestire il 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;
    }
}

e chiama la tua query usando JdbcTemplate nel tuo metodo

 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());

1

Ho dovuto tornare a utilizzare un PreparedStatement, ma ho migliorato un po 'l'implementazione normale ottenendo la connessione da Spring e usando BeanListHandler comuni di apache per mappare il ResultSet a un elenco di oggetti

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

0

È possibile dichiarare fileDataStr come CLOB usando con quale è la connessione

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

e poi usalo come di seguito

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

Inoltre, se si utilizza SID anziché il nome del servizio nella stringa di connessione, provare a modificare il file delle proprietà come di seguito

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

Grazie, ma è molto imbarazzante. I dati possono essere di qualsiasi dimensione, quindi dovrei usare sql dinamico per creare i blocchi, inoltre, non sono sicuro di poter spezzare un blocco in parti come questa.
DS.

qual è il tipo di fileDataStr
psaraj12

È una stringa java
DS.

puoi dichiararlo come CLOB e come java.sql.Clob fileDataStr = oracle.sql.CLOB.createTemporary (con, false, oracle.sql.CLOB.DURATION_SESSION);
psaraj12,

vedi il mio esempio nel commento su come dichiarare fileDataStr come CLOB dove con è connessione
psaraj12
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.