Oracle Query over Pipeline Function - 数据包含 ASCII 扩展字符时出现奇怪错误

Oracle Query over Pipeline Function - Strange Error when Data contains ASCII Extended Characters

我有一个 Oracle 管道函数的问题,我非常想知道发生了什么。我的 Oracle 数据库是基于 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;
/

管道函数

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

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;
/

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

有问题Table

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 或其他类似 table/views 的函数,一切正常。

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 加入这些文件。但是,如果我尝试 运行 它反对上面显示的 table ,就会发生这种情况(为了示例的目的,我只是使用 table 的一些列)

工作

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

我不太明白那些Extended ASCII字符和函数之间存在什么关系。我应该在我的函数中更改什么以使其适用于此类字符?

谢谢大家的帮助。

当你这样做时:

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

您正在计算缓冲区和集合变量中的字符数。当您只有单字节字符时没问题,但对于任何多字节字符,您可能会遇到 个字符 的总数少于 32767,但 个字符的数量bytes 超过了那个。检查通过;但后来你做了:

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

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

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

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