Alguns pontos. Primeiro, você está usando mal o pragma de transação autônoma. Destina-se a transações separadas que você precisa confirmar ou reverter independentemente da transação principal. Você o está usando para reverter a transação principal - e você nunca confirma se não houver erro.
E aquelas "consequências imprevistas" que alguém mencionou? Uma delas é que sua contagem sempre retorna 0. Portanto, remova o pragma tanto porque está sendo mal utilizado e assim a contagem retornará um valor adequado.
Outra coisa é não ter commits ou rollbacks dentro de triggers. Gere um erro e deixe o código de controle fazer o que precisa fazer. Eu sei que as reversões foram por causa do pragma. Só não se esqueça de removê-los quando você remover o pragma.
O seguinte gatilho funciona para mim:
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;
No entanto, não é uma boa ideia que um gatilho em uma tabela acesse essa tabela separadamente. Normalmente, o sistema nem permite isso - esse gatilho não será executado se for alterado para "depois". Se for permitido executar, nunca se pode ter certeza dos resultados obtidos - como você já descobriu. Na verdade, estou um pouco surpreso que o gatilho acima funcione. Eu me sentiria desconfortável ao usá-lo em um banco de dados real.
A melhor opção quando um gatilho deve acessar a tabela de destino é ocultar a tabela atrás de uma visão e escrever um gatilho "em vez de" na visão. Isso o gatilho pode acessar a tabela o quanto quiser.