【问题标题】:Create trigger to update balance after transactions from an account and to an account - SQL Oracle创建触发器以在从帐户到帐户的交易后更新余额 - SQL Oracle
【发布时间】:2021-05-18 08:03:29
【问题描述】:

有人可以帮我吗?我正在尝试创建一个触发器,在 TRANSFERS 表中的事务之后更新 ACCOUNT 表中的余额。整个表模型更大,但这里有两个:

--ACCOUNT--
CREATE TABLE account(
account_num NUMBER(8) NOT NULL PRIMARY KEY,
ktnr REFERENCES account_type(ktnr),
regdate DATE NOT NULL,
balance NUMBER(10,2));

--TRANSFERS--
CREATE TABLE transfers(
row_nr NUMBER(9) NOT NULL PRIMARY KEY,
pers_nr REFERENCES customer(pers_nr),
from_account_num REFERENCES account(account_num),
to_account_num REFERENCES account(account_num),
amount NUMBER(10,2),
date DATE NOT NULL);

而且我认为代码是完全错误的,但这就是解决方案的想法。不知道如何写得更好。

create or replace trigger aifer_transfers
 after insert
 on transfers
 for each row
 when (new.amount is not null)
begin
  if :new.to_account_num != :new.pers_nr then
    update account a
    set a.balance = a.balance - :new.amount
    where :old.account_num = :new:account_num;

    elsif 
    :new.to_account_num = :new.pers_nr  then
    update account a
    set a.balance = a.balance + :new.amount
    where :old.account_num = :new:account_num;
    end if; 
 end; 

许多不同的错误,取决于选择的变量: 行/列:3/9 PL/SQL:忽略 SQL 语句 行/列:5/26 PLS-00049:错误的绑定变量“新”

【问题讨论】:

  • 抱歉,我的代码在翻译成英文时出现了一些错误。我编辑了它,应该更有意义。
  • :new:account_num 应该是:new.account_num。这不是语法错误,但我无法想象您希望您的where 子句比较account_num:old:new 值。我敢打赌,您希望 where 子句为 a.account_num = :new.account_num。我看不出您的if 语句在逻辑上的意义——为什么引用account 表的to_account_num 会匹配引用customer 表的pers_nr 列?
  • insert 声明:old 总是empty
  • @JustinCave transfers 表没有account_num 列。
  • @Baibs 正如我在之前的评论中所说的那样; transfers 表没有 account_num 表,因此使用 :NEW.account_num 将不起作用(您会得到 PLS-00049 异常)。您只能使用 :NEW 绑定变量引用已插入的表的列(即 transfers)。

标签: sql oracle


【解决方案1】:

您的表声明缺少某些数据类型(因为我们没有您的其他表),并且从 Oracle 12 开始,您可以使用 IDENTITY 列作为主键(并且有一些 CHECK 约束和 @987654329 DATE 列的 @ 值):

CREATE TABLE account(
  account_num NUMBER(8,0)
              GENERATED ALWAYS AS IDENTITY
              NOT NULL
              CONSTRAINT account__account_num__pk PRIMARY KEY,
  ktnr        NUMBER(8,0)
              -- CONSTRAINT account__ktnr__fk REFERENCES account_type(ktnr)
              ,
  regdate     DATE
              DEFAULT SYSDATE
              NOT NULL,
  balance     NUMBER(10,2)
              NOT NULL
);

CREATE TABLE transfers(
  row_nr           NUMBER(9)
                   GENERATED ALWAYS AS IDENTITY
                   NOT NULL
                   CONSTRAINT transfers__row_nr__pk PRIMARY KEY,
  pers_nr          NUMBER(8,0)
                   NOT NULL
                   -- CONSTRAINT transfers__pers_nr__fk REFERENCES customer(pers_nr)
                   ,
  from_account_num NOT NULL
                   CONSTRAINT transfers_from__fk REFERENCES account(account_num),
  to_account_num   NOT NULL
                   CONSTRAINT transfers__to__fk REFERENCES account(account_num),
  amount           NUMBER(10,2)
                   NOT NULL
                   CONSTRAINT transfers__amount_chk CHECK ( amount > 0 ),
  datetime         DATE
                   DEFAULT SYSDATE
                   NOT NULL,
  CONSTRAINT transfers__from_ne_to__chk CHECK ( from_account_num != to_account_num )
);

