sql - 用于动态 sql 透视的 Oracle 函数

标签 sql oracle plsql oracle10g

我在这个 article 中发现了这个神奇的功能.该函数将其中一列中的一组值转换为列标题:

CREATE OR REPLACE
type PivotImpl as object
(
  ret_type anytype,      -- The return type of the table function
  stmt varchar2(32767),

  fmt  varchar2(32767),
  cur integer,
  static function ODCITableDescribe( rtype out anytype, p_stmt in varchar2, p_fmt in varchar2 := 'upper(@p@)', dummy in number := 0 )
  return number,
  static function ODCITablePrepare( sctx out PivotImpl, ti in sys.ODCITabFuncInfo, p_stmt in varchar2, p_fmt in varchar2 := 'upper(@p@)', dummy in number := 0 )
  return number,
  static function ODCITableStart( sctx in out PivotImpl, p_stmt in varchar2, p_fmt in varchar2 := 'upper(@p@)', dummy in number := 0 )
  return number,
  member function ODCITableFetch( self in out PivotImpl, nrows in number, outset out anydataset )
  return number,
  member function ODCITableClose( self in PivotImpl )
  return number
)
/

create or replace type body PivotImpl as
  static function ODCITableDescribe( rtype out anytype, p_stmt in varchar2, p_fmt in varchar2 := 'upper(@p@)', dummy in number )
  return number
  is
    atyp anytype;
    cur integer;
    numcols number;
    desc_tab dbms_sql.desc_tab2;
    rc s

ys_refcursor;
    t_c2 varchar2(32767);
    t_fmt varchar2(1000);
  begin
    cur := dbms_sql.open_cursor;
    dbms_sql.parse( cur, p_stmt, dbms_sql.native );
    dbms_sql.describe_columns2( cur, numcols, desc_tab );
    dbms_sql.close_cursor( cur );
--
    anytype.begincreate( dbms_types.typecode_object, atyp );
    for i in 1 .. numcols - 2
    loop
      atyp.addattr( desc_tab( i ).col_name
                  , case desc_tab( i ).col_type
                      when 1   then dbms_types.typecode_varchar2
                      when 2   then dbms_types.typecode_number
                      when 9   then dbms_types.typecode_varchar2
                      when 11  then dbms_types.typecode_varchar2  -- show rowid as varchar2
                      when 12  then dbms_types.typecode_date
                      when 208 then dbms_types.typecode_varchar2  -- show urowid as varchar2
                      when 96  then dbms_types.typecode_char
                      when 180 then dbms_types.typecode_timestamp
                      when 181 then dbms_types.typecode_timestamp_tz
                      when 231 then dbms_types.typecode_timestamp_ltz
                      when 182 then dbms_types.typecode_interval_ym
                      when 183 then dbms_types.typecode_interval_ds
                    end
                  , desc_tab( i ).col_precision
                  , desc_tab( i ).col_scale
                  , case desc_tab( i ).col_type
                      when 11 then 18  -- for rowid col_max_len = 16, and 18 characters are shown
                      else desc_tab( i ).col_max_len
                    end
                  , desc_tab( i ).col_charsetid
                  , desc_tab( i ).col_charsetform
                  );
    end loop;
    if instr( p_fmt, '@p@' ) > 0
    then
      t_fmt := p_fmt;
    else
      t_fmt := '@p@';
    end if;
    open rc for replace( 'select distinct ' || t_fmt || '
                          from( ' || p_stmt || ' )
                          order by ' || t_fmt
                       , '@p@'
                       , desc_tab( numcols - 1 ).col_name
                       );
    loop
      fetch rc into t_c2;
      exit when rc%notfound;
      atyp.addattr( t_c2
                  , case desc_tab( numcols ).col_type
                    when 1   then dbms_types.typecode_varchar2
                    when 2   then dbms_types.typecode_number
                    when 9   then dbms_types.typecode_varchar2
                    when 11  then dbms_types.typecode_varchar2  -- show rowid as varchar2
                    when 12  then dbms_types.typecode_date
                    when 208 then dbms_types.typecode_urowid
                    when 96  then dbms_types.typecode_char
                    when 180 then dbms_types.typecode_timestamp
                    when 181 then dbms_types.typecode_timestamp_tz
                    when 231 then dbms_types.typecode_timestamp_ltz
                    when 182 then dbms_types.typecode_interval_ym
                    when 183 then dbms_types.typecode_interval_ds
                  end
                , desc_tab( numcols ).col_precision
                , desc_tab( numcols ).col_scale
                , case desc_tab( numcols ).col_type
                    when 11 then 18  -- for rowid col_max_len = 16, and 18 characters are shown
                    else desc_tab( numcols ).col_max_len
                  end
                , desc_tab( numcols ).col_charsetid
                , desc_tab( numcols ).col_charsetform
                  );
    end loop;
    close rc;
    atyp.endcreate;
    anytype.begincreate( dbms_types.typecode_table, rtype );
    rtype.SetInfo( null, null, null, null, null, atyp, dbms_types.typecode_object, 0 );
    rtype.endcreate();
    return odciconst.success;
  exception
    when others then
      return odciconst.error;
  end;
