4kを超える文字列から作成された述語でOracle clobを使用する


11

以下のOracle SELECT述語で使用するために、4000文字を超える文字列(file_dataバインド変数で提供)からCLOBを作成しようとしています。

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

TO_CLOB()ラウンドfile_dataを追加すると、varcharの悪名高いOracle 4k制限に失敗します(<4k文字列の場合は問題ありません)。エラー(SQL Developerの場合)は次のとおりです。

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

FYI flexmatch関数は分子の検索に使用され、ここで説明されています:http ://help.accelrysonline.com/ulm/onelab/1.0/content/ulm_pdfs/direct/developers/direct_2016_developersguide.pdf

関数自体は少し複雑ですが、本質は2番目のパラメーターがCLOBでなければなりません。したがって、私の質問は、4000文字を超えるJava文字列bind_variableをSQL(またはJava)のCLOBに変換する方法です。

私は以下を使用してJava(Spring boot 2)で以下の方法(CLOBを挿入するときに機能します)を試しました:

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

この方法は機能するはずですが、FYIである収束したflexmatchエラーで失敗します。

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"

SpringBoot 2を使用していますが、Oracle Connection(Spring NamedParametersJdbcTemplateオブジェクトから取得)を使用して(clobs <4kでも)機能するメソッドを取得できないため、私は何か愚かなことをしたのではないかと思います。私はもう試した:

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

私が古い学校に行って、スプリング以外の接続に加えて、setClob()メソッドを持つPreparedStatementを使用すると、問題なく機能することに注意してください。

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

しかし、私はJavaまたはSqlのSpring 2ソリューションを好みます。どんな助け、提案も歓迎します。


これは非常に良い質問です+1、これまでに行ったこととは異なります。flexmatch()関数のAPIドキュメントを指摘できますか?これの必要性を見たいのですが。正直なところ、WHERE節のパラメータとして大きな値を使用したことはありません。私はそれらを使用してINSERTおり、を使用して取得していSELECTます。あなたのケースは異なります。
インパラー

@The_impaler質問のドキュメントへのリンクがあります。恐れることはありませんが、私たちが持っているのはそれだけです。これは非常にニッチな機能です。必要なのは、分子のデジタル表現を検索することです。これを行うには、専門的な機能が必要です。つまり、すでにdcr_molsテーブルに存在している分子はありますか。
DS。

どのOracleバージョンを使用していますか?
areus

@areaus ojdbc6-11.2.1.0.1
DS。

回答:


5

それをストリーミングします。SQLステートメントに巨大な値を貼り付けることはできません。

次のことを行う必要があります。

  • INSERTステートメントに空のBLOBを挿入します(EMPTY_BLOB()を使用しますか?...よく覚えていません)。
  • 空のblobの出力ストリームを取得します。
  • 次に、ファイルから入力ストリームを取得します。ファイル全体をメモリにロードしないでください。
  • 次に、バッファリングを使用して、ブロックを入力ストリームから出力ストリームに転送します。16 KBのバッファで十分です。
  • 両方のストリームを閉じます。

これは、Oracleで大量のデータを処理する標準的な方法です。そこにはたくさんの例があります。

大量のデータ(BLOBおよびCLOBタイプ)の取得も同じように機能します。その場合は、InputStreamsを使用してください。


@The_impaler私は塊を挿入していません。select
DSの

1

「BIOVIA Direct」APIのドキュメントを読むと、27ページに興味深い例があり、以下に抜粋します。

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

すでにロードされている CLOBを使用します。したがって、適切な解決策は、CLOBを自分のテーブルにロードして(または事前にすべて事前にロードして)、そのまま使用することです。

ステップ#1-CLOBをテーブルにロードします。

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

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

そして、Javaコードを使用して、(おそらくストリームを使用して)CLOB挿入を実行します。これは、別の回答(インターネットにある多くの例)に示されています。たとえば、molデータコンテンツをID =で挿入します123

ステップ#2-既にロードされたmolファイルを使用してクエリを実行します。

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

以前にロードしたファイル(またはその他のファイル)を使用するように:idパラメーターを設定でき123ます。


@The_impalerありがとう、でもそれはナッツを割るためのちょっとした大槌です。実行するクエリがたくさんあるため、コードが複雑になり、処理速度が低下します。私は質問を更新しましたが、古い学校の回答を使用しています。
DS。

1

このようにして、古いファッション関数を呼び出すことができます。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;
    }
}

メソッドでJdbcTemplateを使用してクエリを呼び出します

 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

PreparedStatementの使用に戻す必要がありましたが、Springから接続を取得し、Apache Commons BeanListHandlerを使用してResultSetをオブジェクトリストにマップすることにより、通常の実装を少し改善しました

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

接続であるconを使用して、fileDataStrをCLOBとして宣言できます。

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

以下のように使用します

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

また、接続文字列でサービス名の代わりにSIDを使用している場合は、以下のようにプロパティファイルを変更してみてください

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

ありがとう、でもそれはとても厄介です。データは任意のサイズにすることができるので、動的SQLを使用してチャンクを作成する必要があります。また、このようにclobをいくつかの部分に分割できるかどうかもわかりません。
DS。

fileDataStrのタイプは何ですか
psaraj12

それはjava文字列です
DS。

java.sql.Clob fileDataStr = oracle.sql.CLOB.createTemporary(con、false、oracle.sql.CLOB.DURATION_SESSION);のように、それをCLOBとして宣言できますか。
psaraj12

conが接続であるfileDataStrをCLOBとして宣言する方法に関するコメントの私の例を参照してください
psaraj12
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.