如何使用光标创建流水线函数并返回表?

Posted

技术标签:

【中文标题】如何使用光标创建流水线函数并返回表?【英文标题】:How can I create pipelined function with cursor, and return table? 【发布时间】:2019-07-16 08:16:00 【问题描述】:

我有一个类似的表(这里有 9 列,带有 ';'。这是示例表):

create table mytable as (
select
  1 ID,
  'T1;T2;T3' column_1,
  'B1;B5;B10;B13' column_2
from dual
union all
select
  2 ID,
  'T7;T8;T9;T10,T11',
  'B2;B3;B5'
from dual
)

我需要像这样的目标表:

ID  column_1    column_2
1      T1        B1
1      T1        B5
1      T1        B10
1      T1        B13
1      T2        B1
1      T2        B5
1      T2        B10
1      T2        B13
1      T3        B1
1      T3        B5
1      T3        B10
1      T3        B13
2      T7        B2
2      T7        B3
2      T7        B5
2      T8        B2
2      T8        B3
2      T8        B5
2      T9        B2
2      T9        B3
2      T9        B5
2      T10       B2
2      T10       B3
2      T10       B5
2      T11       B2
2      T11       B3
2      T11       B5

我找到了以下链接: pipelined function with cursor parameter oracle 但我无法定期创建函数。我只为一列创建函数但不能循环,也不能调用表。这是我的功能:

create or replace function fun_pipelined(i_str in varchar2)
  RETURN sys.odcivarchar2list PIPELINED
IS
  v_arr     dbms_sql.varchar2_table;
  v_i       long;
  v_cnt     number;
  i         number;
begin
  v_arr := pl.split(nvl(i_str,' ,'),',');
  v_cnt := regexp_count(nvl(i_str,','), ',') + 1;
  i := 1;
  loop
    exit when i > v_cnt;
    v_i := trim(v_arr(i));
    pipe row (v_i);
    i := i+1;
  end loop;
end;

你能给我一些建议吗?谢谢

【问题讨论】:

您使用的是哪个版本的 Oracle? 我使用的是 Oracle 11g 将数据存储为分隔符分隔值是一种不好的做法,应该避免。规范化您的数据模型并分别存储值。阅读this了解更多 请将您需要的输出作为文本发布。许多人不能或不会尝试访问图像转储站点中的屏幕截图。 谢谢 APC。我将输出更改为文本。 【参考方案1】:

您可以尝试以下查询:

WITH DATAA AS (
    SELECT DISTINCT
        ID,
        REGEXP_SUBSTR(COLUMN_1, '[^;]+', 1, LEVEL) COLUMN_1,
        REGEXP_SUBSTR(COLUMN_2, '[^;]+', 1, LEVEL) COLUMN_2
    FROM
        MYTABLE
    CONNECT BY REGEXP_SUBSTR(COLUMN_1, '[^;]+', 1, LEVEL) IS NOT NULL
               OR REGEXP_SUBSTR(COLUMN_2, '[^;]+', 1, LEVEL) IS NOT NULL
)
SELECT
    ID,
    COLUMN_1,
    COLUMN_2
FROM
    (
        SELECT DISTINCT
            D1.ID,
            D1.COLUMN_1,
            D2.COLUMN_2
        FROM
            DATAA D1
            JOIN DATAA D2 ON ( D1.ID = D2.ID )
    )
WHERE
    ( COLUMN_1 IS NOT NULL
      AND COLUMN_2 IS NOT NULL )
ORDER BY
    ID,
    COLUMN_1;

db<>fiddle demo

干杯

【讨论】:

谢谢,但我的原始表中有 9 列。因此我尝试用光标创建一个函数。抱歉忘记写了。我正在编辑我的帖子 通过使用 PL/SQL 函数,您会降低查询的性能。如果您有 9 个这样的列,那么您也可以在上述查询中写入所有列。是的,这不是像这样存储数据的好习惯。您必须采取措施改进数据库的设计。 很遗憾,我无法更改此表,因为业务部门管理该表。我已经尝试创建上述函数很长时间了:) 因此我想了解如何创建这个 再次,如果您将创建函数,您将增加上下文切换,您的查询将无法正常执行.. 性能将下降。关于数据库设计,如果它不在你的盘子里也没关系。 :)【参考方案2】:

比起 Oracle 的 connect by,我更喜欢公用表表达式。这是您使用 CTE 得到的结果。

