【问题标题】:Count(*) not working properly计数(*)无法正常工作
【发布时间】:2015-04-10 15:14:48
【问题描述】:

我创建了触发器 A1,以便某个类型的文章(即“Bert”)不能多次添加,并且库存中只能有 1 个。 但是,尽管我创建了触发器,但我仍然可以添加类型为“Bert”的文章。不知何故,计数返回“0”,但是当我运行相同的 sql 语句时,它返回正确的数字。如果我放下触发器并重新添加它,它也会开始正确计数。有什么想法可能出了什么问题?

TRIGGER A1 BEFORE INSERT ON mytable
FOR EACH ROW
DECLARE
 l_count NUMBER;
 PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
  SELECT COUNT(*) INTO l_count FROM mytable WHERE article = :new.article;

    dbms_output.put_line('Count: ' || l_count);
  IF l_count >0  THEN
    IF(:new.TYPEB = 'Bert') THEN 
      dbms_output.put_line('article already exists!');
      ROLLBACK; 
    END IF;
  ELSIF (:new.TYPEB = 'Bert' AND :new.stock_count>1) THEN
    dbms_output.put_line('stock cannot have more than 1 of this article with type Bert');
    ROLLBACK; 
  END IF;
END;

这是我使用的插入语句:

INSERT INTO mytable VALUES('Chip',1,9,1,'Bert');

【问题讨论】:

  • 你为什么不为此使用唯一约束?
  • 这种样式触发器不能工作。即使触发器是固定的,同时添加一条记录的两个事务也看不到对方,因为隔离,会允许添加。
  • 可能你没有提交以前的插入,这对自主事务很重要。
  • 并且在触发器中使用自治事务可能会产生无法预料的后果。此功能应谨慎使用,主要用于日志记录表。
  • Count(*) 按预期工作,但自治事务中的 ROLLBACK 仅回滚该自治事务,并且对导致触发器触发的语句没有影响。最好改用 raise_application_error。顺便说一句,即使来自同一个会话,自主事务也看不到未提交的数据!删除并重新创建触发器会隐式提交您的事务。

标签: sql oracle plsql triggers count


【解决方案1】:

几点。首先,您滥用了自主事务编译指示。它适用于您需要独立于主事务提交或回滚的单独事务。您正在使用它来回滚主事务——如果没有错误,您永远不会提交。

还有人提到的那些“不可预见的后果”?其中之一是您的计数始终返回 0。因此请删除编译指示,因为它被滥用,因此计数将返回正确的值。

另一件事是触发器中没有提交或回滚。引发错误并让控制代码做它需要做的事情。我知道回滚是因为编译指示。只是不要忘记在删除 pragma 时删除它们。

以下触发器适用于我:

CREATE OR REPLACE TRIGGER trg_mytable_biu 
BEFORE INSERT OR UPDATE ON mytable 
FOR EACH ROW 
WHEN (NEW.TYPEB = 'Bert') -- Don't even execute unless this is Bert
DECLARE
    L_COUNT NUMBER;
BEGIN
    SELECT  COUNT(*) INTO L_COUNT
    FROM    MYTABLE 
    WHERE   ARTICLE = :NEW.ARTICLE
        AND TYPEB = :NEW.TYPEB;

    IF L_COUNT > 0  THEN
        RAISE_APPLICATION_ERROR( -20001, 'Bert already exists!' );
    ELSIF :NEW.STOCK_COUNT > 1 THEN
        RAISE_APPLICATION_ERROR( -20001, 'Can''t insert more than one Bert!' );
    END IF;
END;

但是,表上的触发器单独访问该表并不是一个好主意。通常系统甚至不会允许它 - 如果更改为“之后”,则此触发器根本不会执行。如果允许执行,则永远无法确定所获得的结果——正如您已经发现的那样。实际上,我对上面的触发器起作用感到有些惊讶。在真正的数据库中使用它会感到不安。

当触发器必须访问目标表时,最好的选择是将表隐藏在视图后面并在视图上编写“代替”触发器。 那个触发器可以随心所欲地访问表。

【讨论】:

  • 只是一个后续问题:为什么我们需要使用“instead-of”触发器?
  • 因为正常的前/后触发器是正在执行的 DML 语句的一部分。这就是他们不能或不应该访问底层表的原因——它位于导致触发器执行的插入、更新或删除语句的中间。 “代替”触发器是“执行此代码而不是 DML 语句”操作。所以底层表不参与插入、更新或删除。触发代码本身必须启动 DML,并且不必是相同的 DML。代替删除触发器可以执行“update table set is_delete = 'Y'”进行软删除。
【解决方案2】:

您需要执行 AFTER 触发器,而不是 BEFORE 触发器。执行 count(*) "BEFORE" 插入会导致零行,因为尚未插入数据。

【讨论】:

    猜你喜欢
    • 2013-01-30
    • 2013-04-03
    • 1970-01-01
    • 1970-01-01
    • 2021-03-05
    • 1970-01-01
    • 1970-01-01
    • 2016-12-01
    • 2018-11-11
    相关资源
    最近更新 更多