Reading clob line by line with pl\sql

后端 未结 9 933
长情又很酷
长情又很酷 2020-12-02 00:53

In my project i use oracle as primary database and i\'ve faced a problem with parsing clob. So suppose we have a clob with value

   aaaaaa
   cccccc
   bbbbb         


        
相关标签:
9条回答
  • 2020-12-02 01:22

    I created a table called lixo_mq:

    CREATE TABLE LIXO_MQ (CAMPO1 VARCHAR2(4000))
    

    I copied printout procedure and changed it to work different:

    PROCEDURE PRINTOUT (P_CLOB IN OUT NOCOPY CLOB) IS
       V_APARTIR                     NUMBER (20);
       V_CONTAR                      NUMBER (20);
       V_LINHA                       VARCHAR2 (4000);
       V_REG                         NUMBER (20);
       V_CORINGA                     VARCHAR2 (10) := CHR (10);
       V_ERRO                        VARCHAR2 (4000);
    BEGIN
       IF (DBMS_LOB.ISOPEN (P_CLOB) != 1) THEN
          DBMS_LOB.OPEN (P_CLOB, 0);
       END IF;
    
       V_APARTIR                  := 1;
       V_REG                      := 1;
    
       WHILE DBMS_LOB.INSTR (LOB_LOC                       => P_CLOB
                            ,PATTERN                       => V_CORINGA
                            ,OFFSET                        => 1
                            ,NTH                           => V_REG
                            ) > 0
       LOOP
          V_CONTAR                   :=
                     DBMS_LOB.INSTR (LOB_LOC                       => P_CLOB
                                    ,PATTERN                       => V_CORINGA
                                    ,OFFSET                        => 1
                                    ,NTH                           => V_REG
                                    )
                   - V_APARTIR;
    
          IF V_APARTIR > 1 THEN
             V_LINHA                    :=
                             DBMS_LOB.SUBSTR (LOB_LOC                       => P_CLOB
                                             ,AMOUNT                        =>   V_CONTAR
                                                                               - 1
                                             ,OFFSET                        =>   V_APARTIR
                                                                               + 1
                                             );
          ELSE
             V_LINHA                    :=
                                     DBMS_LOB.SUBSTR (LOB_LOC                       => P_CLOB
                                                     ,AMOUNT                        => V_CONTAR
                                                     ,OFFSET                        => V_APARTIR
                                                     );
          END IF;
    
          INSERT INTO LIXO_MQ
                      (CAMPO1
                      )
               VALUES (   V_REG
                       || ':'
                       || V_LINHA
                      );
    
          COMMIT;
          V_APARTIR                  :=
                               DBMS_LOB.INSTR (LOB_LOC                       => P_CLOB
                                              ,PATTERN                       => V_CORINGA
                                              ,OFFSET                        => 1
                                              ,NTH                           => V_REG
                                              );
          V_REG                      :=   V_REG
                                        + 1;
       END LOOP;
    
       IF (DBMS_LOB.ISOPEN (P_CLOB) = 1) THEN
          DBMS_LOB.CLOSE (P_CLOB);
       END IF;
    EXCEPTION
       WHEN OTHERS THEN
          V_ERRO                     :=    'Error : '
                                        || SQLERRM;
    
          INSERT INTO LIXO_MQ
                      (CAMPO1
                      )
               VALUES (V_ERRO
                      );
    
          COMMIT;
    END PRINTOUT;
    
    0 讨论(0)
  • 2020-12-02 01:26

    Emmanuel's Answer

    This is an elegant solution that works just fine with clobs exceeding 32767 chars or lines exceeding 4K characters.

    ANSI Standard Query:

    DECLARE
      v_tmp clob :='aaaa'||chr(10)||
                   'bbb'||chr(10)||
                   'ccccc';
    BEGIN
      FOR rec IN (WITH clob_table(c) as (SELECT v_tmp c FROM DUAL),
                       recurse(text,line) as (SELECT regexp_substr(c, '.+', 1, 1) text,1 line
                                                FROM clob_table
                                               UNION ALL
                                              SELECT regexp_substr(c, '.+', 1, line+1),line+1
                                                FROM recurse r,clob_table
                                               WHERE line<regexp_count(c, '.+'))
                SELECT text,line FROM recurse) LOOP
        dbms_output.put_line(rec.text);
      END LOOP;
    END;
    

    Oracle Specific Query (original post):

    DECLARE
      v_tmp clob :='aaaa'||chr(10)||
                   'bbb'||chr(10)||
                   'ccccc';
    BEGIN
      FOR rec IN (WITH clob_table(c) as (SELECT v_tmp c FROM DUAL)
                SELECT regexp_substr(c, '.+', 1, level) text,level line
                 FROM clob_table
              CONNECT BY LEVEL <= regexp_count(c, '.+')) LOOP
          dbms_output.put_line(rec.text);
      END LOOP;
    END;
    
    0 讨论(0)
  • 2020-12-02 01:29

    This is a follow-up answer based on @Pierre-Gilles Levallois his answer. Since I think it contained some bugs, I've tried to fix them.

    • Empty lines caused errors
    • Newlines where still in the print output (might be desired)
    • "amount :=" was not inside the while loop, i think this caused a big bug if any value was shorter than the first line in the CLOB

    I've implemented this as rather quick and dirty fixes. I'm sure there should be more elegant solutions... Here goes. The example table:

    create table test (c clob);
    
    insert into test (c) values (
    -- line 1 (empty)
    chr(10)||'line 2'
    ||chr(10) -- line 3 (empty)
    ||chr(10)||'line 4'
    ||chr(10)||'line 5'
    ||chr(10)); -- line 6 (empty)
    

    And the altered code:

    set serveroutput on;
    declare
        cursor c_clob is
        select c from test;
    
        c clob;
        -------------------------------
        procedure printout
           (p_clob in out nocopy clob) is
          offset number := 1;
          amount number := 32767;
          len    number := dbms_lob.getlength(p_clob);
          lc_buffer varchar2(32767);
          i pls_integer := 1;
        begin
          if ( dbms_lob.isopen(p_clob) != 1 ) then
            dbms_lob.open(p_clob, 0);
          end if;
          while ( offset < len )
              loop
                -- If no more newlines are found, read till end of CLOB
                if (instr(p_clob, chr(10), offset) = 0) then
                    amount := len - offset + 1;
                else
                    amount := instr(p_clob, chr(10), offset) - offset;
                end if;
    
                -- This is to catch empty lines, otherwise we get a NULL error
                if ( amount = 0 ) then
                    lc_buffer := '';
                else
                    dbms_lob.read(p_clob, amount, offset, lc_buffer);
                end if;
                dbms_output.put_line('Line #'||i||':'||lc_buffer);
    
                -- This is to catch a newline on the last line with 0 characters behind it
                i := i + 1;
                if (instr(p_clob, chr(10), offset) = len) then
                    lc_buffer := '';
                    dbms_output.put_line('Line #'||i||':'||lc_buffer);
                end if;
    
                offset := offset + amount + 1;
              end loop; 
         if ( dbms_lob.isopen(p_clob) = 1 ) then
            dbms_lob.close(p_clob);
          end if; 
        exception
          when others then
             dbms_output.put_line('Error : '||sqlerrm);
        end printout;
        ---------------------------
    begin
        dbms_output.put_line('-----------');
        open c_clob;
        loop
           fetch c_clob into c;
           exit when c_clob%notfound;
           printout(c);
        end loop;
        close c_clob;
    end;
    
    0 讨论(0)
  • 2020-12-02 01:30

    In case... - you have APEX installed - and the clob is less than 32K you may also want to look into the following code:

    declare
      l_text varchar2(32767) := '...';
      l_rows wwv_flow_global.vc_arr2;
    begin
      l_rows := apex_util.string_to_table(l_text, chr(10));
      for i in 1 .. l_rows.count loop
        dbms_output.put_line(l_rows(i));
      end loop;
    end;
    /
    
    0 讨论(0)
  • 2020-12-02 01:31

    sample for dynamicly length of rows

    and alternative for UNIX and WIN files

    and CR/LF on end of file or without it


    Create table for TEST

    drop table pbrev.test_SVT_tmp;
    create table pbrev.test_SVT_tmp (xc clob);
    insert into pbrev.test_SVT_tmp (xc) values (
    --'azertyuiop;11' || chr(13) || chr(10) ||'qsdfghjklm;7878' || chr(13) || chr(10) ||'wxcvbn;0' || chr(13) || chr(10) );
    'azertyuiop;11' || chr(13) || chr(10) ||'qsdfghjklm;7878' || chr(13) || chr(10) ||'wxcvbn;0' );
    'azerty jhjh  huiop;11
    qsdfgkj  hjklhhhhhhhhhhhm;7878
    wxcvbn;0
    dkjsk kjdsk5456 4654 5646 54645
    FINISH'
    );
    delete from pbrev.test_SVT_tmp ;
    select xc from pbrev.test_SVT_tmp;
    
    --SET SERVEROUTPUT ON;
    --SET SERVEROUTPUT OFF;
    declare
        nStartIndex number := 1;
        nEndIndex number := 1;
        nLineIndex number := 0;
        vLine varchar2(2000);
        cursor c_clob is
        select xc from pbrev.test_SVT_tmp;
        c clob;
        procedure printout
           (p_clob in out nocopy clob) is
          offset number := 1;
          amount number := 32767;
          amount_last number := 0;
          len    number := dbms_lob.getlength(p_clob);
          lc_buffer varchar2(32767);
          line_seq pls_integer := 1;
          -- For UNIX type file - replace CHR(13) to NULL
          CR char := chr(13);
          --CR char := NULL;
          LF char := chr(10);      
          nCRLF number;
          sCRLF varchar2(2);
          b_finish boolean := true;
    begin
          sCRLF := CR || LF;
          nCRLF := Length(sCRLF);
          if ( dbms_lob.isopen(p_clob) != 1 ) then
            dbms_lob.open(p_clob, 0);
          end if;
          amount := instr(p_clob, sCRLF, offset);
          while ( offset < len )
          loop
            -- For without CR/LF on end file
            If amount < 0 then
              amount := len - offset + 1;
              b_finish := false;
            End If;
            dbms_lob.read(p_clob, amount, offset, lc_buffer);
            If b_finish then
              lc_buffer := SUBSTR(lc_buffer,1,Length(lc_buffer)-1);  
            End If;
            if (line_seq-1) > 0 then
              amount_last := amount_last + amount;
              offset := offset + amount; 
            else
              amount_last := amount;
              offset := amount + nCRLF;
            end if;
            amount := instr(p_clob, sCRLF, offset);
            amount := amount - amount_last;
            dbms_output.put_line('Line #'||line_seq||': '||lc_buffer);
            line_seq := line_seq + 1;
          end loop; 
          if ( dbms_lob.isopen(p_clob) = 1 ) then
            dbms_lob.close(p_clob);
          end if; 
        exception
          when others then
             dbms_output.put_line('Error : '||sqlerrm);
        end printout;
    begin
        open c_clob;
        loop
           fetch c_clob into c;
           exit when c_clob%notfound;
           printout(c);
        end loop;
        close c_clob;
    end;
    
    0 讨论(0)
  • 2020-12-02 01:35

    While the SQL regexp / connect by level approach is probably the most elegant, it is quite bad performancewise (for my testcase on 11.2.0.3.0). Much faster is a simple parse like this.

    procedure parse_clob(p_clob in clob) is
    l_offset pls_integer:=1;
    l_line varchar2(32767);
    l_total_length pls_integer:=length(p_clob);
    l_line_length pls_integer;
    begin
      while l_offset<=l_total_length loop
        l_line_length:=instr(p_clob,chr(10),l_offset)-l_offset;
        if l_line_length<0 then
          l_line_length:=l_total_length+1-l_offset;
        end if;
        l_line:=substr(p_clob,l_offset,l_line_length);
        dbms_output.put_line(l_line); --do line processing
        l_offset:=l_offset+l_line_length+1;
      end loop;
    end parse_clob;
    
    0 讨论(0)
提交回复
热议问题