用于动态 sql 透视的 Oracle 函数

Posted

技术标签:

【中文标题】用于动态 sql 透视的 Oracle 函数【英文标题】:Oracle function for dynamic sql pivoting 【发布时间】:2015-04-23 09:56:59 【问题描述】:

我在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

【问题讨论】:

你如何定义列的顺序;从 EDITS 值的 varchar2 排序顺序?通过快速测试,我按该顺序获得列。您可以添加表和数据设置来重新创建问题吗? 顺序是使用列定义的,每个编辑都有其预定义的顺序(我没有使用编辑的值)。刚刚用选择查询更新了我的问题。 我的意思是为什么你认为你得到的顺序是错误的。 EDIT 的实际值是多少-您​​在问题中显示的内容,更多样的不同情况?除非您传递了 p_fmt 参数,否则我认为您不会得到那些确切的数据透视表头,这让我认为您已经编辑了这些值 - 可能隐藏了问题...... 是的,编辑是不同的,每个需求都有一组预定义的编辑(更复杂的名称..这就是我在示例中使用虚拟名称的原因^^) 但是我们看不到这些值,所以我们不知道枢轴当前放置它们的顺序,以及您期望它们的顺序。代码按EDITS 排序价值;好吧,默认情况下是upper(EDITS)。您可能会想出一个p_fmt 来生成您想要的订单,或者更改您的查询,但我们没有足够的信息来告诉您如何(或者如果可能的话)。 【参考方案1】:

您正在使用的函数会忽略您使用 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@',并且可能还允许进行一些操作。这必须反映在所有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 cmets 标记了我更改的位:

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

【讨论】:

【参考方案2】:

我正在使用相同的枢轴并遇到相同的问题。您提到的动态透视按字母顺序排列透视列。我发现的 hacky 解决方法是使用 case 语句添加字符 像

SELECT 
                    ID_DEMAND
                    , NAME
                    , CASE
                    WHEN EDIT < 'EDIT2' THEN ' EDIT2'
                    WHEN EDIT > 'EDIT4' THEN '_EDIT4'
                    ELSE EDIT
                    END AS EDIT,
                    , ORDER_EDIT
                    , MIN (DATE_ACTION) as EDIT_START

这缺乏复杂性和风格,但可以完成工作。

【讨论】:

问题是我的编辑没有预定义的值(名称可能不同,可以从专用表中更改)所以我无法比较:/ @RidRoid - 那么你怎么知道你想要它们的顺序?是否在专用表中定义,例如带有序列/优先级列,您可以加入并在案例中使用? @AlexPoole 在我最初的selectquery 中有ORDER BY ID_DEMAND, ORDER_EDIT.. 但是一旦我将查询作为参数传递,订单就会丢失。【参考方案3】:

如果您不关心列名,可以作为代码修改的替代方法...

ROWNUM 将给出我认为与原始顺序相同的顺序。

由于文本排序,ROWNUM 无法自行正确排序。

IE: 1,10,11,12,2,20,21,22

如果你不关心列名,那么

用 0 前缀或填充数字允许“正常”排序行为。

IE 001,002,003,004,005,006,007

001 列名 1,002 列名 2, 003 列名 3

如果使用连接在一定程度上保持原始列名

注意: to_number 似乎不起作用,因为我相信在内部这会被按摩回文本。

【讨论】:

以上是关于用于动态 sql 透视的 Oracle 函数的主要内容,如果未能解决你的问题,请参考以下文章

使用 Sql Developer Oracle 的动态数据透视查询

oracle sql中的动态数据透视

oracle sql中的动态数据透视

动态 SQL 透视查询中的分组和聚合函数

从 SQL 函数返回动态透视表的困难 - “必须声明表变量 xxx”

Oracle 组和数据透视 - Oracle 中的动态数据透视