然后就可以创建触发器了:

CREATE TRIGGER aifer_transfers
AFTER INSERT ON TRANSFERS
  FOR EACH ROW
BEGIN
  UPDATE account
  SET   balance = CASE account_num
                  WHEN :NEW.from_account_num
                  THEN balance - :NEW.amount
                  WHEN :NEW.to_account_num
                  THEN balance + :NEW.amount
                  ELSE balance
                  END
  WHERE account_num IN ( :new.from_account_num, :new.to_account_num );
END;
/

如果您创建 3 个帐户:

INSERT INTO account ( ktnr, balance ) VALUES ( 0, 100 );
INSERT INTO account ( ktnr, balance ) VALUES ( 0, 100 );
INSERT INTO account ( ktnr, balance ) VALUES ( 0, 100 );

然后:

INSERT INTO transfers (
  pers_nr, from_account_num, to_account_num, amount
) VALUES (
  0, 1, 2, 20
);

然后:

SELECT * FROM account;

输出:

ACCOUNT_NUM KTNR REGDATE BALANCE
1 0 18-MAY-21 80
2 0 18-MAY-21 120
3 0 18-MAY-21 100

然后:

INSERT INTO transfers (
  pers_nr, from_account_num, to_account_num, amount
) VALUES (
  0, 1, 3, 15
);

应该是:

ACCOUNT_NUM KTNR REGDATE BALANCE
1 0 18-MAY-21 65
2 0 18-MAY-21 120
3 0 18-MAY-21 115

然后:

INSERT INTO transfers ( pers_nr, from_account_num, to_account_num, amount )
SELECT 0, 3, 2, 10 FROM DUAL UNION ALL
SELECT 0, 2, 3, 20 FROM DUAL UNION ALL
SELECT 0, 1, 2, 10 FROM DUAL;

应该是:

ACCOUNT_NUM KTNR REGDATE BALANCE
1 0 18-MAY-21 55
2 0 18-MAY-21 120
3 0 18-MAY-21 125

db小提琴here


更新

对于您的表,没有任何额外的CHECK 约束,您可能需要:

CREATE TRIGGER aifer_transfers
AFTER INSERT ON TRANSFERS
  FOR EACH ROW
BEGIN
  IF :NEW.amount <= 0 THEN
    RAISE_APPLICATION_ERROR( -20000, 'Amount must be above zero.' );
  END IF;
  IF :NEW.from_account_num IS NULL THEN
    RAISE_APPLICATION_ERROR( -20000, 'From account must be non-null.' );
  END IF;
  IF :NEW.to_account_num IS NULL THEN
    RAISE_APPLICATION_ERROR( -20000, 'To account must be non-null.' );
  END IF;
  IF :NEW.from_account_num = :NEW.to_account_num THEN
    RAISE_APPLICATION_ERROR( -20000, 'Accounts must be different.' );
  END IF;
  UPDATE account
  SET   balance = CASE account_num
                  WHEN :NEW.from_account_num
                  THEN balance - :NEW.amount
                  WHEN :NEW.to_account_num
                  THEN balance + :NEW.amount
                  ELSE balance
                  END
  WHERE account_num IN ( :new.from_account_num, :new.to_account_num );
END;
/

(但该验证应该全部作为CHECK 约束在transfers 表上而不是在触发器中实现。)

db小提琴here

【讨论】:

  • 这看起来很棒!但由于这是学校作业,我恐怕无法更改表格代码。此外,我还有其他 13 个基于当前代码的连接分配(触发器、过程和函数)。
  • @Baibs 表有 4 个(我认为)不同之处(身份列除外),它们都是 NOT NULLCHECK 约束。如果您想将这些检查移到触发器的 WHERE 子句中,那么您可以,但否则答案仍然使用原始表。
  • @Baibs db<>fiddle 表的 DDL 中唯一改变的是在 DATE 标识符周围使用双引号,因为它是一个关键字;除了答案适用于您的桌子。对TRIGGER 的唯一更改是将应在transfers 表上的CHECK 约束中检查的内容移到触发器的WHEN 子句中的检查中。
猜你喜欢
  • 1970-01-01
  • 2017-03-31
  • 2022-01-11
  • 2020-09-08
  • 1970-01-01
  • 2014-11-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多