使用oracle plsql触发器计算生日年龄并在表中插入年龄

时间:2012-09-08 08:13:04

标签: sql database oracle triggers plsql

我有一张桌子

dates
(dob date,
age number(4)
);

我将插入一个出生日期,触发器将计算年龄并在年龄字段中插入该年龄。

CREATE OR REPLACE PROCEDURE getage IS 
ndob date;
nage number(10);
BEGIN
select dob into ndob from dates; 
select (sysdate-to_date(ndob))/365 into nage from dual;
update dates set age=nage;
END;
/
SHOW ERRORS;

这个程序工作正常,但只有一行,我需要触发所有行,但如果我从触发器调用它,则会发生错误。

CREATE OR REPLACE TRIGGER agec after INSERT OR UPDATE ON dates
FOR EACH ROW
BEGIN
getage;
END; 
/

3 个答案:

答案 0 :(得分:8)

  

请帮助......我真的需要这个...

不,你没有。我不确定你会注意;并且没有理由你应该:-)但是:

不要在您的数据库中存储年龄。您绝对可以保证偶尔出错。每个人每年的年龄都会发生变化,但是,对于某些人来说,每天都会发生变化。这反过来意味着您需要每天运行批处理作业并更新年龄。如果此操作失败,或者非常严格并且运行两次,那么您就遇到了麻烦。

您应该始终在需要时计算年龄。这是一个相当简单的查询,从长远来看可以为您节省很多痛苦。

select floor(months_between(sysdate,<dob>)/12) from dual

我已经设置了一点SQL Fiddle来演示


现在,实际回答你的问题

  

这个程序工作正常,但只有一行,,,但对于所有行   我需要触发器,但如果我从触发器调用它然后错误   发生...

你没有提到错误,请在将来这样做,因为它非常有帮助,但我怀疑你的错误

  

ORA-04091:表string.string是变异的,触发器/函数可能不是   看到它

这是因为您的过程正在查询正在更新的表。 Oracle不允许这样做以保持数据的读一致视图。避免这种情况的方法是不查询表,您不需要这样做。将您的程序更改为在出生日期返回正确结果的函数:

function get_age (pDOB date) return number is
   /* Return the the number of full years between 
      the date given and sysdate.
      */    
begin    
   return floor(months_between(sysdate,pDOB)/12);    
end;

再次注意我使用的是months_between()函数,因为并非所有年份都有365天。

在触发器中,您可以直接将值分配给列。

CREATE OR REPLACE TRIGGER agec before INSERT OR UPDATE ON dates
FOR EACH ROW
BEGIN
   :new.age := get_age(:new.dob);
END;

:new.<column>语法是对正在更新的<column>的引用。在这种情况下,:new.age是将放在表中的实际值。

这意味着您的表格会自动更新,即the point of a DML trigger

正如你所看到的那样,功能根本就没什么意义;你的触发器可以变成

CREATE OR REPLACE TRIGGER agec before INSERT OR UPDATE ON dates
FOR EACH ROW
BEGIN
   :new.age := floor(months_between(sysdate,:new,DOB)/12);
END; 

但是,话虽如此,如果您打算在数据库的其他地方使用此功能,请将其保持独立。在这样的函数中保留在多个位置使用的代码是一种很好的做法,因此它总是以相同的方式使用。它还确保每当有人计算年龄时,他们都会正确地做到这一点。

稍微说一下,你确定要让人们成为9,999岁吗?或0.000000000001998 (proof)Numeric precision基于重要位数;这个(根据Oracle)只是非零数字。你很容易被这个抓住。数据库的要点是将可能的输入值限制为仅有效的输入值。我会认真考虑将您的年龄列声明为number(3,0),以确保只包含“可能”的值。

答案 1 :(得分:4)

如果您通过触发器执行此操作,则在一天之后该年龄可能会出错。这就是为什么要保存 数据库表中的年龄是不好的做法,没有人这样做。你需要的是一个观点。

create view person_age as
  select person_id, 
    floor(months_between(trunc(sysdate),birthdate)/12) as age
    from birthdays;

查看SQL Fiddle

答案 2 :(得分:1)

如果您希望AGE可以从数据库中获得,则有两种选择:

  1. 定义包含年龄计算的视图,或
  2. 定义执行相同操作的虚拟列
  3. 定义这样的视图:

    CREATE OR REPLACE VIEW DATES_VIEW
      AS SELECT DOB,
                FLOOR(MONTHS_BETWEEN(SYSDATE, DOB) / 12) AS AGE
            FROM DATES
    

    这个问题是你必须记住你从DATES_VIEW中选择,但需要更新DATES,这在精神上很混乱。 IMO向表中添加虚拟列更清晰:

    CREATE TABLE DATES
      (DOB      DATE,
       AGE      GENERATED ALWAYS AS (FLOOR(MONTHS_BETWEEN(SYSDATE, DOB) / 12)) VIRTUAL);
    

    请注意,无法更新虚拟列。

    这两种方法都有助于确保AGE始终保持一致。

    分享并享受。