【问题标题】:Best way to encapsulate complex Oracle PL/SQL cursor logic as a view?将复杂的 Oracle PL/SQL 游标逻辑封装为视图的最佳方式?
【发布时间】:2010-09-06 09:53:41
【问题描述】:

我编写了 PL/SQL 代码以将表非规范化为更易于查询的表单。该代码使用一个临时表来完成它的一些工作,将原始表中的一些行合并在一起。

逻辑写为pipelined table function,遵循链接文章中的模式。 table 函数使用PRAGMA AUTONOMOUS_TRANSACTION 声明来允许临时表操作,并且还接受游标输入参数以将非规范化限制为某些 ID 值。

然后,我创建了一个视图来查询表函数,将所有可能的 ID 值作为游标传入(该函数的其他用途将更具限制性)。

我的问题:这一切真的有必要吗?我是否完全错过了完成同一件事的更简单的方法?

每次我接触 PL/SQL 时,我都会觉得我打字太多了。

更新:我将添加我正在处理的表格的草图,让每个人都了解我正在谈论的非规范化。该表存储员工工作的历史记录,每个工作都有一个激活行和(可能)一个终止行。员工可能同时拥有多个工作,以及在不连续的日期范围内一遍又一遍地从事相同的工作。例如:

| EMP_ID | JOB_ID | STATUS | EFF_DATE    | other columns...
|      1 |     10 | A      | 10-JAN-2008 |
|      2 |     11 | A      | 13-JAN-2008 |
|      1 |     12 | A      | 20-JAN-2008 |
|      2 |     11 | T      | 01-FEB-2008 |
|      1 |     10 | T      | 02-FEB-2008 |
|      2 |     11 | A      | 20-FEB-2008 |

查询它以找出谁在做什么工作是不平凡的。因此,对于通过游标传入的任何EMP_IDs,我的非规范化函数仅使用每个作业的日期范围填充临时表。传入EMP_IDs 1 和 2 将产生以下结果:

| EMP_ID | JOB_ID | START_DATE  | END_DATE    |
|      1 |     10 | 10-JAN-2008 | 02-FEB-2008 |
|      2 |     11 | 13-JAN-2008 | 01-FEB-2008 |
|      1 |     12 | 20-JAN-2008 |             |
|      2 |     11 | 20-FEB-2008 |             |

END_DATE 允许 NULLs 用于没有预定终止日期的工作。)

您可以想象,这种非规范化的形式查询起来要容易得多,但创建它——据我所知——需要一个临时表来存储中间结果(例如,激活的作业记录)已找到行,但尚未找到终止...)。使用流水线表函数填充临时表,然后返回其行是我想出的唯一方法。

