【问题标题】:ORACLE: Using CTEs (Common Table Expressions) with PL/SQLORACLE:在 PL/SQL 中使用 CTE(公用表表达式)
【发布时间】:2014-11-12 15:26:46
【问题描述】:

首先,我的背景是 SQL Server。使用 CTE(公用表表达式)是一件轻而易举的事,将其转换为带有变量的存储过程不需要对 SQL 的结构进行任何更改,只需将输入的值替换为变量名。

然而,在 Oracle PL/SQL 中,情况就完全不同了。我的 CTE 可以像直接 SQL 一样正常工作,但是一旦我尝试将它们包装为 PL/SQL,我就会遇到很多问题。据我了解,SELECT 现在需要一个 INTO,它只保存一条记录的结果。但是,我想要多个值的整个记录​​集。

如果我在这里遗漏了明显的内容,我深表歉意。我认为我 99% 的问题是我需要进行的范式转变。

举个例子:

注意:我在这里大大简化了 SQL。我知道下面的示例可以在单个 SQL 语句中完成。实际的 SQL 要复杂得多。这是我在这里寻找的基础知识。

WITH A as (SELECT * FROM EMPLOYEES WHERE DEPARTMENT = 200),

B as (SELECT * FROM A WHERE EMPLOYEE_START_DATE > date '2014-02-01'),

C as (SELECT * FROM B WHERE EMPLOYEE_TYPE = 'SALARY')

SELECT 'COUNTS' as Total,
(SELECT COUNT(*) FROM A) as 'DEPT_TOTAL',
(SELECT COUNT(*) FROM B) as 'NEW_EMPLOYEES',
(SELECT COUNT(*) FROM C) as 'NEW_SALARIED'
FROM A
WHERE rowcount = 1;

现在,如果我想使用传入或在顶部预定义的变量将其制成 PL/SQL,那么声明变量、将值弹出并将我的硬编码值更改为变量并不是一件简单的事情并运行它。 注意:我知道我可以简单地将硬编码的值更改为变量,例如 :Department、:StartDate 和 :Type,但我再次将示例过于简单化了。

我正在努力解决三个问题:

1) 使用带有声明变量的 PL/SQL 重写它的最佳方法是什么?现在,CTE 必须进入某些内容。但后来我一次处理一行,而不是整张桌子。所以 CTE 'A' 一次是一行,而 CTE B 只会看到一行,而不是 A 的所有数据结果,等等。我知道我很可能不得不使用 CURSORS 来遍历记录,不知何故,这似乎过于复杂了。

2) 输出现在必须使用 DBMS_OUTPUT。对于多条记录,我将不得不使用带有 FETCH 的 CURSOR(或 FOR...LOOP)。是的?

3) 在速度和使用的资源方面,此 SQL 与直接 SQL 相比是否存在很大的性能问题?

再次感谢您,如果我在这里遗漏了一些非常明显的东西,我深表歉意!

【问题讨论】:

标签: oracle plsql common-table-expression


【解决方案1】:

首先,这与 CTE 无关。这种行为与简单的select * from table 查询相同。不同之处在于,使用 T-SQL,查询进入一个隐式游标,该游标返回给调用者。从 Management Studio 执行 SP 时,这很方便。结果集出现在数据窗口中,就好像我们直接执行了查询一样。但这实际上是非标准行为。 Oracle 有更标准的行为,可以表述为“任何未定向到游标的查询的结果集必须定向到变量”。当指向变量时,查询必须只返回一行。

要复制 T-SQL 的行为,您只需显式声明并返回游标。然后调用代码从游标中获取整个结果集,但一次获取一行。你没有得到Sql Developer或PL/SQL Developer将结果集引流到数据显示窗口的便利,但你不能拥有一切。

但是,由于我们通常不会编写 SP 只是为了从 IDE 中调用,因此使用 Oracle 的显式游标比使用 SQL Server 的隐式游标更容易。只需谷歌“oracle return ref cursor to caller”即可获得很多好的材料。

【讨论】:

    【解决方案2】:

    最简单的方法是把它包装到一个隐式的for循环中

    begin
       for i in (select object_id, object_name
                   from user_objects
                  where rownum = 1) loop
          -- Do something with the resultset
          dbms_output.put_line (i.object_id || ' ' || i.object_name);
       end loop;
    end;
    

    单行查询,无需预定义变量。

    【讨论】:

    • 作为旁注 - 从 Oracle 10 隐式 FOR 循环开始,如 @OlafurTryggvason 所示,在内部自动使用 BULK COLLECT 语义。因此,根据我的经验,使用 CURSOR 声明来声明游标几乎没有什么好处。您应该只使用 SELECT...INTO... 如果您绝对肯定有问题的 SELECT 只会返回一行。 TSQL 和 PL/SQL 是使用不同编程模型的不同野兽,但坚持下去,经过少量的精神痛苦之后,您就会习惯它(PL/SQL,我的意思是 - 不是痛苦 :-)。祝你好运。
    • 那么为什么不明确使用 BULK COLLECT 和 FORALL 语句 ;-) 我希望发帖者特别提到他们的 Oracle 版本,最多保留四位小数以避免任何混淆(如果有的话)。
    • 我只使用带有 Limit 子句的显式批量收集。
    • Bob - 谢谢鼓励,痛苦是对的!我一直在想我让这比实际上更难。 BULK COLLECT 看起来是朝着正确方向迈出的一步。会用它做一些实验。拉利特 - Oracle 11.2.0.4
    • 这里有一点更新。 BULK COLLECT 绝对是我需要做的事情。在查询使用 BULK COLLECT 创建的集合时,我确实遇到了问题。我确实使用了表([NAME_OF_BULK_COLLECT_COLLECTION]),我被“SQL 语句中不允许的本地集合类型”错误所困扰。然后我在过程顶部使用了创建或替换类型,但没有结果。看起来 DBA 没有让我访问在数据库中创建对象。明天将不得不鞭打她。 ;)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-02
    • 2017-06-02
    • 2011-06-12
    • 2020-12-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多