--
  static function ODCITablePrepare( sctx out PivotImpl, ti in sys.ODCITabFuncInfo, p_stmt in varchar2, p_fmt in varchar2 := 'upper(@p@)', dummy in number )
  return number
  is
    prec     pls_integer;
    scale    pls_integer;
    len      pls_integer;
    csid     pls_integer;
    csfrm    pls_integer;
    elem_typ anytype;
    aname    varchar2(30);
    tc       pls_integer;
  begin
    tc := ti.RetType.GetAttrElemInfo( 1, prec, scale, len, csid, csfrm, elem_typ, aname );
--
    if instr( p_fmt, '@p@' ) > 0
    then
      sctx := PivotImpl( elem_typ, p_stmt, p_fmt, null );
    else
      sctx := PivotImpl( elem_typ, p_stmt, '@p@', null );
    end if;
    return odciconst.success;
  end;
--
  static function ODCITableStart( sctx in out PivotImpl, p_stmt in varchar2, p_fmt in varchar2 := 'upper(@p@)', dummy in number )
  return number
  is
    cur         integer;
    numcols     number;
    desc_tab    dbms_sql.desc_tab2;
    t_stmt      varchar2(32767);
    type_code   pls_integer;
    prec        pls_integer;
    scale       pls_integer;
    len         pls_integer;
    csid        pls_integer;
    csfrm       pls_integer;
    schema_name varchar2(30);
    type_name   varchar2(30);
    version     varchar2(30);
    attr_count  pls_integer;
    attr_type   anytype;
    attr_name   varchar2(100);
    dummy2      integer;
  begin
    cur := dbms_sql.open_cursor;
    dbms_sql.parse( cur, p_stmt, dbms_sql.native );
    dbms_sql.describe_columns2( cur, numcols, desc_tab );
    dbms_sql.close_cursor( cur );
--
    for i in 1 .. numcols - 2
    loop
      t_stmt := t_stmt || ', "' || desc_tab( i ).col_name || '"';
    end loop;
