(对不起,很长的帖子,但我想所有的信息都是必要的)
我们有两个表 - 任务和子任务。每个任务由一个或多个子任务组成,每个对象都有一个开始日期,结束日期和持续时间。此外,子任务有一个排序。
表格
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;
答案 0 :(得分:7)
在这里使用触发器只是在寻找麻烦。
此外,选择使用调度程序可能不是最好的主意,因为预定作业只能看到已提交的数据。因此要么你提交了触发器,它会将事务逻辑抛出窗口,要么对表的更改会延迟到事务结束。
你应该:
使用程序。最简单的答案。当您有多个应用程序时,它们不应该直接执行DML / businees逻辑,它们应该始终使用过程来执行它们以便它们都运行相同的代码。禁止直接使用授权或视图的DML。您可能需要通过视图上的INSTEAD OF
触发器强制使用过程(仅当您无法修改应用程序时才考虑这一点。)
可能比您的情况下的程序更好:使用不包含重复数据的模式。您不希望存储冗余数据:这使得应用程序开发比需要的更复杂。在性能,资源和能源方面,解决问题的最佳方法是当您意识到任务不必要时。
从模型的描述中,您可以删除以下列:
task
表仅包含开始日期,每个子任务仅存储其持续时间。如果需要聚合信息,请使用联接。您可以使用视图让应用程序透明地访问数据。
使用使用包变量的mutating trigger workaround来标识具有BEFORE
和AFTER
语句触发器的已修改行。显然,这将涉及大量难以编码,测试和维护的代码,因此您应尽可能使用选项(1)和(2)。
答案 1 :(得分:1)
从我的观点来看一些更一般的建议(据我了解你的要求):
删除列“duration_in_days”,这是多余的。您可以通过视图或查询提供此功能。
使触发器尽可能简单,即仅用于:
end_date - start_date
不要直接对表格进行任何更新或插入,提供PL / SQL程序,您可以在其中处理所有业务规则。
PROCEDURE INSERT_Task(id in task.pk%type, name in task.pk%type, start_date in task.start_date%type) is
...
PROCEDURE INSERT_SubTask(Task_id in task.pk%type, subtask_id in subtask.pk%type,
name in subtask.name%type, start_date in subtask.start_date%type) is
...
PROCEDURE DELETE_SubTask(subtask_id in subtask.pk%type) is
...
etc.
然后你不需要重新计算持续时间或时间,并且更容易保持一致。