oracle 以绑定变量作为参数的PL SQL表函数

au9on6nz  于 2023-04-29  发布在  Oracle
关注(0)|答案(2)|浏览(143)

字符串文字的限制为4000字节。我决定建立一个表函数,将由逗号,冒号,分号或管道分开,并输出它作为一个列中的值。通常我会用regexp_replace来做这件事,但是没有clob数据类型的函数,对吗?有更好的方法吗?
如果传递一些值,函数将编译并工作。
我以为我能做这样的事

variable patrik clob;
exec :patrik := 'abc,ghj,yut';

select * from patrik_split_clob(:patrik)

SQL Developer再次打开一个弹出窗口输入值,我认为它可以使用刚刚分配的值的绑定变量并运行。
我也可以用这种方式,只是好奇。
存在另一个问题,并出现以下错误

ORA-06502: PL/SQL: numeric or value error: character string buffer too small
ORA-06512: at "PATRIK_SPLIT_CLOB", line 28
06502. 00000 -  "PL/SQL: numeric or value error%s"
*Cause:    An arithmetic, numeric, string, conversion, or constraint error
           occurred. For example, this error occurs if an attempt is made to
           assign the value NULL to a variable declared NOT NULL, or if an
           attempt is made to assign an integer larger than 99 to a variable
           declared NUMBER(2).
*Action:   Change the data, how it is manipulated, or how it is declared so
           that values do not violate constraints.

CREATE OR REPLACE TYPE patrik_split_value AS OBJECT (value VARCHAR2(4000));
CREATE OR REPLACE TYPE patrik_split_table AS TABLE OF patrik_split_value;

CREATE OR REPLACE FUNCTION patrik_split_clob(p_clob CLOB)
RETURN patrik_split_table
AS
    v_result patrik_split_table := patrik_split_table();
    v_start_pos NUMBER := 1;
    v_end_pos NUMBER;
    v_length NUMBER := DBMS_LOB.GETLENGTH(p_clob);
BEGIN
    LOOP
        v_end_pos := DBMS_LOB.INSTR(p_clob, ',', v_start_pos);
        IF v_end_pos = 0 THEN
            v_end_pos := DBMS_LOB.INSTR(p_clob, ':', v_start_pos);
        END IF;
        IF v_end_pos = 0 THEN
            v_end_pos := DBMS_LOB.INSTR(p_clob, ';', v_start_pos);
        END IF;
        IF v_end_pos = 0 THEN
            v_end_pos := DBMS_LOB.INSTR(p_clob, '|', v_start_pos);
        END IF;
        IF v_end_pos = 0 THEN
            EXIT;
        END IF;
        v_result.EXTEND;
        v_result(v_result.COUNT) := patrik_split_value(DBMS_LOB.SUBSTR(p_clob, v_end_pos - v_start_pos, v_start_pos));
        v_start_pos := v_end_pos + 1;
    END LOOP;
    v_result.EXTEND;
    v_result(v_result.COUNT) := patrik_split_value(DBMS_LOB.SUBSTR(p_clob, v_length - v_start_pos + 1, v_start_pos));
    RETURN v_result;
END;

我做错了什么?$NUMBER应该处理99个以上,是因为v_start_pos:= 1吗?

ki1q1bka

ki1q1bka1#

我做错了什么?
从字符串中给定的开始位置,您将找到

  • 第一个逗号
  • 然后,如果在整个剩余的字符串中没有逗号,则查找下一个冒号;
  • 然后,如果在整个剩余的字符串中没有逗号或冒号,则会找到下一个分号;
  • 然后,如果在整个剩余字符串中没有逗号、冒号或分号,则查找下一个管道字符;
  • 然后你从你找到的字符开始重新查找。

