Use um clob Oracle em um predicado criado a partir de uma String> 4k

11

Estou tentando criar um clob a partir de uma seqüência de caracteres> 4000 caracteres (fornecida na variável de ligação file_data) a ser usada em um predicado Oracle SELECT abaixo:

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

Se eu adicionar TO_CLOB () round file_data, ele falha no infame limite do Oracle 4k para um varchar (é bom para <4k strings). O erro (no SQL Developer) é:

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

FYI A função flexmatch é usada para pesquisar moléculas e é descrita aqui: http://help.accelrysonline.com/ulm/onelab/1.0/content/ulm_pdfs/direct/developers/direct_2016_developersguide.pdf

A função em si é um pouco complicada, mas a essência é que o segundo parâmetro precisa ser um clob. Portanto, minha pergunta é como converter uma variável Java bind bind_variable de mais de 4000 caracteres em um clob em sql (ou Java).

Eu tentei o método abaixo (que funciona ao inserir clobs) em Java (Spring boot 2) usando:

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

Esse método deve funcionar, mas falha com um erro de flexmatch convergido, que é o 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"

Observe que estou usando o SpringBoot 2, mas não consigo obter nenhum método usando um OracleConnection (obtido do meu objeto Spring NamedParametersJdbcTemplate) para funcionar (mesmo em clobs <4k), por isso suspeito que fiz algo estúpido. Eu tentei:

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

Observe que funciona bem se eu for para o ensino médio e usar uma conexão sem mola, além de um PreparedStatement, que possui um 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();

Mas eu preferiria uma solução Spring 2 em Java ou Sql. Qualquer ajuda, sugestões apreciadas.

DS.
fonte
Essa é uma boa pergunta +1, diferente do que eu fiz até agora. Você pode apontar os documentos da API para a flexmatch()função? Eu gostaria de ver a necessidade disso. Sinceramente, nunca usei grandes valores como parâmetros na WHEREcláusula. Eu os usei INSERTe os recuperei usando SELECT. Seu caso é diferente.
The Empaler
@ The_impaler, há um link para os documentos na pergunta. Não é uma API que eu tenho medo, mas é tudo o que temos. Esta é uma função muito específica. A necessidade é que eu estou procurando uma representação digital de uma molécula, e você precisa de uma função especializada para fazer isso. ou seja, a molécula que eu já existo na tabela dcr_mols.
DS.
Qual versão do Oracle você está usando?
areus 8/04
@areaus ojdbc6-11.2.1.0.1
DS.

Respostas:

5

Transmita-o. Você não pode simplesmente colar um valor enorme em uma instrução SQL.

Você precisará:

  • Inserir um BLOB vazio na INSERTinstrução (usando EMPTY_BLOB ()? ... não me lembro).
  • Obtenha o fluxo de saída para o blob vazio.
  • Em seguida, obtenha um fluxo de entrada do arquivo. Por favor, não carregue o arquivo inteiro na memória.
  • Em seguida, transfira os blocos do fluxo de entrada para o fluxo de saída usando o buffer. Um buffer de 16 KB deve funcionar.
  • Feche os dois fluxos.

Essa é a maneira padrão de lidar com dados massivos no Oracle. Muitos exemplos por aí.

A recuperação de dados ( BLOBe CLOBtipos) massivos funciona da mesma maneira. Basta usar InputStreams nesse caso.

O Empalador
fonte
@The_impaler não estou inserindo um clob. Estou fornecendo um clob para uma função que é chamada no predicado de um select
DS.
1

Lendo a documentação da API "BIOVIA Direct", há um exemplo interessante na página 27, trecho mostrado abaixo:

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

Ele usa um CLOB já carregado . Portanto, acho que uma solução suficientemente decente seria carregar o CLOB em uma tabela sua (ou pré-carregá-los todos com antecedência) e depois usá-los.

Etapa 1 - Carregue seu CLOB em uma tabela:

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

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

E use seu código Java para executar a inserção CLOB (provavelmente usando fluxos), como mostrado em outra resposta (muitos exemplos na Internet). Por exemplo, insira seu conteúdo de dados mol com ID = 123.

Etapa 2 - Execute sua consulta usando o arquivo mol já carregado:

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

Você pode definir o :idparâmetro para 123usar o arquivo carregado anteriormente (ou qualquer outro).

O Empalador
fonte
@ The_impaler Obrigado, mas isso é um pouco de marreta para quebrar uma noz. Temos muitas consultas para executar e isso complicaria o código e tornaria as coisas mais lentas. Atualizei minha pergunta com uma resposta da velha escola, que usarei com relutância se nada melhor aparecer.
DS.
1

Você pode chamar sua função antiga como esta. crie uma classe para manipular o 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 chame sua consulta usando o JdbcTemplate no seu 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
fonte
1

Eu tive que voltar a usar um PreparedStatement, mas aprimorei um pouco a implementação normal obtendo a conexão do Spring e usando o apache commons BeanListHandler para mapear o ResultSet para um objeto List

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

Você pode declarar fileDataStr como CLOB usando con, que é a conexão

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

e depois use como abaixo

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

Além disso, se você estiver usando o SID em vez do nome do serviço na cadeia de conexão, tente alterar o arquivo de propriedades como abaixo

spring.datasource.url=jdbc:oracle:thin:@//${ORA_HOST}:${ORA_PORT}:${ORA_SID}
psaraj12
fonte
Obrigado, mas isso é muito estranho. Os dados podem ter qualquer tamanho, então eu teria que usar o sql dinâmico para criar os pedaços, também, não tenho certeza de que você pode quebrar um clob em partes como esta.
DS.
Qual é o tipo de fileDataStr
psaraj12 8/04
É uma String java
DS.
você pode declará-lo como CLOB e como java.sql.Clob fileDataStr = oracle.sql.CLOB.createTemporary (con, false, oracle.sql.CLOB.DURATION_SESSION);
psaraj12 8/04
Veja meu exemplo no comentário sobre como declarar fileDataStr como CLOB onde con é conexão
psaraj12