3

我有一张桌子

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; 
/
4

3 回答 3

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,是要放入表中的实际值。

这意味着您的表将自动更新,这是DML 触发器的点

如您所见,该功能根本没有意义。你的触发器可以变成

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 (证明)数值精度基于有效位数;这(根据 Oracle)只是非零数字。你很容易被这个抓住。数据库的重点是将可能的输入值限制为仅有效的输入值。我会认真考虑声明您的年龄列,number(3,0)以确保仅包含“可能”的值。

于 2012-09-08T08:38:14.833 回答
4

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

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

看看SQL Fiddle

于 2012-09-08T14:02:41.137 回答
1

如果您希望 AGE 从数据库中可用,有两种选择:

  1. 定义一个包含年龄计算的视图,或者
  2. 定义一个执行相同操作的虚拟列

要定义这样的视图:

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 始终保持一致。

分享和享受。

于 2015-03-05T15:24:17.570 回答