将复杂的Oracle PL / SQL游标逻辑封装为视图的最佳方法是什么?

时间:2008-08-21 14:29:49

标签: sql oracle plsql

我编写了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_ID,我的非规范化函数仅使用每个作业的日期范围填充临时表。传入EMP_ID s 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允许NULL s用于没有预定终止日期的作业。)

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

6 个答案:

答案 0 :(得分:4)

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

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

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                              

答案 1 :(得分:1)

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

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

答案 2 :(得分:1)

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

不了解要求,也不了解您想要达到的复杂程度。我会尝试

  1. 定义一个视图,在SQL中包含(可能很复杂的)逻辑,否则我会添加一些PL / SQL;
  2. 一个流水线表函数,但使用SQL集合类型(而不是临时表)。这里有一个简单的例子:http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:4447489221109
  3. 第2号可以减少移动部件并解决您的一致性问题。

    Mathew Butler

答案 3 :(得分:1)

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

你能否这样做:

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

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

答案 4 :(得分:0)

我不能同意你的意见,HollyStyles。我曾经也是一个TSQL人,并且发现一些Oracle的特性更让人感到困惑。不幸的是,临时表在Oracle中不那么方便,在这种情况下,其他现有的SQL逻辑期望直接查询表,所以我给它这个视图。在这个系统中,数据库外部确实没有应用程序逻辑。

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

答案 5 :(得分:0)

最简单的解决方案是:

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

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

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

      SELECT yt.col1, yt.col2 FROM your\_table yt  
       WHERE EXISTS (  
          SELECT 'X' FROM tab_ids ti  
           WHERE ti.id = yt.id  
       )
    
  4. 您还可以将逗号分隔的ID字符串作为函数参数传递,并将其解析为表格。这是由一个SELECT执行的。想知道更多 - 问我如何:-)但它必须是一个单独的问题。