【问题讨论】:

    标签: sql oracle plsql


    【解决方案1】:

    我认为解决这个问题的一种方法是使用分析函数...

    我使用以下方法设置您的测试用例:

    create table employee_job (
        emp_id integer,
        job_id integer,
        status varchar2(1 char),
        eff_date date
        );  
    
    insert into employee_job values (1,10,'A',to_date('10-JAN-2008','DD-MON-YYYY'));
    insert into employee_job values (2,11,'A',to_date('13-JAN-2008','DD-MON-YYYY'));
    insert into employee_job values (1,12,'A',to_date('20-JAN-2008','DD-MON-YYYY'));
    insert into employee_job values (2,11,'T',to_date('01-FEB-2008','DD-MON-YYYY'));
    insert into employee_job values (1,10,'T',to_date('02-FEB-2008','DD-MON-YYYY'));
    insert into employee_job values (2,11,'A',to_date('20-FEB-2008','DD-MON-YYYY'));
    
    commit;
    

    我使用 lead 函数获取下一个日期,然后将其全部包装为子查询,以获取“A”记录并添加结束日期(如果有)。

    select
        emp_id,
        job_id,
        eff_date start_date,
        decode(next_status,'T',next_eff_date,null) end_date
    from
        (
        select
            emp_id,
            job_id,
            eff_date,
            status,
            lead(eff_date,1,null) over (partition by emp_id, job_id order by eff_date, status) next_eff_date,
            lead(status,1,null) over (partition by emp_id, job_id order by eff_date, status) next_status
        from
            employee_job
        )
    where
        status = 'A'
    order by
        start_date,
        emp_id,
        job_id
    

    我确信我错过了一些用例,但你明白了。分析函数是你的朋友:)

    EMP_ID   JOB_ID     START_DATE     END_DATE            
      1        10       10-JAN-2008    02-FEB-2008         
      2        11       13-JAN-2008    01-FEB-2008         
      2        11       20-FEB-2008                              
      1        12       20-JAN-2008                              
    

    【讨论】:

    • 非常酷。我从未听说过分析函数,但我一定会检查出来。这看起来比我尝试过的简单得多。
    • 事实证明这可以正常工作。有一个复杂的情况是,单个终止可能有多个激活,但是通过将 decode 行更改为: decode(next_status, 'A', next_eff_date - 1, next_eff_date) 问题得到了很好的处理。非常感谢。
    【解决方案2】:

    而不是将输入参数作为游标,我将有一个表变量(不知道 Oracle 是否有这样的东西,我是 TSQL 人)或者用 ID 值填充另一个临时表并加入它在视图/功能或任何你需要的地方。

    在我看来,光标的唯一时间是当你 必须 循环时。当你必须循环时,我总是建议在应用程序逻辑中的数据库之外执行此操作。

    【讨论】:

      【解决方案3】:

      听起来您在这里放弃了一些读取一致性,即:如果您有并发修改数据修改,则临时表的内容可能与源数据不同步。

      不知道要求,也不知道您想要实现的目标的复杂性。我会尝试

      1. 定义一个视图,在 SQL 中包含(可能很复杂)逻辑,否则我会添加一些 PL/SQL 到混合中;
      2. 流水线表函数,但使用 SQL 集合类型(而不是临时表)。一个简单的例子在这里:http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:4447489221109

      2 号可以减少活动部件并解决一致性问题。

      马修·巴特勒

      【讨论】:

      • 感谢您的提示。我不会过多担心读取一致性,因为视图及其底层表函数仅用于报告,并且不需要最新的一致性。
      【解决方案4】:

      这里真正的问题是“只写”表设计——我的意思是,向其中插入数据很容易,但从中获取有用信息却很棘手且效率低下!您的“临时”表具有“永久”表应该具有的结构。

      您可以这样做吗:

      • 创建具有更好结构的永久表
      • 填充它以匹配第一个表中的数据
      • 在原始表上定义数据库触发器,以使新表从现在开始保持同步

      然后,您只需从新表格中进行选择即可执行您的报告。

      【讨论】:

      • 不幸的是,原始表是供应商提供的系统的核心。我自己考虑了触发器的想法,尽管它必须引用原始表,这会导致其他问题。整个系统有一种“脆弱”的感觉,所以我认为最好是看风景
      【解决方案5】:

      HollyStyles,我完全同意你的看法。我以前也是一个 TSQL 人,发现 Oracle 的一些特性有点令人困惑。不幸的是,临时表在 Oracle 中并不方便,在这种情况下,其他现有的 SQL 逻辑期望直接查询表,所以我给它这个视图。在这个系统中,确实没有存在于数据库之外的应用程序逻辑。

      Oracle 开发人员似乎比我想象的更热衷于使用游标。鉴于 PL/SQL 的束缚和纪律性质,这更加令人惊讶。

      【讨论】:

        【解决方案6】:

        最简单的解决方案是:

        1. 创建一个 global temporary table 只包含您需要的 ID:

          CREATE GLOBAL TEMPORARY TABLE tab_ids (id INTEGER)  
          ON COMMIT DELETE ROWS;
          
        2. 使用您需要的 ID 填充临时表。

        3. 在您的过程中使用 EXISTS 操作来选择仅在 IDs 表中的行:

            SELECT yt.col1, yt.col2 FROM your\_table yt  
             WHERE EXISTS (  
                SELECT 'X' FROM tab_ids ti  
                 WHERE ti.id = yt.id  
             )
          

        您还可以将逗号分隔的 ID 字符串作为函数参数传递,并将其解析为表格。这是由单个 SELECT 执行的。想知道更多 - 问我如何:-)但这必须是一个单独的问题。

        【讨论】:

        • 注意,临时表只需要创建一次。
        • 我提到的临时表实际上是由 ID 组成的(四列,构成原始表的主键)。麻烦的是,一些原始行必须合并在一起,所以它不像你建议的那么简单。
        猜你喜欢
        • 2011-06-12
        • 1970-01-01
        • 1970-01-01
        • 2023-04-01
        • 1970-01-01
        • 1970-01-01
        • 2016-05-10
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多