0

我遇到了 Oracle 管道功能的问题,我很想知道发生了什么。My Oracle Database 是在 Red Hat 7.2 上运行的 19c 版,并配置AL32UTF8为 CharacterSet。

让我解释一下这个场景。

为了使用并行进程生成文件,我有以下两种类型和一种管道功能的设置,因此我可以难以置信地加快大文件的生成。

两种类型

--
-- DUMP_PARALLEL_OBJECT  (Type) 
--
CREATE OR REPLACE TYPE CPL_DATA_OUT.dump_parallel_object AS OBJECT
(file_name VARCHAR2 (128), no_records NUMBER, seq_id NUMBER);
/

--
-- DUMP_PARALLEL_OBJECT_NTT  (Type) 
--
CREATE OR REPLACE TYPE CPL_DATA_OUT.dump_parallel_object_ntt AS TABLE OF cpl_data_out.dump_parallel_object;
/

流水线函数

这是管道功能,用于获取我可以加入然后cat在 Linux 中使用的块中的输出文件。

CREATE OR REPLACE function CPL_DATA_OUT.fn_generate_parallel_file
(
p_source    IN SYS_REFCURSOR,
p_filename  IN VARCHAR2,
p_directory IN VARCHAR2,
p_extension IN VARCHAR2 DEFAULT 'csv',
p_limit     IN NUMBER DEFAULT 10000
) return dump_parallel_object_ntt
pipelined
parallel_enable (partition p_source by any)
as
   type row_ntt is table of varchar2(32767);
   v_rows    row_ntt;
   v_file    UTL_FILE.FILE_TYPE;
   v_buffer  VARCHAR2(32767);
   v_sid     NUMBER;
   v_name    VARCHAR2(128);
   v_lines   PLS_INTEGER := 0;
   c_eol     CONSTANT VARCHAR2(1) := CHR(10);
   c_eollen  CONSTANT PLS_INTEGER := LENGTH(c_eol);
   c_maxline CONSTANT PLS_INTEGER := 32767;
begin

   SELECT generate_random_number.nextval INTO v_sid FROM dual;
   v_name := p_filename || '_' || TO_CHAR(v_sid) || '.' || p_extension;
   v_file := UTL_FILE.FOPEN(p_directory, v_name, 'w', 32767);

   LOOP
     FETCH p_source BULK COLLECT INTO v_rows LIMIT p_limit;

      FOR i IN 1 .. v_rows.COUNT LOOP

         IF LENGTH(v_buffer) + c_eollen + LENGTH(v_rows(i)) <= c_maxline THEN
            v_buffer := v_buffer || c_eol || v_rows(i);
         ELSE
            IF v_buffer IS NOT NULL THEN
               UTL_FILE.PUT_LINE(v_file, v_buffer);
            END IF;
            v_buffer := v_rows(i);
         END IF;

      END LOOP;

      v_lines := v_lines + v_rows.COUNT;

      EXIT WHEN p_source%NOTFOUND;
   END LOOP;
   CLOSE p_source;

   UTL_FILE.PUT_LINE(v_file, v_buffer);
   UTL_FILE.FCLOSE(v_file);

   PIPE ROW (dump_parallel_object(v_name, v_lines, v_sid));
   RETURN;

END fn_generate_parallel_file;
/

我在函数内部使用序列为这些文件分配唯一编号。让我们测试一下场景

有问题的表

SQL> desc SRD_OUT.FCT_EMPROLE_TRANSFORM
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 DAT_MONTH                                          DATE
 PERSNR                                             VARCHAR2(6 CHAR)
 ARBEITS_STATUS                                     VARCHAR2(50 CHAR)
 NAME                                               VARCHAR2(50 CHAR)
 VORNAME                                            VARCHAR2(50 CHAR)
 FTE                                                NUMBER
 WOCHENSTUNDEN                                      NUMBER
 FUNKTION                                           VARCHAR2(50 CHAR)
 OE                                                 VARCHAR2(70 CHAR)
 DIREKTION                                          VARCHAR2(50 CHAR)
 BEREICH                                            VARCHAR2(50 CHAR)
 N_NUMMER                                           VARCHAR2(50 CHAR)
 FTE_VALUE                                          NUMBER
 CENTERKEY                                          VARCHAR2(200 CHAR)
 ROLLE                                              VARCHAR2(200 CHAR)
 BEMESSUNGSFAKTOR                                   VARCHAR2(50 CHAR)
 COD_PROCESS                                        VARCHAR2(30 CHAR)
 DAT_EFFECTIVE                                      DATE

SQL> select count(*) from SRD_OUT.FCT_EMPROLE_TRANSFORM ;

  COUNT(*)
----------
     20436

如果我针对该dba_objects表或其他类似的表/视图运行该函数,则一切正常。

