使用 PL/SQL 游标为报告工具返回结果集

Posted

技术标签:

【中文标题】使用 PL/SQL 游标为报告工具返回结果集【英文标题】:Using PL/SQL Cursor to return a result set for reporting tool 【发布时间】:2017-04-14 17:06:35 【问题描述】:

我是一名报告作者。我的查询通常是来自某个源(在本例中为 Oracle Exadata)的 SELECT 语句,这些语句从一个或多个表中进行选择、连接、WHERE 中的过滤器、HAVING 中的进一步过滤器组等。我通常对我的任何来源都有只读权限连接到。所以,我不能创建存储过程、包或函数。

我需要对复杂查询使用高级功能,使用 FOR..LOOP、IF THEN ELSE 结构等来优化派生结果集以输出到 Qlikview 或 Tableau 等报告工具。

随着我的学习,我发现了很多关于在复杂逻辑结构中使用游标的课程,但每节课都使用 dbms_output.put_line 作为每次迭代的结果。输出最终在缓冲区中,而不是在结果集中。我了解到 SYS_REFCURSOR 是指向结果集的指针。听起来很有希望,但所有示例都以 CREATE OR REPLACE PROCEDURE/PACKAGE 开头。

如果我解释我想要的结果是什么,我希望你能填补我的理解上的空白:

我有一个复杂的查询,它通过 UNION 来合成多个 SELECT 语句,在 Join 和 Where 子句中使用子选择等。它返回一个几乎完整的结果集,但我需要在发送结果之前进一步完善它设置为报告工具。

我可以:

DECLARE
  CURSOR current_Schedule IS
  SELECT --insert complex query here-- ;

  row_Schedule  cur_Schedule%ROWTYPE;

BEGIN
  IF NOT current_Schedule%ISOPEN THEN
    OPEN current_Schedule;
  END IF;

  LOOP
    FETCH current_Schedule INTO row_Schedule;
    EXIT WHEN current_Schedule%NOTFOUND;
    --Here's where I think my question is....
    dbms_output.put_line('row_Schedule.Login ' || row_Schedule.Code)
  END LOOP;

EXCEPTION --put exception clause here

END;

这有效并验证了我可以遍历复杂查询、更新、过滤等结果的概念,但问题是......

它输出到缓冲区,而不是结果集。

我可能误解了一些 PL/SQL 概念。我在这里和其他来源找到的所有研究示例都假设用户有能力创建函数、包等。

我仅限于从具有源只读权限的 QlikView ODBC 连接将其作为脚本启动。

鉴于我的局限性,有解决方案吗?

感谢您花时间阅读本文,如果您能提供一个连贯的答案,我们将不胜感激。

mfc


编辑:附加信息

我正在使用一个劳动力管理模式,该模式已去规范化并位于 Exadata 数据湖中。我有 2 个表 DET_SEG(详细段)和 GEN_SEG(一般段)。 DET_SEG 包含 Employee 和 Segment Code 表、NOM_DATE、START MOMENT 和 STOP MOMENT 的 fk。代码有 2 种类型,加法和减法。加法是表示员工被分配到的业务单位的工作代码,减法代码是让员工下班的任何类型的代码,例如休息、午餐、生病等。所有代码都与一个表格相关联,该表格生成 30 分钟的时间间隔。由于代码可以占据一个完整或部分区间,并且它们可以跨越多个区间,我最终得到了 6 个独特的用例来解决:之前开始的代码,占据整个区间并在后续区间结束。开始于之前,结束于内部的代码是另一个。 你可以想象其余的,我不会遍历它们。最终输出生成一个标题为“分钟”的计算列,指示加法或减法代码在间隔中占用的时间量。

我最终创建了 6 个独特的 SELECT 块 UNION'ed 一起输出倒数第二个解决方案。以下是较短 SELECT 之一的示例:

SELECT 
  cal.GlobalIntervals AS G_Intervals,
  mainstart.startmoment AS StartMoment,
  mainstart.stopmoment AS StopMoment,
  mainstart.code as Code,
  mainstart.ID AS Login,
  ((case mainstart.code
        when 'BRK1' then round(24*60*(mainstart.startmoment -mainstart.stopmoment),0)
        when 'BRK2' then round(24*60*(mainstart.startmoment - mainstart.stopmoment),0)
        when 'BRK3' then round(24*60*(mainstart.startmoment - mainstart.stopmoment),0)
        else round(24*60*(mainstart.startmoment - mainstart.stopmoment),0)
  end)) as Minutes  
