Oracle 查询:动态循环列以创建行

Posted

技术标签:

【中文标题】Oracle 查询:动态循环列以创建行【英文标题】:Oracle Query: Loop through columns dynamically to create rows 【发布时间】:2014-11-21 14:13:51 【问题描述】:

我有以下 Oracle 数据库,它可以包含任意数量的列,这些列可能会随着用户对 Web 应用程序的干预而增长。

ID | Name   | Football | WhoCreated  | When       | Baseball | Cheerleading | Swimming
1  | Billy  | (null)   |  sam, smith | (Timestamp)|   1      | (null)       |   1
2  | Susie  |  1       |  sam, smith | (Timestamp)| (null)   |   1          |   1
3  | Johnny |  1       |  Homer      | (Timestamp)|   1      | (null)       | (null)

我正在尝试生成一个看起来像这样的输出

2, Susie, Football
3, Johnny, Football
1, Billy, Baseball
3, Johnny, Baseball
2, Susie, Cheerleading
1, Billy, Swimming
2, Susie, Swimming

我可以使用 UNION 来做到这一点,但我必须针对特定的名称字段进行调整。我已经有大约 50 列(50 个联合),并且系统中的用户可以随时增长。更复杂的是,我在列表中间有几列用于审计目的。我真的需要某种动态的方式来遍历列,我已经搜索过了,但似乎没有一个能解决我遇到的问题。

【问题讨论】:

规范化你的数据模型怎么样? 我没有设计这个数据库,我只是在做报告。我很想诅咒那些认为这是构建它的最佳方式的人。 【参考方案1】:

这应该会给你一个想法。我无法测试它,但相当容易理解。

create or replace package test_pkg AS

  TYPE REP_CURS IS REF CURSOR;
  TYPE output_REC IS RECORD(
    id_    number,
    name_  varchar2(50),
    field_ varchar2(50));

  TYPE output_TAB IS TABLE OF output_REC;

  FUNCTION Get_Data RETURN output_TAB
    PIPELINED;

END test_pkg;

CREATE OR REPLACE PACKAGE BODY test_pkg IS

  FUNCTION Get_Data RETURN output_TAB
    PIPELINED IS

    output_REC_ output_REC;
    rep_lines_  REP_CURS;
    stmt_       VARCHAR2(5000);
    table_rec_  yourtable%ROWTYPE;

  begin
    stmt_ := '  YOUR QUERY HERE ';

    OPEN rep_lines_ FOR stmt_;
    LOOP
      FETCH rep_lines_
        INTO table_rec_;
      EXIT WHEN rep_lines_%NOTFOUND;

      output_REC_.id_   := table_rec_.id;
      output_REC_.name_ := table_rec_.name;

      if table_rec_.football IS not null then
        output_REC_.field_ := table_rec_.football;
        PIPE ROW(output_REC_);
      end if;

      if table_rec_.Baseball IS not null then
        output_REC_.field_ := table_rec_.Baseball;
        PIPE ROW(output_REC_);
      end if;

      if table_rec_.Cheerleading IS not null then
        output_REC_.field_ := table_rec_.Cheerleading;
        PIPE ROW(output_REC_);
      end if;

      if table_rec_.Swimming IS not null then
        output_REC_.field_ := table_rec_.Swimming;
        PIPE ROW(output_REC_);
      end if;

    END LOOP;
    CLOSE rep_lines_;

    RETURN;
  exception
    when others then
      DBMS_OUTPUT.put_line('Error:' || DBMS_UTILITY.FORMAT_ERROR_BACKTRACE ||
                           DBMS_UTILITY.FORMAT_ERROR_STACK ||
                           DBMS_UTILITY.FORMAT_CALL_STACK);
  END Get_Data;

END test_pkg;

【讨论】:

谢谢,我将不得不在非生产服务器上进行测试。 我的 Oracle 许可证受到供应商的严格限制,我收到权限不足错误。我想我将不得不在 SQL 之外执行此操作。不过,我会坚持使用该代码!【参考方案2】:

你应该创建一个函数然后使用它,请考虑下面的代码:

CREATE OR REPLACE FUNCTION commalist (par_id NUMBER) RETURN VARCHAR2 IS 
  TYPE curs IS REF CURSOR;
  v_emp_cursor curs;
  fieldvalue   VARCHAR2(4000);
  var_out      VARCHAR2(4000);
  CURSOR col IS
  SELECT column_name
    FROM user_tab_columns
   WHERE table_name = 'TABLE';

BEGIN

  FOR reccol IN col LOOP
    OPEN v_emp_cursor FOR ('SELECT '||reccol.column_name||' val
                      FROM TABLE 
                     WHERE id = '||par_id);
    LOOP
      FETCH v_emp_cursor INTO fieldvalue;
        IF fieldvalue IS NOT NULL THEN
          var_out := var_out||fieldvalue||', ';
        END IF;
      EXIT WHEN v_emp_cursor%NOTFOUND;
    END LOOP;

    CLOSE v_emp_cursor;
  END LOOP;

  RETURN SubStr(var_out, 0, Length(var_out) - 2);

END;

【讨论】:

我没有足够的权限来创建函数。不幸的是,我的许可证受到供应商的限制。这些答案虽然是金矿,但我将把它保存起来以备不时之需。我将尝试将表格转储到 Crystal 报表。【参考方案3】:

试试这个

with src as (select 1 ID, 'Billy' name, 0 Football, 'sam smith' WhoCreated, sysTimestamp when,
                   1 Baseball, cast(null as number) Cheerleading, 1 Swimming
              from dual
            union all
            select 2, 'Susie', 1, 'sam smith', sysTimestamp, null, 1, 1
              from dual
            union all
            select 3, 'Johnny', 1, 'Homer', sysTimestamp, 1, null, null 
              from dual),

unpivottbl as (select *
                from src
                UNPIVOT 
                (
                   VAL
                   FOR descript
                   IN (Football, Baseball, Cheerleading, Swimming)
                ))

select ID, name, descript 
  from unpivottbl 
 where VAL = 1

虽然它不是动态解决方案,但您需要添加要取消透视数据的列。这是unpivot的参考。

【讨论】:

这是一个静态解决方案。请检查标题 不幸的是,看起来静态解决方案可能是我唯一的希望。为什么我会使用 unpiviot 表而不是仅使用 Union 语句?记得我的真实数据有 50 左右列。

以上是关于Oracle 查询:动态循环列以创建行的主要内容,如果未能解决你的问题,请参考以下文章

在 Oracle 的存储过程中动态运行查询

如何从函数游标返回行并在oracle中循环

【求助】有关oracle 动态行转列

反透视列以生成行

如何循环在 SQL Server 中动态创建的查询

C# 查询 SQL Server 表中的 JSON 列以获取匹配的行