--
    type_code := sctx.ret_type.getinfo( prec
                                      , scale
                                      , len
                                      , csid
                                      , csfrm
                                      , schema_name
                                      , type_name
                                      , version
                                      , attr_count
                                      );
    for i in numcols - 1 .. attr_count
    loop
      type_code := sctx.ret_type.getattreleminfo( i
                                                 , prec
                                                 , scale
                                                 , len
                                                 , csid
                                                 , csfrm
                                                 , attr_type
                                                 , attr_name
                                                 );
      t_stmt := t_stmt || replace( ', max( decode( ' || sctx.fmt || ', ''' || attr_name || ''', ' || desc_tab( numcols ).col_name || ' ) )'
                                 , '@p@'
                                 , desc_tab( numcols - 1 ).col_name
                                 );
    end loop;
    t_stmt := 'select ' || substr( t_stmt, 2 ) || ' from ( ' || sctx.stmt || ' )';
    for i in 1 .. numcols - 2
    loop
      if i = 1
      then
        t_stmt := t_stmt || ' group by "' || desc_tab( i ).col_name || '"';
      else
        t_stmt := t_stmt || ', "' || desc_tab( i ).col_name || '"';
      end if;
    end loop;
--
dbms_output.put_line( t_stmt );
    sctx.cur := dbms_sql.open_cursor;
    dbms_sql.parse( sctx.cur, t_stmt, dbms_sql.native );
    for i in 1 .. attr_count
    loop
      type_code := sctx.ret_type.getattreleminfo( i
                                                 , prec
                                                 , scale
                                                 , len
                                                 , csid
                                                 , csfrm
                                                 , attr_type
                                                 , attr_name
                                                 );
      case type_code
        when dbms_types.typecode_char          then dbms_sql.define_column( sctx.cur, i, 'x', 32767 );
        when dbms_types.typecode_varchar2      then dbms_sql.define_column( sctx.cur, i, 'x', 32767 );
        when dbms_types.typecode_number        then dbms_sql.define_column( sctx.cur, i, cast( null as number ) );
        when dbms_types.typecode_date          then dbms_sql.define_column( sctx.cur, i, cast( null as date ) );
        when dbms_types.typecode_urowid        then dbms_sql.define_column( sctx.cur, i, cast( null as urowid ) );
        when dbms_types.typecode_timestamp     then dbms_sql.define_column( sctx.cur, i, cast( null as timestamp ) );
        when dbms_types.typecode_timestamp_tz  then dbms_sql.define_column( sctx.cur, i, cast( null as timestamp with time zone ) );
        when dbms_types.typecode_timestamp_ltz then dbms_sql.define_column( sctx.cur, i, cast( null as timestamp with local time zone ) );
        when dbms_types.typecode_interval_ym   then dbms_sql.define_column( sctx.cur, i, cast( null as interval year to month ) );
        when dbms_types.typecode_interval_ds   then dbms_sql.define_column( sctx.cur, i, cast( null as interval day to second ) );
      end case;
    end loop;
    dummy2 := dbms_sql.execute( sctx.cur );
    return odciconst.success;
  end;
--
  member function ODCITableFetch( self in out PivotImpl, nrows in number, outset out anydataset )
  return number
  is
    c1_col_type pls_integer;
    type_code   pls_integer;
    prec        pls_integer;
    scale       pls_integer;
    len         pls_integer;
    csid        pls_integer;
    csfrm       pls_integer;
    schema_name varchar2(30);
    type_name   varchar2(30);
    version     varchar2(30);
    attr_count  pls_integer;
    attr_type   anytype;
    attr_name   varchar2(100);
    v1     varchar2(32767);
    n1     number;
    d1     date;
    ur1    urowid;
    ids1   interval day to second;
    iym1   interval year to month;
    ts1    timestamp;
    tstz1  timestamp with time zone;
    tsltz1 timestamp with local time zone;
  begin
    outset := null;
    if nrows < 1
    then
-- is this possible???
      return odciconst.success;
    end if;
--
dbms_output.put_line( 'fetch' );
    if dbms_sql.fetch_rows( self.cur ) = 0
    then
      return odciconst.success;
    end if;
--
dbms_output.put_line( 'done' );
    type_code := self.ret_type.getinfo( prec
                                      , scale
                                      , len
                                      , csid
                                      , csfrm
                                      , schema_name
                                      , type_name
                                      , version
                                      , attr_count
                                      );
    anydataset.begincreate( dbms_types.typecode_object, self.ret_type, outset );
    outset.addinstance;
    outset.piecewise();
    for i in 1 .. attr_count
    loop
      type_code := self.ret_type.getattreleminfo( i
                                                 , prec
                                                 , scale
                                                 , len
                                                 , csid
                                                 , csfrm
                                                 , attr_type
                                                 , attr_name
                                                 );
dbms_output.put_line( attr_name );
        case type_code
          when dbms_types.typecode_char then
            dbms_sql.column_value( self.cur, i, v1 );
            outset.setchar( v1 );
          when dbms_types.typecode_varchar2 then
            dbms_sql.column_value( self.cur, i, v1 );
            outset.setvarchar2( v1 );
          when dbms_types.typecode_number then
            dbms_sql.column_value( self.cur, i, n1 );
            outset.setnumber( n1 );
          when dbms_types.typecode_date then
            dbms_sql.column_value( self.cur, i, d1 );
            outset.setdate( d1 );
          when dbms_types.typecode_urowid then
            dbms_sql.column_value( self.cur, i, ur1 );
            outset.seturowid( ur1 );
          when dbms_types.typecode_interval_ds then
            dbms_sql.column_value( self.cur, i, ids1 );

    outset.setintervalds( ids1 );
          when dbms_types.typecode_interval_ym then
            dbms_sql.column_value( self.cur, i, iym1 );
            outset.setintervalym( iym1 );
          when dbms_types.typecode_timestamp then
            dbms_sql.column_value( self.cur, i, ts1 );
            outset.settimestamp( ts1 );
          when dbms_types.typecode_timestamp_tz then
            dbms_sql.column_value( self.cur, i, tstz1 );
            outset.settimestamptz( tstz1 );
          when dbms_types.typecode_timestamp_ltz then
            dbms_sql.column_value( self.cur, i, tsltz1 );
            outset.settimestampltz( tsltz1 );
        end case;
    end loop;
    outset.endcreate;
    return odciconst.success;
  end;
--
  member function ODCITableClose( self in PivotImpl )
  return number
  is
    c integer;
  begin
    c := self.cur;
    dbms_sql.close_cursor( c );
    return odciconst.success;
  end;
end;
/

create or replace
function pivot( p_stmt in varchar2, p_fmt in varchar2 := 'upper(@p@)', dummy in number := 0 )
return anydataset pipelined using PivotImpl;
/

不幸的是,该函数没有对列进行排序:我的选择查询的结果是:

ID              NAME            EDITS           DAYS
1               Example         Edt1            10
1               Example         Edt2            5
1               Example         Edt3            3

当我使用 pivot 函数时,我丢失了顺序:

Select * from table (PIVOT('My select query')); 

ID              NAME            Edt2            Edt1        Edt3
1               Example         5               10          3

你能帮我编辑这个函数来保持列的顺序和初始列的值一样吗?谢谢。

更新: 我的选择查询:

SELECT  ID_DEMAND
        , NAME 
        , EDIT 
        , LEAD (EDIT_START) OVER (PARTITION BY ID_DEMAND ORDER BY ORDER_EDIT) - EDIT_START AS TOTAL
            FROM (
                    SELECT 
                    ID_DEMAND
                    , NAME
                    , EDIT
                    , ORDER_EDIT
                    , MIN (DATE_ACTION) as EDIT_START
                          FROM 
                              (SELECT 
                              D.ID_DEMAND
                              , J.NAME
                              , L.DATE_ACTION
                              , L.EDIT
                              , L.ORDER_EDIT
                                  FROM (DEMAND D LEFT JOIN LOG L ON D.ID_DEMAND = L.ID_DEMAND) 
                                  LEFT JOIN JOB J ON D.ID_JOB = J.ID_JOB )T
            GROUP BY 
                  ID_DEMAND
                  , NAME
                  , EDIT
                  , ORDER_EDIT)
            JHERE ID_DEMAND = 1601
            ORDER BY ID_DEMAND, ORDER_EDIT

最佳答案

您正在使用的函数会忽略您使用 order by 子句设置的值的顺序,并将旋转列名称及其顺序基于结果集中的排序值 -基于 pivot 的默认第二个参数被保留为 upper(@p),稍后将转换为 upper(EDIT)。似乎没有获得所需结果的简单方法,因为该排序必须基于倒数第二个 EDIT 字段 - 它可以保留为小写或以其他方式操作,但不是一种可以满足您要求的方式。

您可以修改实现以要求和使用附加的排序列,有效地将 pivot 函数定义转换为:

create or replace
function pivot( p_stmt in varchar2,
  p_fmt in varchar2 := 'upper(@p@)',
  p_ord in varchar2 := '@o@', -- added
  dummy in number := 0 )
return anydataset pipelined using PivotImpl;
/

这有一个额外的排序占位符,我根据现有占位符的精神默认为 '@o@',并且可能还允许进行一些操作。这必须反射(reflect)在所有 PivotImpl 函数声明中,然后必须在提取列值并决定其顺序的游标中使用。我是这样做的:

open rc for replace( replace( 'select ' || t_fmt || '
                      from( ' || p_stmt || ' )
                      group by ' || t_fmt || '
                      order by min(' || t_ord || ')'
                   , '@p@'
                   , desc_tab( numcols - 1 ).col_name
                   )
                   , '@o@'
                   , desc_tab( numcols - 2 ).col_name
                   );

这意味着您的 ORDER_EDIT 值必须包含在选择列表中,并且必须按照我修改的方式位于 EDIT 之前的列位置列号处理;但也意味着您现有的 order by 子句是多余的。

通过一些非常简单的虚拟数据设置:

create table job (id_job number, name varchar2(10));
create table demand (id_demand number, id_job number);
create table log (id_demand number, edit varchar2(4),
  order_edit number, date_action date);

insert into job values (42, 'Name');
insert into demand values (1601, 42);
insert into log values (1601, 'EdtC', 1, sysdate);
insert into log values (1601, 'EdtA', 2, sysdate);
insert into log values (1601, 'EdtB', 3, sysdate);

您的内部查询得到:

 ID_DEMAND NAME       EDIT      TOTAL
---------- ---------- ---- ----------
      1601 Name       EdtC          0
      1601 Name       EdtA          0
      1601 Name       EdtB           

原始实现得到:

 ID_DEMAND NAME             EDTA       EDTB       EDTC
---------- ---------- ---------- ---------- ----------
      1601 Name                0                     0

这个修改后的实现调用了:

Select * from table (PIVOT('SELECT  ID_DEMAND
        , NAME 
        , ORDER_EDIT
        , EDIT 
        , LEAD (EDIT_START) OVER (PARTITION BY ID_DEMAND ORDER BY ORDER_EDIT) - EDIT_START AS TOTAL
            FROM (
                    SELECT 
                    ID_DEMAND
                    , NAME
                    , EDIT
                    , ORDER_EDIT
                    , MIN (DATE_ACTION) as EDIT_START
                          FROM 
                              (SELECT 
                              D.ID_DEMAND
                              , J.NAME
                              , L.DATE_ACTION
                              , L.EDIT
                              , L.ORDER_EDIT
                                  FROM (DEMAND D LEFT JOIN LOG L ON D.ID_DEMAND = L.ID_DEMAND) 
                                  LEFT JOIN JOB J ON D.ID_JOB = J.ID_JOB )T
            GROUP BY 
                  ID_DEMAND
                  , NAME
                  , EDIT
                  , ORDER_EDIT)
            WHERE ID_DEMAND = 1601'));

... ORDER_EDIT 包含在选择列表中,因此结果集现在如下所示:

 ID_DEMAND NAME       ORDER_EDIT EDIT      TOTAL
---------- ---------- ---------- ---- ----------
      1601 Name                1 EdtC          0
      1601 Name                2 EdtA          0
      1601 Name                3 EdtB           

修改后的函数调用得到:

 ID_DEMAND NAME             EDTC       EDTA       EDTB
---------- ---------- ---------- ---------- ----------
      1601 Name                0          0           

它具有按照您的标志指定的顺序旋转的列,这是我认为您想要实现的。


这是整个修改后的实现,对于 SQL Fiddle 来说太大了。我用 -- added-- changed 注释标记了我更改的位:

CREATE OR REPLACE
type PivotImpl as object
(
  ret_type anytype,      -- The return type of the table function
  stmt varchar2(32767),

  fmt  varchar2(32767),
  ord  varchar2(32767), -- added
  cur integer,
  static function ODCITableDescribe( rtype out anytype,
    p_stmt in varchar2,
    p_fmt in varchar2 := 'upper(@p@)',
    p_ord in varchar2 := '@o@', -- added
    dummy in number := 0 )
  return number,
  static function ODCITablePrepare( sctx out PivotImpl,
    ti in sys.ODCITabFuncInfo,
    p_stmt in varchar2,
    p_fmt in varchar2 := 'upper(@p@)',
    p_ord in varchar2 := '@o@', -- added
    dummy in number := 0 )
  return number,
  static function ODCITableStart( sctx in out PivotImpl,
    p_stmt in varchar2,
    p_fmt in varchar2 := 'upper(@p@)',
    p_ord in varchar2 := '@o@', -- added
    dummy in number := 0 )
  return number,
  member function ODCITableFetch( self in out PivotImpl,
    nrows in number,
    outset out anydataset )
  return number,
  member function ODCITableClose( self in PivotImpl )
  return number
)
/

create or replace type body PivotImpl as
  static function ODCITableDescribe( rtype out anytype,
    p_stmt in varchar2,
    p_fmt in varchar2 := 'upper(@p@)',
    p_ord in varchar2 := '@o@', -- added
    dummy in number )
  return number
  is
    atyp anytype;
    cur integer;
    numcols number;
    desc_tab dbms_sql.desc_tab2;
    rc sys_refcursor;
    t_c2 varchar2(32767);
    t_fmt varchar2(1000);
    t_ord varchar2(1000); -- added
  begin
    cur := dbms_sql.open_cursor;
    dbms_sql.parse( cur, p_stmt, dbms_sql.native );
    dbms_sql.describe_columns2( cur, numcols, desc_tab );
    dbms_sql.close_cursor( cur );
--
    anytype.begincreate( dbms_types.typecode_object, atyp );
    for i in 1 .. numcols - 3 -- changed
    loop
      atyp.addattr( desc_tab( i ).col_name
                  , case desc_tab( i ).col_type
                      when 1   then dbms_types.typecode_varchar2
                      when 2   then dbms_types.typecode_number
                      when 9   then dbms_types.typecode_varchar2
                      when 11  then dbms_types.typecode_varchar2  -- show rowid as varchar2
                      when 12  then dbms_types.typecode_date
                      when 208 then dbms_types.typecode_varchar2  -- show urowid as varchar2
                      when 96  then dbms_types.typecode_char
                      when 180 then dbms_types.typecode_timestamp
                      when 181 then dbms_types.typecode_timestamp_tz
                      when 231 then dbms_types.typecode_timestamp_ltz
                      when 182 then dbms_types.typecode_interval_ym
                      when 183 then dbms_types.typecode_interval_ds
                    end
                  , desc_tab( i ).col_precision
                  , desc_tab( i ).col_scale
                  , case desc_tab( i ).col_type
                      when 11 then 18  -- for rowid col_max_len = 16, and 18 characters are shown
                      else desc_tab( i ).col_max_len
                    end
                  , desc_tab( i ).col_charsetid
                  , desc_tab( i ).col_charsetform
                  );
    end loop;
    if instr( p_fmt, '@p@' ) > 0
    then
      t_fmt := p_fmt;
    else
      t_fmt := '@p@';
    end if;
    -- added, but not really implemented
    if instr( p_ord, '@o@' ) > 0
    then
      t_ord := p_ord;
    else
      t_ord := '@o@';
    end if;
    -- changed cursor/replace to handle ordering
    open rc for replace( replace( 'select ' || t_fmt || '
                          from( ' || p_stmt || ' )
                          group by ' || t_fmt || '
                          order by min(' || t_ord || ')'
                       , '@p@'
                       , desc_tab( numcols - 1 ).col_name
                       )
                       , '@o@'
                       , desc_tab( numcols - 2 ).col_name
                       );
    loop
      fetch rc into t_c2;
      exit when rc%notfound;
      atyp.addattr( t_c2
                  , case desc_tab( numcols ).col_type
                    when 1   then dbms_types.typecode_varchar2
                    when 2   then dbms_types.typecode_number
                    when 9   then dbms_types.typecode_varchar2
                    when 11  then dbms_types.typecode_varchar2  -- show rowid as varchar2
                    when 12  then dbms_types.typecode_date
                    when 208 then dbms_types.typecode_urowid
                    when 96  then dbms_types.typecode_char
                    when 180 then dbms_types.typecode_timestamp
                    when 181 then dbms_types.typecode_timestamp_tz
                    when 231 then dbms_types.typecode_timestamp_ltz
                    when 182 then dbms_types.typecode_interval_ym
                    when 183 then dbms_types.typecode_interval_ds
                  end
                , desc_tab( numcols ).col_precision
                , desc_tab( numcols ).col_scale
                , case desc_tab( numcols ).col_type
                    when 11 then 18  -- for rowid col_max_len = 16, and 18 characters are shown
                    else desc_tab( numcols ).col_max_len
                  end
                , desc_tab( numcols ).col_charsetid
                , desc_tab( numcols ).col_charsetform
                  );
    end loop;
    close rc;
    atyp.endcreate;
    anytype.begincreate( dbms_types.typecode_table, rtype );
    rtype.SetInfo( null, null, null, null, null, atyp, dbms_types.typecode_object, 0 );
    rtype.endcreate();
    return odciconst.success;
  exception
    when others then
      return odciconst.error;
  end;
--
  static function ODCITablePrepare( sctx out PivotImpl,
    ti in sys.ODCITabFuncInfo,
    p_stmt in varchar2,
    p_fmt in varchar2 := 'upper(@p@)',
    p_ord in varchar2 := '@o@', -- added
    dummy in number )
  return number
  is
    prec     pls_integer;
    scale    pls_integer;
    len      pls_integer;
    csid     pls_integer;
    csfrm    pls_integer;
    elem_typ anytype;
    aname    varchar2(30);
    tc       pls_integer;
  begin
    tc := ti.RetType.GetAttrElemInfo( 1, prec, scale, len, csid, csfrm, elem_typ, aname );
--
    if instr( p_fmt, '@p@' ) > 0
    then
      sctx := PivotImpl( elem_typ, p_stmt, p_fmt, p_ord, null ); -- changed
    else
      sctx := PivotImpl( elem_typ, p_stmt, '@p@', '@o@', null ); -- changed
    end if;
    return odciconst.success;
  end;
--
  static function ODCITableStart( sctx in out PivotImpl,
    p_stmt in varchar2,
    p_fmt in varchar2 := 'upper(@p@)',
    p_ord in varchar2 := '@o@', -- added
    dummy in number )
  return number
  is
    cur         integer;
    numcols     number;
    desc_tab    dbms_sql.desc_tab2;
    t_stmt      varchar2(32767);
    type_code   pls_integer;
    prec        pls_integer;
    scale       pls_integer;
    len         pls_integer;
    csid        pls_integer;
    csfrm       pls_integer;
    schema_name varchar2(30);
    type_name   varchar2(30);
    version     varchar2(30);
    attr_count  pls_integer;
    attr_type   anytype;
    attr_name   varchar2(100);
    dummy2      integer;
  begin
    cur := dbms_sql.open_cursor;
    dbms_sql.parse( cur, p_stmt, dbms_sql.native );
    dbms_sql.describe_columns2( cur, numcols, desc_tab );
    dbms_sql.close_cursor( cur );
--
    for i in 1 .. numcols - 3 -- changed
    loop
      t_stmt := t_stmt || ', "' || desc_tab( i ).col_name || '"';
    end loop;
--
dbms_output.put_line(t_stmt);
    type_code := sctx.ret_type.getinfo( prec
                                      , scale
                                      , len
                                      , csid
                                      , csfrm
                                      , schema_name
                                      , type_name
                                      , version
                                      , attr_count
                                      );
    for i in numcols - 2 .. attr_count -- changed
    loop
      type_code := sctx.ret_type.getattreleminfo( i
                                                 , prec
                                                 , scale
                                                 , len
                                                 , csid
                                                 , csfrm
                                                 , attr_type
                                                 , attr_name
                                                 );
      t_stmt := t_stmt || replace( ', max( decode( ' || sctx.fmt || ', ''' || attr_name || ''', ' || desc_tab( numcols ).col_name || ' ) )'
                                 , '@p@'
                                 , desc_tab( numcols - 1 ).col_name
                                 );
    end loop;
    t_stmt := 'select ' || substr( t_stmt, 2 ) || ' from ( ' || sctx.stmt || ' )';
    for i in 1 .. numcols - 3 -- changed
    loop
      if i = 1
      then
        t_stmt := t_stmt || ' group by "' || desc_tab( i ).col_name || '"';
      else
        t_stmt := t_stmt || ', "' || desc_tab( i ).col_name || '"';
      end if;
    end loop;
--
    sctx.cur := dbms_sql.open_cursor;
    dbms_sql.parse( sctx.cur, t_stmt, dbms_sql.native );
    for i in 1 .. attr_count
    loop
      type_code := sctx.ret_type.getattreleminfo( i
                                                 , prec
                                                 , scale
                                                 , len
                                                 , csid
                                                 , csfrm
                                                 , attr_type
                                                 , attr_name
                                                 );
      case type_code
        when dbms_types.typecode_char          then dbms_sql.define_column( sctx.cur, i, 'x', 32767 );
        when dbms_types.typecode_varchar2      then dbms_sql.define_column( sctx.cur, i, 'x', 32767 );
        when dbms_types.typecode_number        then dbms_sql.define_column( sctx.cur, i, cast( null as number ) );
        when dbms_types.typecode_date          then dbms_sql.define_column( sctx.cur, i, cast( null as date ) );
        when dbms_types.typecode_urowid        then dbms_sql.define_column( sctx.cur, i, cast( null as urowid ) );
        when dbms_types.typecode_timestamp     then dbms_sql.define_column( sctx.cur, i, cast( null as timestamp ) );
        when dbms_types.typecode_timestamp_tz  then dbms_sql.define_column( sctx.cur, i, cast( null as timestamp with time zone ) );
        when dbms_types.typecode_timestamp_ltz then dbms_sql.define_column( sctx.cur, i, cast( null as timestamp with local time zone ) );
        when dbms_types.typecode_interval_ym   then dbms_sql.define_column( sctx.cur, i, cast( null as interval year to month ) );
        when dbms_types.typecode_interval_ds   then dbms_sql.define_column( sctx.cur, i, cast( null as interval day to second ) );
      end case;
    end loop;
    dummy2 := dbms_sql.execute( sctx.cur );
    return odciconst.success;
  end;
--
  member function ODCITableFetch( self in out PivotImpl,
    nrows in number,
    outset out anydataset )
  return number
  is
    c1_col_type pls_integer;
    type_code   pls_integer;
    prec        pls_integer;
    scale       pls_integer;
    len         pls_integer;
    csid        pls_integer;
    csfrm       pls_integer;
    schema_name varchar2(30);
    type_name   varchar2(30);
    version     varchar2(30);
    attr_count  pls_integer;
    attr_type   anytype;
    attr_name   varchar2(100);
    v1     varchar2(32767);
    n1     number;
    d1     date;
    ur1    urowid;
    ids1   interval day to second;
    iym1   interval year to month;
    ts1    timestamp;
    tstz1  timestamp with time zone;
    tsltz1 timestamp with local time zone;
  begin
    outset := null;
    if nrows < 1
    then
-- is this possible???
      return odciconst.success;
    end if;
--
dbms_output.put_line( 'fetch' );
    if dbms_sql.fetch_rows( self.cur ) = 0
    then
      return odciconst.success;
    end if;
--
dbms_output.put_line( 'done' );
    type_code := self.ret_type.getinfo( prec
                                      , scale
                                      , len
                                      , csid
                                      , csfrm
                                      , schema_name
                                      , type_name
                                      , version
                                      , attr_count
                                      );
    anydataset.begincreate( dbms_types.typecode_object, self.ret_type, outset );
    outset.addinstance;
    outset.piecewise();
    for i in 1 .. attr_count
    loop
      type_code := self.ret_type.getattreleminfo( i
                                                 , prec
                                                 , scale
                                                 , len
                                                 , csid
                                                 , csfrm
                                                 , attr_type
                                                 , attr_name
                                                 );
dbms_output.put_line( attr_name );
        case type_code
          when dbms_types.typecode_char then
            dbms_sql.column_value( self.cur, i, v1 );
            outset.setchar( v1 );
          when dbms_types.typecode_varchar2 then
            dbms_sql.column_value( self.cur, i, v1 );
            outset.setvarchar2( v1 );
          when dbms_types.typecode_number then
            dbms_sql.column_value( self.cur, i, n1 );
            outset.setnumber( n1 );
          when dbms_types.typecode_date then
            dbms_sql.column_value( self.cur, i, d1 );
            outset.setdate( d1 );
          when dbms_types.typecode_urowid then
            dbms_sql.column_value( self.cur, i, ur1 );
            outset.seturowid( ur1 );
          when dbms_types.typecode_interval_ds then
            dbms_sql.column_value( self.cur, i, ids1 );

    outset.setintervalds( ids1 );
          when dbms_types.typecode_interval_ym then
            dbms_sql.column_value( self.cur, i, iym1 );
            outset.setintervalym( iym1 );
          when dbms_types.typecode_timestamp then
            dbms_sql.column_value( self.cur, i, ts1 );
            outset.settimestamp( ts1 );
          when dbms_types.typecode_timestamp_tz then
            dbms_sql.column_value( self.cur, i, tstz1 );
            outset.settimestamptz( tstz1 );
          when dbms_types.typecode_timestamp_ltz then
            dbms_sql.column_value( self.cur, i, tsltz1 );
            outset.settimestampltz( tsltz1 );
        end case;
    end loop;
    outset.endcreate;
    return odciconst.success;
  end;
--
  member function ODCITableClose( self in PivotImpl )
  return number
  is
    c integer;
  begin
    c := self.cur;
    dbms_sql.close_cursor( c );
    return odciconst.success;
  end;
end;
/

create or replace
function pivot( p_stmt in varchar2,
  p_fmt in varchar2 := 'upper(@p@)',
  p_ord in varchar2 := '@o@', -- added
  dummy in number := 0 )
return anydataset pipelined using PivotImpl;
/

关于sql - 用于动态 sql 透视的 Oracle 函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29819968/

相关文章:

sql - 将汇总行添加到 MS Access 查询

python - 有没有更有效的方法在 Django 中根据条件参数数量编写此自定义 SQL?

database - 你怎么知道什么是好的索引?

database - 如何在实体-属性-值设计中处理不同的数据类型(例如,具有多列的单个表或每种数据类型的多个表)?

sql - 如何在匿名 PL/SQL block 中使用重复占位符名称?

php - 选择等效行

php - Mysql加入而不替换原始id的值

database - 将现有列修改为外键

Oracle闪回,查询过去的数据

oracle - PLS-00302 : component must be declared- Unable to resolve