from
(
SELECT
    (
    to_date(to_char((
    SELECT CLNDR_DT
    FROM COMMOBJ.DIM_DATE
    where CLNDR_DT = trunc(sysdate)
    ), 'dd-mon-yyyy') || HOUR_24_LABEL_CD, 'dd-mon-yyyy hh24:mi:ss')
    ) as GlobalIntervals
FROM    COMMOBJ.DIM_TIME t
WHERE   second_nbr = 0
AND     mod(minute_nbr, 30) = 0 
) cal
left outer join
(
  SELECT DISTINCT
        New_time(to_date(30121899,'ddMMyyyy')+EWFM.DET_SEG.START_MOMENT/1440,'GMT','PST') as StartMoment,
    New_time(to_date(30121899,'ddMMyyyy')+EWFM.DET_SEG.STOP_MOMENT/1440,'GMT','PST') as StopMoment,
    SEG_CODE.CODE,
    SEG_CODE.DESCR,
    EMP.ID,
    EMP.LAST_NAME,
    EMP.FIRST_NAME 
FROM EWFM.DET_SEG DET_SEG
    INNER JOIN EWFM.SEG_CODE SEG_CODE ON (DET_SEG.SEG_CODE_SK = SEG_CODE.SEG_CODE_SK)
    INNER JOIN EWFM.EMP EMP ON (DET_SEG.EMP_SK = EMP.EMP_SK)
    INNER JOIN SBCG_ADHOC.SBCG_STAFF SBCG_STAFF ON (EMP.ID = SBCG_STAFF.AGENT_LOGIN)
    WHERE trunc (New_time(to_date(30121899,'ddMMyyyy')+EWFM.DET_SEG.START_MOMENT/1440,'GMT','PST')) =  trunc(sysdate)  
    and EWFM.SEG_CODE.CRNT_REC_IND = 'Y'
    and SEG_CODE.CODE NOT IN ('SHIFT', 'CCBOUT', 'HOLIDA', 'LIND', 'PCHT', 'PIND', 'PXTR', 'LCHT', 'LXTR', 'PSVC', 'HV Saves Inbound' )
  )mainstart
  --StartMoment occurs AFTER Current Interval
  on  cal.GlobalIntervals < mainstart.StartMoment 
  --StopMoment occurs on Next Interval 
  and (cal.GlobalIntervals + (1/24/60 * 30)) = mainstart.StopMoment 
  --StartMoment occurs prior to Next Interval
  and mainstart.StartMoment < (cal.GlobalIntervals + (1/24/60 * 30))

输出如下所示:

G_INTERVALS |开始时刻 |停止|代码 |登录 |分钟 2017 年 4 月 17 日 12:00 | 2/17/17 12:00 | 2/17/17 12:15 | BRK1 | ABC123 | -15 2017 年 4 月 17 日 12:00 | 2/17/17 12:00 | 2/17/17 08:00 |移位 | XYZ321 | 30 2017 年 4 月 17 日 12:30 | 2/17/17 12:45 | 2/17/17 01:45 |午餐 | LK4567 | -15

对于每个用例,分钟列的 Case 语句会发生变化,最终联接中的 ON 条件也会发生变化。虽然可能不是最有效的,但它生成的数据集几乎可以用于收缩率报告

最后一个条件(以及这篇文章的原因)是 GEN_SEG 表。它包含 Day Long 事件的信息,例如 Planned Sick (SICKPL)、unplanned Sick (SICKUP) 等。它只有一个日期,没有 START 或 STOP 时刻。这是我寻求进一步改进的地方。使用光标遍历结果集和在 GEN_SEG 中找到的每个登录,对于每个

最终,我们希望发现员工队伍中的收缩。员工时间表只是第一步。计划内和计划外收缩的员工计划细化后,我将引入实际数据以进一步细化。

我怀疑我写了一部短篇小说,但我希望我已经包含了足够的细节来解释对二级细化的需要。如果您有任何想法或批评,我将不胜感激。

干杯~!

【问题讨论】:

您好,您为什么要寻找结果集?你试过意见吗? @hmmftg - OP 声明“我无法创建存储过程、包或函数”,这也排除了视图。 如果您的组织不允许您创建(或创建)对象来支持报告,那么您的选择将非常有限。您将不得不专注于解决这个“它返回一个几乎完整的结果集,但在将结果集发送到报告工具之前我需要进一步完善它” 在 SQL 中 .这些天来,您会惊讶于 Oracle SQL 的可能性。显然,除非您告诉我们此后期处理的详细信息,否则我们无法帮助您解决该问题。另外,请告诉我们您使用的是哪个版本的 Oracle。 谢谢 APC,我已经为我的原始问题添加了更多细节。我希望我已经很好地描述了我的问题,以解释我对后处理的需求。应该注意的是,我为这个脚本编写的所有原型都是在 Oracle 11g 中完成的。 【参考方案1】:

在您的 PL/SQL 块中,您正在使用游标,因此没有任何内容可以返回给客户端。

如果您使用的是 Oracle 12c(请参阅 implicit result sets),则可以从匿名块返回游标,但随后您将返回到没有过程控制的 SQL。

您可能想要的是pipelined function,它实际上为您提供了一个程序视图。但是,您不能从只读帐户执行此操作,因为您需要创建函数及其返回的集合类型。

不过,您可以使用 SQL 做很多事情。我很想知道什么报告要求需要程序逻辑。

【讨论】:

威廉,感谢您的回复。我将在这里和我原来的问题中回答(由于 cmets 的字符限制)。我对 PL/SQL 还很陌生,并试图摆脱对 T-SQL 的思考。 Oracle SQL 似乎更强大,但学习曲线有点陡峭,语法更复杂,而且有些概念似乎不能很好地在两者之间转换。请参阅我上面的编辑。谢谢

以上是关于使用 PL/SQL 游标为报告工具返回结果集的主要内容,如果未能解决你的问题,请参考以下文章

在 Java 中调用 Oracle PL/SQL 中的过程或函数。返回结果集 false

PL/SQL 打印出存储过程返回的引用游标

Oracle 存储过程返回结果集

oracle 如何返回多条记录

oracle-游标-存储过程-函数-包

oracle存储过程 中把临时表数据 返回结果集