WITH
    mytable AS
        (SELECT 1 id, 'T1;T2;T3' column_1, 'B1;B5;B10;B13' column_2 FROM DUAL
         UNION ALL
         SELECT 2 id, 'T7;T8;T9;T10;T11', 'B2;B3;B5' FROM DUAL),
    mytable2 AS( SELECT id, column_1 || ';' AS column_1, column_2 || ';' AS column_2 FROM mytable ),
    splitset1 ( id
              , column_1
              , column_2
              , REMAINDER ) AS
        (SELECT id
              , SUBSTR( column_1
                      , 1
                      , INSTR( column_1, ';' ) - 1 )              AS column1
              , column_2
              , SUBSTR( column_1, INSTR( column_1, ';' ) + 1 )    AS REMAINDER
           FROM mytable2
         UNION ALL
         SELECT id
              , SUBSTR( REMAINDER
                      , 1
                      , INSTR( REMAINDER, ';' ) - 1 )
              , column_2
              , SUBSTR( REMAINDER, INSTR( REMAINDER, ';' ) + 1 )
           FROM splitset1
          WHERE REMAINDER IS NOT NULL),
    splitset2 ( id
              , column_1
              , column_2
              , REMAINDER ) AS
        (SELECT id
              , column_1
              , SUBSTR( column_2
                      , 1
                      , INSTR( column_2, ';' ) - 1 )              AS column2
              , SUBSTR( column_2, INSTR( column_2, ';' ) + 1 )    AS REMAINDER
           FROM splitset1
         UNION ALL
         SELECT id
              , column_1
              , SUBSTR( REMAINDER
                      , 1
                      , INSTR( REMAINDER, ';' ) - 1 )
              , SUBSTR( REMAINDER, INSTR( REMAINDER, ';' ) + 1 )
           FROM splitset2
          WHERE REMAINDER IS NOT NULL)
  SELECT id
       , column_1
       , column_2
    FROM splitset2
ORDER BY id
       , CAST( SUBSTR( column_1, 2 ) AS NUMBER )
       , CAST( SUBSTR( column_2, 2 ) AS NUMBER )

如果您拥有 Oracle 12,则可以使用 SQL 函数使您的 SQL 非常易读,但会产生一些开销:

WITH
    FUNCTION after( p_value IN VARCHAR2, p_separator IN VARCHAR2 DEFAULT ';' )
        RETURN VARCHAR2 AS
        l_pos   INTEGER;
    BEGIN
        l_pos   := INSTR( p_value, p_separator );
        RETURN CASE WHEN l_pos > 0 THEN SUBSTR( p_value, l_pos + 1 ) ELSE NULL END;
    END after;

    FUNCTION before( p_value IN VARCHAR2, p_separator IN VARCHAR2 DEFAULT ';' )
        RETURN VARCHAR2 AS
        l_pos   INTEGER;
    BEGIN
        l_pos   := INSTR( p_value, p_separator );
        RETURN CASE
                   WHEN l_pos > 0
                   THEN
                       SUBSTR( p_value
                             , 1
                             , l_pos - 1 )
                   ELSE
                       p_value
               END;
    END before;

    mytable AS
        (SELECT 1 id, 'T1;T2;T3' column_1, 'B1;B5;B10;B13' column_2 FROM DUAL
         UNION ALL
         SELECT 2 id, 'T7;T8;T9;T10;T11', 'B2;B3;B5' FROM DUAL),
    mytable2 AS( SELECT id, column_1 || ';' AS column_1, column_2 || ';' AS column_2 FROM mytable ),
    splitset1 ( id
              , column_1
              , column_2
              , REMAINDER ) AS
        (SELECT id
              , before( column_1 )     AS column1
              , column_2
              , after( column_1 )      AS REMAINDER
           FROM mytable2
         UNION ALL
         SELECT id
              , before( REMAINDER )
              , column_2
              , after( REMAINDER )
           FROM splitset1
          WHERE REMAINDER IS NOT NULL),
    splitset2 ( id
              , column_1
              , column_2
              , REMAINDER ) AS
        (SELECT id
              , column_1
              , before( column_2 )     AS column2
              , after( column_2 )      AS REMAINDER
           FROM splitset1
         UNION ALL
         SELECT id
              , column_1
              , before( REMAINDER )
              , after( REMAINDER )
           FROM splitset2
          WHERE REMAINDER IS NOT NULL)
  SELECT id
       , column_1
       , column_2
    FROM splitset2
ORDER BY id
       , CAST( SUBSTR( column_1, 2 ) AS NUMBER )
       , CAST( SUBSTR( column_2, 2 ) AS NUMBER )

【讨论】:

以上是关于如何使用光标创建流水线函数并返回表?的主要内容,如果未能解决你的问题,请参考以下文章

如何存储将返回ref光标的函数的结果?

如何在“流水线表函数”、视图和显式游标之间进行选择

在函数中,如何在不使用 ref 游标的情况下返回多个值?

Oracle 11g - 如何使用表连接从函数返回记录

创建函数 - 流水线 DB2

Oracle函数:如何将表名作为参数传递,并使用游标结果作为表名?