SQL> COL FILE_NAME FOR A50
SQL> set lines 220
SQL> r
  1  SELECT *
  2  FROM TABLE(
  3  cpl_data_out.fn_generate_parallel_file(
  4  CURSOR(
  5  SELECT /*+ PARALLEL(s,10) */
  6  "OWNER"          ||'~'||
  7  "OBJECT_NAME"    ||'~'||
  8  "SUBOBJECT_NAME" ||'~'||
  9  "OBJECT_ID"      ||'~'||
 10  "DATA_OBJECT_ID" ||'~'||
 11  "OBJECT_TYPE"    ||'~'||
 12  "CREATED"        ||'~'||
 13  "LAST_DDL_TIME" as csv
 14  FROM DBA_OBJECTS s)
 15  , 'test_file'
 16  , 'DIR_SRD_OUT'
 17  , 'csv')
 18  ) nt
 19*

FILE_NAME                                          NO_RECORDS     SEQ_ID
-------------------------------------------------- ---------- ----------
test_file_459.csv                                       25496        459
test_file_449.csv                                       25496        449
test_file_453.csv                                       25496        453
test_file_461.csv                                       25496        461
test_file_455.csv                                       25499        455
test_file_451.csv                                       25496        451
test_file_447.csv                                       25496        447
test_file_443.csv                                       25496        443
test_file_457.csv                                       25496        457
test_file_445.csv                                       25497        445

10 rows selected.

如您所见,流水线函数按预期工作,它创建了 10 个 csv 文件,我以后可以使用cat. 但是,如果我尝试针对上面显示的表运行它,就会发生这种情况(出于示例的目的,我只是使用了表的某些列)

在职的

SQL> SELECT *
  2  FROM TABLE(
  3  cpl_data_out.fn_generate_parallel_file(
  4  CURSOR(
  5  SELECT /*+ PARALLEL(s,10) */
  6  "DAT_MONTH"      ||'~'||
  7  "PERSNR"         ||'~'||
  8  "COD_PROCESS"    ||'~'||
  9  "DAT_EFFECTIVE"
 10  as csv
 11  FROM SRD_OUT.FCT_EMPROLE_TRANSFORM s)
 12  , 'test_file'
 13  , 'DIR_SRD_OUT'
 14  , 'csv')
 15* ) nt
SQL> /

FILE_NAME                                          NO_RECORDS     SEQ_ID
-------------------------------------------------- ---------- ----------
test_file_569.csv                                         456        569
test_file_571.csv                                         489        571
test_file_575.csv                                         314        575
test_file_573.csv                                         483        573
test_file_577.csv                                         496        577
test_file_581.csv                                         487        581
test_file_579.csv                                         430        579
test_file_567.csv                                        3500        567
test_file_565.csv                                        3606        565
test_file_563.csv                                       10175        563

10 rows selected.

不工作

SQL> SELECT *
  2  FROM TABLE(
  3  cpl_data_out.fn_generate_parallel_file(
  4  CURSOR(
  5  SELECT /*+ PARALLEL(s,10) */
  6  "DAT_MONTH"      ||'~'||
  7  "PERSNR"         ||'~'||
  8  "COD_PROCESS"    ||'~'||
  9  "DAT_EFFECTIVE"  ||'~'||
 10  "ROLLE"
 11  as csv
 12  FROM SRD_OUT.FCT_EMPROLE_TRANSFORM s)
 13  , 'test_file'
 14  , 'DIR_SRD_OUT'
 15  , 'csv')
 16* ) nt
SQL> /
ERROR:
ORA-12801: error signaled in parallel query server P005
ORA-06502: PL/SQL: numeric or value error: character string buffer too small
ORA-06512: at "CPL_DATA_OUT.FN_GENERATE_PARALLEL_FILE", line 34
ORA-06512: at line 1

两个查询之间的唯一区别是“ROLLE”列包含 ASCII 扩展字符(如德语中的字母,例如“äüöß”)。包含此类字符的每一列都会发生这种情况。

实际上错误是指这一行:v_buffer := v_buffer || c_eol || v_rows(i);,但我不知道当涉及这些字符时有什么问题。

SQL> set pages 200
SQL> r
  1* select distinct rolle from SRD_OUT.FCT_EMPROLE_TRANSFORM

ROLLE
------------------------
Filialleiter (große Filiale)
Vertriebsdirektor Vermögensberatung

我不太明白那些扩展 ASCII 字符和函数之间存在什么关系。我应该对我的功能进行哪些更改以使其与此类字符一起使用?

谢谢大家的帮助。

4

1 回答 1

5

当你这样做时:

IF LENGTH(v_buffer) + c_eollen + LENGTH(v_rows(i)) <= c_maxline

您正在计算缓冲区和集合变量中的字符数。当您只有单字节字符时可以,但对于任何多字节字符,您可能会遇到字符总数小于 32767,但字节数超过该值的情况。支票通过;但是你这样做:

v_buffer := v_buffer || c_eol || v_rows(i)

超出缓冲区的大小,并引发错误。如果您的缓冲区被声明为小于最大值并使用字符语义,您可能仍然可以逃脱;但是使用最大尺寸(和任何语义)它将失败。

如果您计算字节而不是字符,则不会超过字节限制:

IF LENGTHB(v_buffer) + c_eollen + LENGTHB(v_rows(i)) <= c_maxline
于 2022-01-14T17:46:25.160 回答