4

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

该逻辑按照链接文章中的模式编写为流水线表函数。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因此,对于通过游标传入的任何 s,我的非规范化函数仅使用每个作业的日期范围填充临时表。传入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 用于没有预定终止日期的工作。)

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

4

6 回答 6

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;

我使用了前导函数来获取下一个日期,然后将其全部包装为子查询,以获取“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                              
于 2008-09-18T21:19:12.863 回答
1

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

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

于 2008-08-22T10:25:06.327 回答
1

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

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

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

2 号会给您更少的活动部件并解决您的一致性问题。

马修·巴特勒

于 2008-09-18T12:57:26.140 回答
1

The real problem here is the "write-only" table design - by which I mean, it's easy to insert data into it, but tricky and inefficient to get useful information out of it! Your "temporary" table has the structure the "permanent" table should have had in the first place.

Could you perhaps do this:

  • Create a permanent table with the better structure
  • Populate it to match the data in the first table
  • Define a database trigger on the original table to keep the new table in sync from now on

Then you can just select from the new table to perform your reporting.

于 2008-09-19T15:33:44.390 回答
0

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

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

于 2008-08-22T15:36:36.080 回答
0

最简单的解决方案是:

  1. 创建一个仅包含您需要的 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 执行的。想知道更多 - 问我如何:-)但这必须是一个单独的问题。

于 2008-09-18T01:04:32.563 回答