如果你有字符串a|b;c:d,e,那么你匹配的第一个分隔符将是逗号,第一个术语将是a|b;c:d,第二个术语是e;你不会在多个分隔符上分割字符串,除非它们都是按照,然后:然后;然后|的顺序。
此外:

  • 不需要对象表类型;您可以使用TABLE OF VARCHAR2(4000),或者如果每个术语可能大于4000字节,则可以使用TABLE OF CLOB
  • 您使用的是一个非流水线函数,它需要解析整个字符串并在内存中生成整个集合;相反,您可以使用管道函数,它将在找到行时对其进行管道传输,并且可能不需要在内存中保存整个集合。

这是我为earlier question创建的一个函数,它由一个分隔符分隔:

CREATE TYPE clob_table AS TABLE OF CLOB;

CREATE FUNCTION split_clob(
  p_value     IN CLOB,
  p_delimiter IN VARCHAR2 DEFAULT ','
) RETURN clob_table PIPELINED
IS
  v_start  PLS_INTEGER;
  v_next   PLS_INTEGER;
  v_len    PLS_INTEGER;
BEGIN
  v_start := 1;
  LOOP
    v_next := DBMS_LOB.INSTR( p_value, p_delimiter, v_start );
    v_len  := CASE v_next WHEN 0 THEN LENGTH( p_value ) + 1 ELSE v_next END - v_start;
    PIPE ROW ( SUBSTR( p_value, v_start, v_len ) );
    EXIT WHEN v_next = 0;
    v_start := v_next + LENGTH(p_delimiter);
  END LOOP;
END;
/

如果您想将其更改为多个分隔符(并确保每个术语将为4000个字符或更少),则:

CREATE TYPE varchar2_4000_table AS TABLE OF VARCHAR2(4000);

CREATE FUNCTION split_clob(
  p_value     IN CLOB
) RETURN varchar2_4000_table PIPELINED
IS
  v_start  PLS_INTEGER;
  v_next   PLS_INTEGER;
  v_delim  PLS_INTEGER;
  v_len    PLS_INTEGER;
BEGIN
  v_start := 1;
  LOOP
    v_next  := 0;
    v_delim := DBMS_LOB.INSTR( p_value, ',', v_start );
    IF v_delim > 0 AND (v_next = 0 OR v_delim < v_next) THEN
      v_next := v_delim;
    END IF;
    v_delim := DBMS_LOB.INSTR( p_value, ':', v_start );
    IF v_delim > 0 AND (v_next = 0 OR v_delim < v_next) THEN
      v_next := v_delim;
    END IF;
    v_delim := DBMS_LOB.INSTR( p_value, ';', v_start );
    IF v_delim > 0 AND (v_next = 0 OR v_delim < v_next) THEN
      v_next := v_delim;
    END IF;
    v_delim := DBMS_LOB.INSTR( p_value, '|', v_start );
    IF v_delim > 0 AND (v_next = 0 OR v_delim < v_next) THEN
      v_next := v_delim;
    END IF;

    v_len := CASE v_next WHEN 0 THEN LENGTH( p_value ) + 1 ELSE v_next END - v_start;
    PIPE ROW ( SUBSTR( p_value, v_start, v_len ) );
    EXIT WHEN v_next = 0;
    v_start := v_next + 1;
  END LOOP;
END;
/

然后:

SELECT *
FROM   split_clob('abc,ghj,yut')

输出:
| 列值|
| --------------|
| ABC|
| ghj|
| 尤特|
以及:

SELECT *
FROM   split_clob('a|b;c:d,e')

输出:
| 列值|
| --------------|
| 一种|
| B|
| c|
| d|
| e|

  • 注意:在早期的Oracle版本中,您需要使用SELECT * FROM TABLE(split_clob(:varaible)),但在更高版本中,表集合表达式语法是可选的,可以省略(正如您所做的那样)。*

fiddle

6ie5vjzr

6ie5vjzr2#

这或多或少是好的,你只是叫它在一个错误的方式。

SQL> variable patrik clob;
SQL> exec :patrik := 'abc,ghj,yut';

PL/SQL procedure successfully completed.

SQL> select * from table(patrik_split_clob(:patrik));

VALUE
--------------------------------------------------------------------------------
abc
ghj
yut

SQL>

相关问题