如何在 Oracle 中合并结果集?

Posted

技术标签:

【中文标题】如何在 Oracle 中合并结果集?【英文标题】:How do I combine result sets in Oracle? 【发布时间】:2011-06-03 14:55:18 【问题描述】:

我有一个包含输入参数和输出游标的过程。该光标给出的结果如下所示:

0100 | 0
0130 | 1
0200 | 2
0230 | 0
...

第一列是静态时间码。第二列是在给定日期的那个时间段内安排了多少次的聚合。

那个程序是:

PROCEDURE DAILYLOAD (datep IN DATE, results OUT SYS_REFCURSOR)
   AS
   BEGIN
   Open results for
      SELECT RUN_TIME_C, COUNT (SCH_RPT_I)
    FROM    ITS_SCH_RPT_RUN_TIME
         LEFT OUTER JOIN
            ITS_SCH_RPT
         ON (   RUN_TIME_C = RUN_TIME1_C
             OR RUN_TIME_C = RUN_TIME2_C
             OR RUN_TIME_C = RUN_TIME3_C)
   WHERE EXP_DATE_D IS NULL
         OR datep < exp_date_d AND datep > start_date_d AND SUSPENDED_USER='N'
            AND (   ( (TO_CHAR (datep, 'D') = 1) AND RUN_SUNDAY_C = 'Y')
                 OR ( (TO_CHAR (datep, 'D') = 2) AND RUN_MONDAY_C = 'Y')
                 OR ( (TO_CHAR (datep, 'D') = 3) AND RUN_TUESDAY_C = 'Y')
                 OR ( (TO_CHAR (datep, 'D') = 4) AND RUN_WEDNESDAY_C = 'Y')
                 OR ( (TO_CHAR (datep, 'D') = 5) AND RUN_THURSDAY_C = 'Y')
                 OR ( (TO_CHAR (datep, 'D') = 6) AND RUN_FRIDAY_C = 'Y')
                 OR ( (TO_CHAR (datep, 'D') = 7) AND RUN_SATURDAY_C = 'Y'))
GROUP BY RUN_TIME_C
ORDER BY RUN_TIME_C;
   END DAILYLOAD;

我想用不同的参数从包装过程中多次调用这个过程,这样我就可以得出每周负载和每月负载。从概念上讲,这可以通过将各个结果集通过 union all 之类的方法连接起来,然后通过第一列对每个分组的第二列求和来进行分组。

现在,我有类似的东西

Dailyload(datep, results1);
Dailyload(datep + 1, results2);
...

OPEN results FOR
  SELECT run_time_c, 
         SUM(rpt_option_i) 
    FROM SELECT * 
           FROM results1 
         UNION ALL 
         SELECT * 
           FROM results2 
         UNION ALL ... 
         GROUP BY run_time_c 
         ORDER BY run_time_c

有没有办法在 Oracle 中做到这一点?使用批量收集进行获取看起来很有希望,但我没有看到在我的特定场景中使用它的好方法。

【问题讨论】:

您需要发布执行繁重工作的查询——即运行多次的查询。当您封装功能时,SQL 往往表现不佳——它是基于 SET 的,不像 Java/C#/等。当性能不佳时,易于维护是否值得? 请澄清:您是否想要 1) 执行多个过程调用(每个具有不同的参数)并让 Oracle 合并结果。 2) 使用多个参数执行单个过程,并在单个响应中获得这些结果的联合。或者 3) 多次执行一个过程并获得一个组合结果集,你不在乎它们在哪里组合? (如果是,请将您的开发语言添加到 OP 中 我添加了基础查询。我认为@CosCallis 的第 1 项描述了我想要的。 似乎有一个版本会更容易,您可以在其中传递您想要总结的天数,并相应地调整日期检查。 date_p &lt; (exp_date_d + num_days_p),例如,也许。 @Alex Poole:同意。或者更琐碎的startdate_penddate_p 【参考方案1】:

您可以将其作为一个联合来执行,包括一个标识组的列。个人选择或多或少会复制您的 DailyLoad SP 正在做的事情。

 select foo.Mygroup, sum(foo.col1) 
  from
 (
 select 'A' as MyGroup, col1 WHERE ...
 union all
 select 'B' as MyGroup, col1 WHERE ...
 union all
 select 'C' as MyGroup, col1 WHERE ...
 ) as Foo
 group by MyGroup

