Użyj Oracle Clob w predykacie utworzonym z String> 4k

11

Próbuję utworzyć clob z ciągu> 4000 znaków (podanego w zmiennej powiązania file_data) do użycia w predykacie Oracle SELECT poniżej:

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

Jeśli dodam TO_CLOB () okrągłe dane_pliku, nie powiedzie się niesławny limit Oracle 4k dla varchara (jest w porządku dla <4k ciągów). Błąd (w SQL Developer) to:

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

FYI Funkcja flexmatch służy do wyszukiwania cząsteczek i jest opisana tutaj: http://help.accelrysonline.com/ulm/onelab/1.0/content/ulm_pdfs/direct/developers/direct_2016_developersguide.pdf

Sama funkcja jest nieco skomplikowana, ale istotą jest to, że drugim parametrem musi być clob. Więc moje pytanie brzmi: jak przekonwertować zmienną wiążącą Java String o wartości ponad 4000 znaków na clob w sql (lub Java).

Wypróbowałem poniższą metodę (która działa podczas wstawiania bloków) w Javie (Spring boot 2), używając:

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

Ta metoda powinna działać, ale kończy się niepowodzeniem z skonwertowanym błędem dopasowania elastycznego, który 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"

Uwaga: używam SpringBoot 2, ale nie mogę uzyskać żadnej metody przy użyciu OracleConnection (uzyskanej z mojego obiektu Spring NamedParametersJdbcTemplate) do pracy (nawet na blokach <4k), więc podejrzewam, że zrobiłem coś głupiego. Próbowałem:

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

Zauważ, że działa dobrze, jeśli pójdę do starej szkoły i użyję nie-sprężynowego połączenia plus PreparedStatement, który ma metodę 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();

Ale wolałbym rozwiązanie Spring 2 w Javie lub Sql. Każda pomoc, sugestie mile widziane.

DS.
źródło
To całkiem dobre pytanie +1, inne niż do tej pory. Czy możesz wskazać dokumenty API dla tej flexmatch()funkcji? Chciałbym dostrzec potrzebę tego. Szczerze mówiąc, nigdy nie użyłem dużych wartości jako parametrów w WHEREklauzuli. Użyłem ich INSERTi odzyskuję za pomocą SELECT. Twoja sprawa jest inna.
Impaler
@ The_impaler jest link do dokumentów w pytaniu. Nie boję się api, ale to wszystko, co mamy. To bardzo niszowa funkcja. Potrzebuję szukać cyfrowej reprezentacji cząsteczki i potrzebujesz do tego specjalistycznej funkcji. tzn. czy cząsteczka, którą już mam, istnieje w tabeli dcr_mols.
DS.
Jakiej wersji Oracle używasz?
areus
@areaus ojdbc6-11.2.1.0.1
DS.

Odpowiedzi:

5

Przesyłaj strumieniowo. Nie można po prostu wkleić dużej wartości do instrukcji SQL.

Musisz:

  • Wstaw pusty BLOB do INSERTinstrukcji (używając EMPTY_BLOB ()? ... nie całkiem pamiętasz).
  • Uzyskaj strumień wyjściowy dla pustego obiektu blob.
  • Następnie pobierz strumień wejściowy z pliku. Nie ładuj całego pliku do pamięci.
  • Następnie przenieś bloki ze strumienia wejściowego do strumienia wyjściowego za pomocą buforowania. Powinien wystarczyć bufor 16 KB.
  • Zamknij oba strumienie.

Jest to standardowy sposób radzenia sobie z ogromnymi danymi w Oracle. Wiele przykładów.

Pobieranie ogromnych danych ( BLOBi CLOBtypów) działa w ten sam sposób. W takim przypadku wystarczy użyć InputStreams.

The Impaler
źródło
@ The_impaler nie wstawiam clob. Dostarczam clob do funkcji, która jest wywoływana w predykacie select
DS.
1

Czytając dokumentację API „BIOVIA Direct”, interesujący przykład znajduje się na stronie 27, fragment pokazany poniżej:

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

Używa już załadowanego CLOB. Dlatego chyba przyzwoitym rozwiązaniem byłoby załadowanie CLOBa do twojej tabeli (lub wstępne załadowanie ich wszystkich z wyprzedzeniem), a następnie po prostu ich użycie.

Krok # 1 - Załaduj CLOB do tabeli:

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

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

I użyj kodu Java, aby wykonać wstawianie CLOB (prawdopodobnie przy użyciu strumieni), jak pokazano w innej odpowiedzi (wiele przykładów w Internecie). Na przykład wstaw zawartość danych mol z ID = 123.

Krok # 2 - Uruchom zapytanie, używając już załadowanego pliku mol:

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

Możesz ustawić :idparametr tak, 123aby używał wcześniej załadowanego pliku (lub dowolnego innego).

The Impaler
źródło
@The_impaler Dzięki, ale to trochę młot, żeby złamać orzecha. Musimy uruchomić wiele zapytań, co skomplikuje kod i spowolni działanie. Zaktualizowałem moje pytanie, udzielając odpowiedzi ze starej szkoły, której niechętnie użyję, jeśli nic lepszego się nie pojawi.
DS.
1

Możesz tak nazwać swoją starą funkcję mody. utwórz klasę do obsługi zestawu wyników

 class MyPreparedStatementCallback implements PreparedStatementCallback {
    public Object doInPreparedStatement(PreparedStatement preparedStatement)
            throws SQLException, DataAccessException {
        ResultSet rs = preparedStatement.executeQuery();
        List result = new LinkedList();
        rs.close();
        return result;
    }
}

i wywołaj zapytanie za pomocą JdbcTemplate w swojej metodzie

 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
źródło
1

Musiałem powrócić do korzystania z PreparedStatement, ale poprawiłem nieco normalną implementację, uzyskując połączenie ze Spring i używając wspólnych apache BeanListHandler do mapowania zestawu wyników na listę obiektów

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.
źródło
0

Możesz zadeklarować fileDataStr jako CLOB, używając con, który jest połączeniem

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

a następnie użyj go jak poniżej

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

Również jeśli używasz SID zamiast nazwy usługi w ciągu połączenia, spróbuj zmienić plik właściwości jak poniżej

spring.datasource.url=jdbc:oracle:thin:@//${ORA_HOST}:${ORA_PORT}:${ORA_SID}
psaraj12
źródło
Dzięki, ale to bardzo niezręczne. Dane mogą mieć dowolny rozmiar, więc musiałbym użyć dynamicznego narzędzia SQL do utworzenia porcji, a także nie jestem pewien, czy można rozbić clob na takie części.
DS.
jaki jest typ fileDataStr
psaraj12
To java String
DS.
czy możesz zadeklarować go jako CLOB i jak java.sql.Clob fileDataStr = oracle.sql.CLOB.createTemporary (con, false, oracle.sql.CLOB.DURATION_SESSION);
psaraj12
zobacz mój przykład w komentarzu na temat deklarowania fileDataStr jako CLOB, gdzie con jest połączeniem
psaraj12