Використовуйте клоб Oracle у присудку, створеному з рядка> 4k


11

Я намагаюся створити клоб із рядка> 4000 знаків (надається у змінній файлу файлу_даних), який буде використаний у предикаті Oracle SELECT нижче:

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

Якщо я додаю TO_CLOB () круглий file_data, це не відповідає сумнозвісному обмеженню Oracle 4k для varchar (це добре для <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

Сама функція трохи складна, але суть у тому, що другий параметр повинен бути клобом. Отже, моє запитання полягає в тому, як перетворити Java-рядок bind_variable з понад 4000 символів у clob в sql (або Java).

Я спробував метод нижче (який працює при вставці клопів) в Java (Spring boot 2), використовуючи:

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

Цей метод повинен працювати, але він не вдається зі збіжною помилкою flexmatch, яка є 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"

Зауважте, що я використовую SpringBoot 2, але я не можу отримати жодного методу за допомогою OracleConnection (отриманого від мого об'єкта 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}

Зверніть увагу, що це добре працює, якщо я переходжу в стару школу і використовую непідпрудне з'єднання плюс PreparedStatement, у якому є метод 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();

Але я вважаю за краще рішення Spring 2 на Java або Sql. Будь-яка допомога, пропозиції високо оцінені.


Це досить гарне запитання +1, відмінне від того, що я робив досі. Чи можете ви вказати документи API для flexmatch()функції? Я хотів би бачити потребу в цьому. Чесно кажучи, я ніколи не використовував великі значення як параметри в WHEREпункті. Я використовував їх, INSERTі я їх отримував за допомогою SELECT. Ваш випадок інший.
The Impaler

@The_impaler у запитанні є посилання на документи. Це не страх, але це все, що ми маємо. Це дуже ніша функція. Необхідність полягає в тому, що я шукаю цифрове представлення молекули, і для цього вам потрібна спеціалізована функція. тобто молекула, яку я вже існував у таблиці dcr_mols.
ДС.

Яку версію Oracle ви використовуєте?
areus

@areaus ojdbc6-11.2.1.0.1
DS.

Відповіді:


5

Потоку. Ви не можете просто вставити величезне значення в оператор SQL.

Вам потрібно буде:

  • Вставте порожній BLOB в INSERTоператор (використовуючи EMPTY_BLOB ()? ... не зовсім пам'ятаю).
  • Отримайте вихідний потік для порожнього блобу.
  • Потім отримайте вхідний потік з файлу. Не завантажуйте весь файл у пам'ять.
  • Потім перенесіть блоки з вхідного потоку у вихідний потік, використовуючи буферизацію. Буфер 16 Кб повинен робити.
  • Закрийте обидва потоки.

Це стандартний спосіб поводження з масовими даними в Oracle. Багато прикладів там.

Отримання масових даних ( BLOBі CLOBтипів) працює аналогічно. Просто використовуйте InputStreams у такому випадку.


@The_impaler я не вставляю клобу. Я постачаю клоб функції, яка називається в предикаті вибору
DS.

1

Читаючи документацію API "BIOVIA Direct", є цікавий приклад на сторінці 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 (можливо, використовуючи потоки), як показано в іншій відповіді (безліч прикладів в Інтернеті). Наприклад, вставити вміст даних мовляв з 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 Спасибі, але це трохи кувалда, щоб зламати горіх. У нас багато запущених запитів, і це ускладнить код і сповільнить речі. Я оновив своє запитання зі старою шкільною відповіддю, яку я неохоче використовую, якщо нічого кращого не виявиться.
ДС.

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 та використовуючи апаш-общинник 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

Ви можете оголосити fileDataStr як CLOB, використовуючи con, який є з'єднанням

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, щоб створити шматки, також, я не впевнений, що ви можете розбити клобу на такі частини.
ДС.

який тип файлуDataStr
psaraj12

Це java String
DS.

чи можете ви оголосити це як CLOB і схоже на java.sql.Clob fileDataStr = oracle.sql.CLOB.createTemporary (con, false, oracle.sql.CLOB.DURATION_SESSION);
psaraj12

дивіться мій приклад у коментарі про те, як оголосити fileDataStr як CLOB, де con - це з'єднання
psaraj12
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.