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
我有一个 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