Verwenden Sie einen Oracle-Clob in einem Prädikat, das aus einem String> 4k erstellt wurde

11

Ich versuche, aus einer Zeichenfolge von> 4000 Zeichen (in der Bindevariablen file_data enthalten) einen Clob zu erstellen, der in einem der folgenden Oracle SELECT-Prädikate verwendet wird:

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

Wenn ich TO_CLOB () round file_data hinzufüge, verfehlt dies das berüchtigte Oracle 4k-Limit für einen Varchar (es ist in Ordnung für <4k-Strings). Der Fehler (in SQL Developer) ist:

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

Zu Ihrer Information Die Flexmatch-Funktion wird zum Suchen von Molekülen verwendet und hier beschrieben: http://help.accelrysonline.com/ulm/onelab/1.0/content/ulm_pdfs/direct/developers/direct_2016_developersguide.pdf

Die Funktion selbst ist etwas kompliziert, aber das Wesentliche ist, dass der 2. Parameter ein Clob sein muss. Meine Frage ist also, wie ich eine Java-Zeichenfolge bind_variable mit über 4000 Zeichen in einen Clob in SQL (oder Java) konvertiere.

Ich habe die folgende Methode (die beim Einfügen von Clobs funktioniert) in Java (Spring Boot 2) mit folgenden Methoden ausprobiert:

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

Diese Methode sollte funktionieren, schlägt jedoch mit einem überlasteten Flexmatch-Fehler fehl, der zu Ihrer Information lautet:

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"

Beachten Sie, dass ich SpringBoot 2 verwende, aber mit einer OracleConnection (die von meinem Spring NamedParametersJdbcTemplate-Objekt erhalten wurde) keine Methode zum Arbeiten (auch bei Clobs <4k) erhalten kann. Ich vermute also, dass ich etwas Dummes getan habe. Ich habe es versucht:

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

Beachten Sie, dass es gut funktioniert, wenn ich in die alte Schule gehe und eine Nicht-Feder-Verbindung sowie ein PreparedStatement verwende, das eine setClob () -Methode hat:

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

Aber ich würde eine Spring 2-Lösung in Java oder SQL bevorzugen. Jede Hilfe, Vorschläge geschätzt.

DS.
quelle
Dies ist eine ziemlich gute Frage +1, anders als ich es bisher getan habe. Können Sie auf die API-Dokumente für die flexmatch()Funktion hinweisen ? Ich würde gerne die Notwendigkeit dafür sehen. Ehrlich gesagt habe ich in der WHEREKlausel nie große Werte als Parameter verwendet . Ich habe sie in verwendet INSERTund ich habe sie mit abgerufen SELECT. Ihr Fall ist anders.
Der Impaler
@The_impaler gibt es einen Link zu den Dokumenten in der Frage. Es ist keine API, fürchte ich, aber es ist alles was wir haben. Dies ist eine sehr Nischenfunktion. Die Notwendigkeit ist, dass ich eine digitale Darstellung eines Moleküls suche, und Sie benötigen eine spezielle Funktion, um dies zu tun. dh existiert das Molekül, das ich bereits in der Tabelle dcr_mols habe.
DS.
Welche Oracle-Version verwenden Sie?
Areus
@areaus ojdbc6-11.2.1.0.1
DS.

Antworten:

5

Streame es. Sie können nicht einfach einen großen Wert in eine SQL-Anweisung einfügen.

Sie müssen:

  • Fügen Sie ein leeres BLOB in die INSERTAnweisung ein (mit EMPTY_BLOB ()? ... nicht ganz erinnern).
  • Holen Sie sich den Ausgabestream für den leeren Blob.
  • Holen Sie sich dann einen Eingabestream aus der Datei. Bitte laden Sie nicht die gesamte Datei in den Speicher.
  • Übertragen Sie dann Blöcke vom Eingabestream mithilfe von Pufferung in den Ausgabestream. Ein 16-KB-Puffer sollte ausreichen.
  • Schließen Sie beide Streams.

Dies ist die Standardmethode für den Umgang mit massiven Daten in Oracle. Viele Beispiele da draußen.

Das Abrufen umfangreicher Daten ( BLOBund CLOBTypen) funktioniert auf die gleiche Weise. Verwenden Sie in diesem Fall einfach InputStreams.

Der Pfähler
quelle
@ The_impaler Ich füge keinen Clob ein. Ich Versorgung einen clob zu einer Funktion , die in dem Prädikat eines select genannt wird
DS.
1

Wenn Sie die Dokumentation der API "BIOVIA Direct" lesen, finden Sie auf Seite 27 ein interessantes Beispiel, Auszug unten:

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

Es wird ein bereits geladenes CLOB verwendet. Daher denke ich, dass eine ausreichend gute Lösung darin besteht, das CLOB in eine Tabelle von Ihnen zu laden (oder sie alle im Voraus vorzuladen) und sie dann einfach zu verwenden.

Schritt 1 - Laden Sie Ihren CLOB in eine Tabelle:

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

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

Verwenden Sie Ihren Java-Code, um die CLOB-Einfügung (wahrscheinlich mithilfe von Streams) durchzuführen, wie in einer anderen Antwort gezeigt (viele Beispiele im Internet). Fügen Sie beispielsweise Ihren Mol-Dateninhalt mit ID = ein 123.

Schritt 2 - Führen Sie Ihre Abfrage mit der bereits geladenen mol-Datei aus:

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

Sie können den :idParameter so einstellen 123, dass er die zuvor geladene Datei (oder eine andere) verwendet.

Der Pfähler
quelle
@ The_impaler Danke, aber das ist ein bisschen ein Vorschlaghammer, um eine Nuss zu knacken. Wir müssen viele Abfragen ausführen, was den Code komplizieren und die Dinge verlangsamen würde. Ich habe meine Frage mit einer Antwort der alten Schule aktualisiert, die ich nur ungern verwenden werde, wenn nichts Besseres auftaucht.
DS.
1

Sie können Ihre alte Modefunktion so nennen. Erstellen Sie eine Klasse für das 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;
    }
}

und rufen Sie Ihre Abfrage mit der JdbcTemplate in Ihrer Methode auf

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

Ich musste wieder ein PreparedStatement verwenden, aber ich habe die normale Implementierung ein wenig verbessert, indem ich die Verbindung von Spring abgerufen und Apache Commons BeanListHandler verwendet habe, um das ResultSet einer Objektliste zuzuordnen

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

Sie können fileDataStr mit con als Verbindung deklarieren

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

und dann benutze es wie unten

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

Wenn Sie in Ihrer Verbindungszeichenfolge die SID anstelle des Dienstnamens verwenden, ändern Sie die Eigenschaftendatei wie unten beschrieben

spring.datasource.url=jdbc:oracle:thin:@//${ORA_HOST}:${ORA_PORT}:${ORA_SID}
psaraj12
quelle
Danke, aber das ist sehr umständlich. Die Daten können eine beliebige Größe haben, daher müsste ich dynamisches SQL verwenden, um die Chunks zu erstellen. Außerdem bin ich mir nicht sicher, ob Sie einen Clob in solche Teile aufteilen können.
DS.
Was ist der Typ von fileDataStr
psaraj12
Es ist ein Java-String
DS.
können Sie es als CLOB deklarieren und wie java.sql.Clob fileDataStr = oracle.sql.CLOB.createTemporary (con, false, oracle.sql.CLOB.DURATION_SESSION);
psaraj12
Siehe mein Beispiel im Kommentar zum Deklarieren von fileDataStr als CLOB, wobei con eine Verbindung ist
psaraj12