Utiliser un clob Oracle dans un prédicat créé à partir d'une chaîne> 4k

11

J'essaie de créer un clob à partir d'une chaîne de> 4000 caractères (fourni dans la variable de liaison file_data) à utiliser dans un prédicat Oracle SELECT ci-dessous:

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

Si j'ajoute TO_CLOB () autour de file_data, il échoue à la fameuse limite Oracle 4k pour un varchar (c'est bien pour <4k strings). L'erreur (dans SQL Developer) est:

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

Pour info La fonction flexmatch est utilisée pour rechercher des molécules et est décrite ici: http://help.accelrysonline.com/ulm/onelab/1.0/content/ulm_pdfs/direct/developers/direct_2016_developersguide.pdf

La fonction elle-même est un peu compliquée mais l'essentiel est que le 2ème paramètre doit être un clob. Donc, ma question est de savoir comment convertir une chaîne Java bind_variable de plus de 4000 caractères en un clob en sql (ou Java).

J'ai essayé la méthode ci-dessous (qui fonctionne lors de l'insertion de clobs) en Java (Spring boot 2) en utilisant:

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

Cette méthode devrait fonctionner, mais elle échoue avec une erreur de flexmatch convergée dont FYI est:

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"

Notez que j'utilise SpringBoot 2 mais je ne peux obtenir aucune méthode utilisant un OracleConnection (obtenu à partir de mon objet Spring NamedParametersJdbcTemplate) pour travailler (même sur des clobs <4k), donc je soupçonne que j'ai fait quelque chose de stupide. J'ai essayé:

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

Notez que cela fonctionne bien si je vais à l'ancienne et que j'utilise une connexion sans ressort plus un PreparedStatement, qui a une méthode 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();

Mais je préférerais une solution Spring 2 en Java ou Sql. Toute aide, suggestions appréciées.

DS.
la source
C'est une assez bonne question +1, différente de ce que j'ai fait jusqu'à présent. Pouvez-vous indiquer les documents API pour la flexmatch()fonction? J'aimerais voir la nécessité de cela. Honnêtement, je n'ai jamais utilisé de grandes valeurs comme paramètres dans la WHEREclause. Je les ai utilisés INSERTet je les ai récupérés en utilisant SELECT. Votre cas est différent.
L'Empaleur
@The_impaler il y a un lien vers les documents dans la question. Ce n'est pas un api, j'ai peur, mais c'est tout ce que nous avons. Il s'agit d'une fonction très niche. Le besoin est que je recherche une représentation numérique d'une molécule, et vous avez besoin d'une fonction spécialisée pour le faire. c'est-à-dire que la molécule que j'ai existe déjà dans la table dcr_mols.
DS.
Quelle version d'Oracle utilisez-vous?
areus
@areaus ojdbc6-11.2.1.0.1
DS.

Réponses:

5

Diffusez-le. Vous ne pouvez pas simplement coller une énorme valeur dans une instruction SQL.

Vous devrez:

  • Insérez un BLOB vide dans l' INSERTinstruction (en utilisant EMPTY_BLOB ()? ... je ne me souviens pas très bien).
  • Obtenez le flux de sortie pour le blob vide.
  • Obtenez ensuite un flux d'entrée à partir du fichier. Veuillez ne pas charger le fichier entier en mémoire.
  • Transférez ensuite les blocs du flux d'entrée dans le flux de sortie à l'aide de la mise en mémoire tampon. Un tampon de 16 Ko devrait suffire.
  • Fermez les deux flux.

Il s'agit de la manière standard de traiter des données massives dans Oracle. Beaucoup d'exemples là-bas.

La récupération de données massives ( BLOBet de CLOBtypes) fonctionne de la même manière. Utilisez simplement InputStreams dans ce cas.

L'Impaler
la source
@The_impaler, je n'insère pas de bloc. Je fournis un clob à une fonction qui est appelée dans le prédicat d'un select
DS.
1

En lisant la documentation de l'API "BIOVIA Direct", voici un exemple intéressant à la page 27, extrait ci-dessous:

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

Il utilise un CLOB déjà chargé . Par conséquent, je suppose qu'une solution décente serait de charger le CLOB dans une de vos tables (ou de les précharger tous à l'avance), puis de les utiliser.

Étape # 1 - Chargez votre CLOB dans une table:

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

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

Et utilisez votre code Java pour effectuer l'insertion CLOB (probablement à l'aide de flux) comme indiqué dans une autre réponse (de nombreux exemples sur Internet). Par exemple, insérez votre contenu de données mol avec ID = 123.

Étape # 2 - Exécutez votre requête, en utilisant le fichier mol déjà chargé:

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

Vous pouvez définir le :idparamètre pour 123utiliser le fichier que vous avez chargé avant (ou tout autre).

L'Impaler
la source
@The_impaler Merci, mais c'est un peu un marteau pour casser une noix. Nous avons beaucoup de requêtes à exécuter et cela compliquerait le code et ralentirait les choses. J'ai mis à jour ma question, avec une réponse old school, que j'utiliserai à contrecœur si rien de mieux n'arrive.
DS.
1

Vous pouvez appeler votre ancienne fonction comme ça. créer une classe pour gérer le 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;
    }
}

et appelez votre requête en utilisant le JdbcTemplate dans votre méthode

 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
la source
1

J'ai dû revenir à l'utilisation d'un PreparedStatement, mais j'ai un peu amélioré l'implémentation normale en obtenant la connexion de Spring et en utilisant apache commons BeanListHandler pour mapper le ResultSet à une liste d'objets

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.
la source
0

Vous pouvez déclarer fileDataStr comme CLOB en utilisant con qui est la connexion

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

puis l'utiliser comme ci-dessous

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

Aussi, si vous utilisez SID au lieu du nom du service dans votre chaîne de connexion, essayez de changer votre fichier de propriétés comme ci-dessous

spring.datasource.url=jdbc:oracle:thin:@//${ORA_HOST}:${ORA_PORT}:${ORA_SID}
psaraj12
la source
Merci, mais c'est très gênant. Les données peuvent être de n'importe quelle taille, donc je devrais utiliser SQL dynamique pour créer les morceaux, aussi, je ne suis pas sûr que vous puissiez diviser un bloc en plusieurs parties comme celle-ci.
DS.
quel est le type de fileDataStr
psaraj12
C'est une chaîne java
DS.
pouvez-vous le déclarer comme CLOB et comme java.sql.Clob fileDataStr = oracle.sql.CLOB.createTemporary (con, false, oracle.sql.CLOB.DURATION_SESSION);
psaraj12
voir mon exemple en commentaire sur la façon de déclarer fileDataStr comme CLOB où con est une connexion
psaraj12