按日期限制的触发器

时间:2014-10-17 01:22:26

标签: sql oracle

我试图创建一个限制阅读器在给定月份内可以阅读的数量的触发器。

CREATE OR REPLACE trigger Readings_Limit
Before update or insert on reading
for each row declare
readingcount integer;
max_read integer := 5;
Begin
Select count(*) into readingcount
from (select * 
from Reading
where to_char(DateRead, 'YYYY-MM') = to_char(DateRead, 'YYYY-MM'))
where employeeid = :new.employeeid;
if :old.employeeid = :new.employeeid then
return;
else
if readingcount >= max_read then
raise_application_error (-20000, 'An Employee can only read 5 per month');
end if;
end if;
end;

这限制了读者总共5个月,无论月份,我似乎每个月都不能达到5个最大值。任何想法都非常感谢!

3 个答案:

答案 0 :(得分:0)

尝试像这样重写你的触发器:

CREATE OR REPLACE trigger Readings_Limit
  Before update or insert on reading
  for each row
declare
  readingcount integer;
  max_read integer := 5;
Begin
  Select count(*) into readingcount
    from Reading
    where DateRead between trunc(sysdate,'MM') and last_day(sysdate)
      and employeeid = :new.employeeid;

  if :old.employeeid = :new.employeeid then
    return;
  else
    if readingcount >= max_read then
      raise_application_error (-20000, 'An Employee can only read 5 per month');
    end if;
  end if;
end;

您可以将实际月份添加到选择中,并避免不必要的日期转换。

我不明白条件

if :old.employeeid = :new.employeeid then

是不是意味着触发器不应该触发更新?在这种情况下,最好只为插入或使用子句if inserting then...

创建触发器

答案 1 :(得分:0)

为了使用触发器正确创建此验证,应创建一个过程以获取用户指定的锁,以便在多用户环境中正确地序列化验证。

PROCEDURE request_lock
  (p_lockname                     IN     VARCHAR2
  ,p_lockmode                     IN     INTEGER  DEFAULT dbms_lock.x_mode
  ,p_timeout                      IN     INTEGER  DEFAULT 60
  ,p_release_on_commit            IN     BOOLEAN  DEFAULT TRUE
  ,p_expiration_secs              IN     INTEGER  DEFAULT 600)
IS
  -- dbms_lock.allocate_unique issues implicit commit, so place in its own
  -- transaction so it does not affect the caller
  PRAGMA AUTONOMOUS_TRANSACTION;
  l_lockhandle                   VARCHAR2(128);
  l_return                       NUMBER;
BEGIN
  dbms_lock.allocate_unique
    (lockname                       => p_lockname
    ,lockhandle                     => p_lockhandle
    ,expiration_secs                => p_expiration_secs);
  l_return := dbms_lock.request
    (lockhandle                     => l_lockhandle
    ,lockmode                       => p_lockmode
    ,timeout                        => p_timeout
    ,release_on_commit              => p_release_on_commit);
  IF (l_return not in (0,4)) THEN
    raise_application_error(-20001, 'dbms_lock.request Return Value ' || l_return);
  END IF;
  -- Must COMMIT an autonomous transaction
  COMMIT;
END request_lock;

为了对可伸缩性产生最小影响,序列化应该在最好的级别上完成,对于此约束,每个employeeid和month。在语句完成后检查约束之前,可以使用类型来创建变量以存储每行的此信息。这些类型可以在数据库中定义,也可以(从Oracle 12c中)在包规范中定义。

CREATE OR REPLACE TYPE reading_rec
AS OBJECT
  (employeeid NUMBER(10) -- Note should match the datatype of reading.employeeid
  ,dateread   DATE);

CREATE OR REPLACE TYPE readings_tbl
AS TABLE OF reading_rec;

然后可以在复合触发器中使用过程和类型(假设至少Oracle 11,这需要在早期版本中拆分为单独的触发器)

CREATE OR REPLACE TRIGGER too_many_readings
  FOR INSERT OR UPDATE ON reading
  COMPOUND TRIGGER

  -- Table to hold identifiers of inserted/updated readings
  g_readings readings_tbl;

BEFORE STATEMENT 
IS
BEGIN
  -- Reset the internal readings table
  g_readings := readings_tbl();
END BEFORE STATEMENT; 

AFTER EACH ROW
IS
BEGIN
  -- Store the inserted/updated readings
  IF (  INSERTING
     OR :new.employeeid  <> :old.employeeid
     OR :new.dateread <> :old.dateread)
  THEN           
    g_readings.EXTEND;
    g_readings(g_readings.LAST) := reading_rec(:new.employeeid, :new.dateread);
  END IF;
END AFTER EACH ROW;

AFTER STATEMENT
IS
  CURSOR csr_readings
  IS
    SELECT DISTINCT
           employeeid
          ,trunc(dateread,'MM') monthread
    FROM TABLE(g_readings)
    ORDER BY employeeid
            ,trunc(dateread,'MM');
  CURSOR csr_constraint_violations
    (p_employeeid reading.employeeid%TYPE
    ,p_monthread  reading.dateread%TYPE)
  IS
    SELECT count(*) readings
    FROM reading rdg
    WHERE rdg.employeeid = p_employeeid
    AND trunc(rdg.dateread, 'MM') = p_monthread
    HAVING count(*) > 5;
  r_constraint_violation csr_constraint_violations%ROWTYPE;
BEGIN
  -- Check if for any inserted/updated readings there exists more than
  -- 5 readings for the same employee in the same month. Serialise the
  -- constraint for each employeeid so concurrent transactions do not
  -- affect each other
  FOR r_reading IN csr_readings LOOP
    request_lock('TOO_MANY_READINGS_'
      || r_reading.employeeid
      || '_' || to_char(r_reading.monthread, 'YYYYMM'));
    OPEN csr_constraint_violations(r_reading.employeeid, r_reading.monthread);
    FETCH csr_constraint_violations INTO r_constraint_violation;
    IF csr_constraint_violations%FOUND THEN
      CLOSE csr_constraint_violations;
      raise_application_error(-20001, 'Employee ' || r_reading.employeeid
        || ' now has ' || r_constraint_violation.readings
        || ' in ' || to_char(r_reading.monthread, 'FMMonth YYYY'));
    ELSE
      CLOSE csr_constraint_violations;
    END IF;
  END LOOP;
END AFTER STATEMENT;

END;

答案 2 :(得分:-1)

您需要设置您正在查看的月份,因此如果您正在考虑当前月份,请将内部查询设置为:

( select * from Reading 
where to_char(DateRead,'YYYY-MM') = to_char(DateRead,'YYYY-MM')
and to_char(sysdate,'YYYY-MM') = to_char(DateRead,'YYYY-MM'))

这样它总会与当前月份进行比较,并且应该随着日期的变化而移动。