如果事先不知道组的数量,可以构建一个符合这个基本结构的动态sql语句。

如果组的数量太大以至于您的动态语句太大,您可以使用存储过程将每次调用的结果与 MyGroup 列一起推送到临时表中。然后您可以通过 select 语句针对临时表发布您的组。

【讨论】:

【参考方案2】:

如果过程的 out 参数是一个 ref 游标,并且您无法像 OMG Ponies 建议的那样复制它在内部执行的操作以生成基于单个集合的良好查询,this previous answer 可能会有所帮助。您可以使用中间流水线函数将 sys_refcursor 结果转换为可以视为表格的内容:

create package p as
    type tmp_rec_type is record (run_time_c varchar2(4),
        rpt_option_i number);
    type tmp_table_type is table of tmp_rec_type;

    procedure dailyload(p_date in date, p_results out sys_refcursor);
    function func(p_date in date) return tmp_table_type pipelined;
    procedure sumload(p_start_date in date, p_results out sys_refcursor);
end;
/

create package body p as
    /* Your existing procedure, which may be elsewhere */
    procedure dailyload(p_date in date, p_results out sys_refcursor) is
    begin
        open p_results for
            select to_char(created, 'HH24MI') as run_time_c,
                count(*) as rpt_option_i
            from all_objects
            where trunc(created) = trunc(p_date)
            group by to_char(created, 'HH24MI');
    end;

    /* Intermediate pipelined function */
    function func(p_date in date) return tmp_table_type pipelined is
        tmp_cursor sys_refcursor;
        tmp_rec tmp_rec_type;
    begin
        dailyload(p_date, tmp_cursor);
        loop
            fetch tmp_cursor into tmp_rec;
            exit when tmp_cursor%notfound;
            pipe row(tmp_rec);
        end loop;
    end;

    /* Wrapper function to join the result sets together */
    procedure sumload(p_start_date in date, p_results out sys_refcursor) is
    begin
        open p_results for
            select run_time_c, sum(rpt_option_i) from (
                select * from table(func(p_start_date))
                union all
                select * from table(func(p_start_date + 1))
                union all
                select * from table(func(p_start_date + 2))
            )
            group by run_time_c;
    end;
end;
/

猜测你的数据类型,并从一个随机表中挑选数据,作为一个例子,crouse。从 SQL*Plus 或 SQL Developer 调用:

var results refcursor;

exec p.sumload(to_date('01-Jun-11','DD-Mon-RR'), :results);

print :results

【讨论】:

【参考方案3】:

我没有时间对此进行测试,但我相信这会奏效:

    修改存储过程,使 SYS_REFCURSOR 成为 IN OUT 参数,而不仅仅是 OUT。 在 for/each 循环中设置参数(无论您使用什么语言...) 在循环中传递对相同 SYS_REFCURSOR 的引用。 在 sproc 中创建一个本地 SYS_REFCURSOR 变量以像您当前一样选择。 在 sproc 内部合并本地和参数 SYS_REFCURSOR

这应该构建您的结果集。

如果您不想对此进行测试,我可能会在周末在 C#/Oracle 10g 中为此构建一个测试用例,以验证我的假设。

如果您使用的是 11g,另一种选择是流水线查询,正如 How to create Oracle stored procedure which can return specific entities as well all entity 所讨论的那样(查看 @tbone 的答案和他提供的链接...)

【讨论】:

【参考方案4】:

您可以使用oracle全局临时表来积累和进一步处理数据。

它是内存结构,开销很小。

【讨论】:

以上是关于如何在 Oracle 中合并结果集?的主要内容,如果未能解决你的问题,请参考以下文章

如何在java中合并来自两个不同数据库的两个不同结果集

Oracle中函数如何返回结果集

MDX 查询:如果与其他维度无关,如何在查询结果中显示虚拟集?

如何加入/合并数据集?

在oracle里,多条返回结果,如何只能返回一条

如何将结果集作为输入从java传递到oracle存储过程