Use un clob de Oracle en un predicado creado a partir de una Cadena> 4k

11

Estoy tratando de crear un clob a partir de una cadena de> 4000 caracteres (suministrados en la variable de enlace file_data) para usar en un predicado de Oracle SELECT a continuación:

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

Si agrego TO_CLOB () round file_data, falla el infame límite de Oracle 4k para un varchar (está bien para <4k cadenas). El error (en SQL Developer) es:

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

FYI La función flexmatch se usa para buscar moléculas, y se describe aquí: http://help.accelrysonline.com/ulm/onelab/1.0/content/ulm_pdfs/direct/developers/direct_2016_developersguide.pdf

La función en sí es un poco complicada, pero la esencia es que el segundo parámetro tiene que ser un clob. Entonces, mi pregunta es cómo convierto una variable de enlace Java String de más de 4000 caracteres en un clob en sql (o Java).

Probé el siguiente método (que funciona al insertar clobs) en Java (Spring boot 2) usando:

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

Este método debería funcionar, pero falla con un error de coincidencia flexible que FYI es:

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"

Tenga en cuenta que estoy usando SpringBoot 2 pero no puedo obtener ningún método que use OracleConnection (obtenido de mi objeto Spring NamedParametersJdbcTemplate) para trabajar (incluso en clobs <4k), por lo que sospecho que he hecho algo estúpido. He intentado:

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

Tenga en cuenta que funciona bien si voy a la vieja escuela y uso una conexión que no sea de resorte más un PreparedStatement, que tiene un método 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();

Pero preferiría una solución Spring 2 en Java o SQL. Cualquier ayuda, sugerencias apreciadas.

DS.
fuente
Esta es una muy buena pregunta +1, diferente de lo que he hecho hasta ahora. ¿Puedes señalar los documentos de la API para la flexmatch()función? Me gustaría ver la necesidad de esto. Honestamente, nunca usé valores grandes como parámetros en la WHEREcláusula. Los he usado INSERTy los he recuperado usando SELECT. Tu caso es diferente.
El Empalador
@The_impaler hay un enlace a los documentos en la pregunta. No es una api miedo, pero es todo lo que tenemos. Esta es una función muy específica. La necesidad es que estoy buscando una representación digital de una molécula, y necesita una función especializada para hacerlo. es decir, ¿la molécula que tengo ya existe en la tabla dcr_mols?
DS.
¿Qué versión de Oracle estás usando?
areus
@areaus ojdbc6-11.2.1.0.1
DS.

Respuestas:

5

Transmitirlo. No puede simplemente pegar un gran valor en una declaración SQL.

Necesitarás:

  • Inserte un BLOB vacío en la INSERTinstrucción (usando EMPTY_BLOB ()? ... no lo recuerdo).
  • Obtenga la secuencia de salida para el blob vacío.
  • Luego obtenga una secuencia de entrada del archivo. No cargue todo el archivo en la memoria.
  • Luego transfiera bloques de la secuencia de entrada a la secuencia de salida utilizando el almacenamiento en búfer. Un búfer de 16 KB debería hacer.
  • Cierra ambas corrientes.

Esta es la forma estándar de tratar con datos masivos en Oracle. Un montón de ejemplos por ahí.

La recuperación de datos masivos ( BLOBy CLOBtipos) funciona de la misma manera. Simplemente use InputStreams en ese caso.

El Empalador
fuente
@The_impaler no estoy insertando un clob. Estoy proporcionando un clob a una función que se llama en el predicado de un select
DS.
1

Al leer la documentación de la API "BIOVIA Direct", hay un ejemplo interesante en la página 27, extracto que se muestra a continuación:

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

Utiliza un CLOB ya cargado . Por lo tanto, supongo que una solución lo suficientemente decente sería cargar el CLOB en una tabla suya (o precargarlos todos por adelantado), y luego simplemente usarlos.

Paso # 1 - Cargue su CLOB en una tabla:

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

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

Y use su código Java para realizar la inserción de CLOB (probablemente usando secuencias) como se muestra en otra respuesta (muchos ejemplos en Internet). Por ejemplo, inserte su contenido de datos mol con ID = 123.

Paso # 2 - Ejecute su consulta, utilizando el archivo mol ya cargado:

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

Puede configurar el :idparámetro para 123usar el archivo que cargó antes (o cualquier otro).

El Empalador
fuente
@The_impaler Gracias, pero eso es un poco mazo para romper una nuez. Tenemos muchas consultas que ejecutar y esto complicaría el código y ralentizaría las cosas. He actualizado mi pregunta, con una respuesta de la vieja escuela, que usaré de mala gana si no aparece nada mejor.
DS.
1

Puedes llamar a tu antigua función de esta manera. crear una clase para manejar el conjunto de resultados

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

y llame a su consulta usando JdbcTemplate en su método

 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
fuente
1

Tuve que volver a usar un PreparedStatement, pero mejoré un poco la implementación normal al obtener la conexión de Spring y usar apache commons BeanListHandler para asignar el ResultSet a una Lista de objetos

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.
fuente
0

Puede declarar fileDataStr como CLOB usando con que es la conexión

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

y luego úsalo como a continuación

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

Además, si está utilizando SID en lugar del nombre del servicio en su cadena de conexión, intente cambiar su archivo de propiedades como se muestra a continuación

spring.datasource.url=jdbc:oracle:thin:@//${ORA_HOST}:${ORA_PORT}:${ORA_SID}
psaraj12
fuente
Gracias, pero eso es muy incómodo. Los datos pueden ser de cualquier tamaño, por lo que tendría que usar sql dinámico para crear los fragmentos, también, no estoy seguro de que pueda dividir un clob en partes como esta.
DS.
¿Cuál es el tipo de fileDataStr
psaraj12
Es un Java String
DS.
¿puede declararlo como CLOB y como java.sql.Clob fileDataStr = oracle.sql.CLOB.createTemporary (con, false, oracle.sql.CLOB.DURATION_SESSION);
psaraj12
vea mi ejemplo en el comentario sobre cómo declarar fileDataStr como CLOB donde con es conexión
psaraj12