(很长的帖子,很抱歉,但是我想所有信息都非常有必要)
我们有两个表-任务和子任务。每个任务由一个或多个子任务组成,并且每个对象都有开始日期,结束日期和持续时间。此外,子任务具有顺序。
桌子
create table task ( pk number not null primary key, name varchar2(30) not null, start_date date, duration_in_days number, end_date date, needs_recomputation number default 0 ); create table subtask ( pk number not null primary key, task_fk references task(pk), name varchar2(30) not null, start_date date, duration_in_days number, end_date date, ordering number not null );
商业规则
start_date + duration = end_date
duration = sum(duration of subtasks)
这直接为更新/删除生成以下要求:
目前的方法
这种(种类)有效,但是它有几个缺点:
所以我的问题是-是否有任何明智的替代方法?
包裹
create or replace package pkg_task is g_update_in_progress boolean; procedure recomputeDates(p_TaskID in task.pk%TYPE); procedure recomputeAllDates; end; create or replace package body pkg_task is procedure recomputeDates(p_TaskID in task.pk%TYPE) is begin g_update_in_progress := true; -- update the subtasks merge into subtask tgt using (select pk, start_date, duration_in_days, end_date, sum(duration_in_days) over(partition by task_fk order by ordering) as cumulative_duration, min(start_date) over(partition by task_fk) + sum(duration_in_days) over(partition by task_fk order by ordering rows between unbounded preceding and 1 preceding) as new_start_date, min(start_date) over(partition by task_fk) + sum(duration_in_days) over(partition by task_fk order by ordering) as new_end_date from subtask s where s.task_fk = p_TaskID order by task_fk, ordering) src on (src.pk = tgt.pk) when matched then update set tgt.start_date = nvl(src.new_start_date, src.start_date), tgt.end_date = nvl(src.new_end_date, src.end_date); -- update the task merge into task tgt using (select p_TaskID as pk, min(s.start_date) as new_start_date, max(s.end_date) as new_end_date, sum(s.duration_in_days) as new_duration from subtask s where s.task_fk = p_TaskID) src on (tgt.pk = src.pk) when matched then update set tgt.start_date = src.new_start_date, tgt.end_date = src.new_end_date, tgt.duration_in_days = src.new_duration, tgt.needs_recomputation = 0; g_update_in_progress := false; end; procedure recomputeAllDates is begin for cur in (select pk from task t where t.needs_recomputation = 1) loop recomputeDates(cur.pk); end loop; end; begin g_update_in_progress := false; end;
扳机
create or replace trigger trg_task before update on task for each row begin if (:new.start_date <> :old.start_date and not pkg_task.g_update_in_progress) then pkg_task.g_update_in_progress := true; -- set the start date for the first subtask update subtask s set s.start_date = :new.start_date where s.task_fk = :new.pk and s.ordering = 1; :new.needs_recomputation := 1; pkg_task.g_update_in_progress := false; end if; end; create or replace trigger trg_subtask before update on subtask for each row declare l_date_changed boolean := false; begin if (not pkg_task.g_update_in_progress) then pkg_task.g_update_in_progress := true; if (:new.start_date <> :old.start_date) then :new.end_date := :new.start_date + :new.duration_in_days; l_date_changed := true; end if; if (:new.end_date <> :old.end_date) then :new.duration_in_days := :new.end_date - :new.start_date; l_date_changed := true; end if; if (:new.duration_in_days <> :old.duration_in_days) then :new.end_date := :new.start_date + :new.duration_in_days; l_date_changed := true; end if; if l_date_changed then -- set the needs_recomputation flag for the parent task -- if this is the first subtask, set the parent's start date, as well update task t set t.start_date = (case when :new.ordering = 1 then :new.start_date else t.start_date end), t.needs_recomputation = 1 where t.pk = :new.task_fk; end if; pkg_task.g_update_in_progress := false; end if; end;
工作
begin dbms_scheduler.create_job( job_name => 'JOB_SYNC_TASKS' ,job_type => 'PLSQL_BLOCK' ,job_action => 'begin pkg_task.recomputeAllDates; commit; end; ' ,start_date => to_timestamp_tz('2014-01-14 10:00:00 Europe/Berlin', 'yyyy-mm-dd hh24:mi:ss tzr') ,repeat_interval => 'FREQ=HOURLY;BYMINUTE=0,5,10,15,20,25,30,35,40,45,50,55' ,enabled => TRUE ,comments => 'Task sync job, runs every 5 minutes'); end;
在这里使用触发器只是自找麻烦。
此外,选择使用调度程序可能不是最好的主意,因为调度的作业只能看到已提交的数据。因此,要么提交触发将事务逻辑抛出窗口之外的触发器,要么将对表的更改延迟到事务结束为止。
您应该:
使用程序。最简单的答案。当您有多个应用程序时,它们不应直接执行DML /业务逻辑,而应始终使用过程来执行它们,以便它们都运行相同的代码。禁止使用授权或视图的直接DML。您可能需要通过INSTEAD OF视图上的触发器来强制使用过程(仅当您无法修改应用程序时才考虑使用此过程)。
INSTEAD OF
在这种情况下,甚至可能比过程还要好:使用不包含重复数据的架构。您不想存储冗余数据:这使应用程序开发比所需的更为复杂。就性能,资源和精力而言,解决问题的最佳方法是当您意识到任务是不必要的时。
在模型的描述中,以下是您可以删除的列:
* task.duration_in_days * task.end_date * task.needs_recomputation * subtask.start_date * subtask.end_date
该task表将仅包含开始日期,并且每个子任务将仅存储其持续时间。当您需要汇总信息时,请使用联接。您可以使用视图使应用程序透明地访问数据。
task
BEFORE
AFTER