Showing posts with label Triggers. Show all posts
Showing posts with label Triggers. Show all posts

Friday, February 20, 2026

How to find triggers in a PostgreSQL schema

Execute the SQL below as a user with ownership to the schema. To verify that you have schema privileges, execute:
SELECT has_schema_privilege('myuser', 'myschema', 'USAGE');
If the reply is
has_schema_privilege
----------------------
 t
You can login
psql -h localhost -d mydb -U myuser
and execute
\x
Expanded display is on.

SELECT
    'TRIGGER' AS object_type,
    t.tgname AS trigger_name,
    n.nspname AS schema_name,
    c.relname AS table_name,

    -- Decode tgenabled letters
    CASE t.tgenabled
        WHEN 'O' THEN 'ENABLE'
        WHEN 'A' THEN 'ENABLE ALWAYS'
        WHEN 'R' THEN 'ENABLE REPLICA'
        WHEN 'D' THEN 'DISABLED'
        ELSE t.tgenabled
    END AS enabled_state,

    -- Trigger timing from tgtype
    CASE
        WHEN (t.tgtype & 2)  <> 0 THEN 'BEFORE'
        WHEN (t.tgtype & 64) <> 0 THEN 'INSTEAD OF'
        ELSE 'AFTER'
    END AS timing,

    -- Events
    (SELECT string_agg(event, ', ')
     FROM (
            SELECT unnest(
                ARRAY[
                    CASE WHEN (t.tgtype & 4)  <> 0 THEN 'INSERT'   END,
                    CASE WHEN (t.tgtype & 8)  <> 0 THEN 'DELETE'   END,
                    CASE WHEN (t.tgtype & 16) <> 0 THEN 'UPDATE'   END,
                    CASE WHEN (t.tgtype & 32) <> 0 THEN 'TRUNCATE' END
                ]
            ) AS event
        ) ev
    ) AS events,

    pron.nspname || '.' || p.proname AS trigger_function

FROM pg_trigger t
JOIN pg_class c        ON c.oid = t.tgrelid
JOIN pg_namespace n    ON n.oid = c.relnamespace
JOIN pg_proc p         ON p.oid = t.tgfoid
JOIN pg_namespace pron ON pron.oid = p.pronamespace

WHERE n.nspname = current_schema()
  AND NOT t.tgisinternal
  AND t.tgconstraint = 0
  AND c.relkind <> 'p'
ORDER BY trigger_name;
Which will list your triggers:
-[ RECORD 1 ]----+------------------------------------------------------------------
object_type      | TRIGGER
trigger_name     | mytrg1
schema_name      | myschema
table_name       | mytable1
enabled_state    | E
timing           | BEFORE
events           | UPDATE
trigger_function | myschema.trigger_fct_upd_mytable1
-[ RECORD 2 ]----+------------------------------------------------------------------
object_type      | TRIGGER
trigger_name     | mytrg2
schema_name      | myschema
table_name       | mytable2
enabled_state    | E
timing           | AFTER
events           | DELETE
trigger_function | myschema.trg_close_fct

Friday, February 1, 2019

How to create a trigger that will add newly created tables to a role


A commonly asked for functionality in my day-to-day work is READ-ONLY access for users to various tables in the database. This is easy to solve by creating a role, and granting SELECT on the tables to that role.

Every time a new table is created, the role must be updated. This will for most of the time be impossible to administer, and should be automated by creating a trigger on the schema owning the tables/views.

Here is how:

create a new role:
create role my_ro_role;

Grant execute permissions on dbms_jobs to scott:
grant create job to scott;

Note that if you do not have the password for the schema that should own the trigger, you cannot use the SYS user to create it for them. You need SYSTEM or another DBA user to avoid the error
ORA-30510: system triggers cannot be defined on the schema of SYS user

Create the trigger. In this example, I am using the system user to accomplish this:
conn system/password
create or replace trigger scott.add_to_ro_role
after CREATE on schema
declare
 l_str varchar2(255);
 l_job number;
begin
 if ( ora_dict_obj_type = 'TABLE' ) then
  l_str := 'execute immediate "grant select on ' || ora_dict_obj_name || ' to my_ro_role";';
  dbms_job.submit( l_job, replace(l_str,'"','''') );
 end if;
end;
/

Every time a new table is created in the SCOTT schema, SELECT on the same table will be granted to the role.
The role can then be granted to individual users.

Thanks to Tron Malmø-Lund for the idea and the code to implement it! ;-)

Friday, April 7, 2017

How to create a logon trigger

In this particular example, I create a logon trigger that sets one of many available NLS session parameters, as well as sets an undocumentet parameter that I was recommended to set by Oracle Support services:

CREATE OR REPLACE TRIGGER logon_optimizer 
after logon on database
begin 
   if user in ('SCOTT','JACK','BOB')
   then 
      execute immediate 'alter session set NLS_LANGUAGE="GERMAN"';
      execute immediate 'alter session set "_optimizer_unnest_disjunctive_subq"= FALSE';
   end if; 
end;
/

Test to verify that it Works:

Connect scott/tiger
set lines 200
col parameter format a30
col value format a40
select * from nls_session_parameters;


PARAMETER                      VALUE
------------------------------ -----------
NLS_LANGUAGE                   GERMAN
NLS_TERRITORY                  NORWAY

Now generate an error:
select * from xxx
              *
ERROR at line 1:
ORA-00942: Tabelle oder View nicht vorhanden

Wednesday, March 29, 2017

How to create a startup-trigger

In this example, I needed to write a simple trigger that opens the wallet upon startup of the instance:
create or replace trigger
   open_wallet
after startup on database
begin
   execute immediate 'ALTER SYSTEM SET ENCRYPTION WALLET OPEN IDENTIFIED BY "mysecretpassword"';
end;
/

Wednesday, July 6, 2016

A workaround for ORA-02287: sequence number not allowed here when using GROUP BY in a subquery

When you use GROUP BY in the subquery of an INSERT statement, Oracle will throw

ORA-02287: sequence number not allowed here 

if you try to reference a sequence's NEXTVAL pseudocolumn directly in the insert statement, like this:
INSERT INTO mytable
        (
        entry_id,   
        order_date, 
        sum(quantity)
        )
        MYSEQ.NEXTVAL, <-- INCORRECT
        SELECT  
            TRUNC(SYSDATE),
            SUM(quantity)
        FROM    orders
        WHERE   
        GROUP BY order_date
        ORDER BY 2 ASC, 3 DESC;
Solution is to create a trigger that will fire on INSERT statements, before a new row is inserted. Note that I am overriding the default correlation name NEW with NewID, by using the REFERENCING clause of the "CREATE TRIGGER" command:
CREATE OR REPLACE TRIGGER MYTRG
BEFORE INSERT ON mytable REFERENCING NEW as NewID
    FOR EACH ROW
        BEGIN
            SELECT myseq.NEXTVAL
            INTO :NewID.ENTRY_ID
            FROM DUAL;
        END;
Change your insert statement slightly:
INSERT INTO mytable
        (
        order_date, 
        sum(quantity)
        )
        SELECT  
            TRUNC(SYSDATE),
            SUM(quantity)
        FROM    orders
        WHERE   
        GROUP BY order_date
        ORDER BY 2 ASC, 3 DESC;

Monday, January 18, 2016

How to use dbms_metadata to generate DDL for a trigger

set long 10000
set heading off
set trimspool on
set lines 300
set longchunksize 300
spool mytrigger.sql
exec dbms_metadata.set_Transform_param(dbms_metadata.session_transform,'SQLTERMINATOR',true);

select dbms_metadata.get_ddl('TRIGGER','MYTRIGGER','SCOTT')  FROM DUAL;

Friday, November 29, 2013

How to create a sequence and a trigger that will emulate "autonumber" in other RDBMS systems

If you have been working with other RDBMS systems, such as SQL Server from Microsoft, you may be used to a datatype called AUTONUMBER, which will automatically insert a freshly generated number and create a unique value in your table. Perfect for generation of primary key values.
In oracle, no such data type exist. To achieve the same functionality, you need to create two additional objects: a sequence and a trigger. The trigger will fire on certain events. The sequence will generate a new number. Here is a simple example on how to set it up.
CREATE TABLE ADDRESSES
(
 ADDRESS_ID NUMBER,
 EMLOYEE_ID NUMBER,
 STREET_ADDRESS VARCHAR2(40),
 STREET_ADDRESS2 VARCHAR2(40),
 CITY VARCHAR2 (30),
 STATE VARCHAR2(2),
 ZIP VARCHAR2(5),
 ZIP_PLUS VARCHAR2(4),
 COUNTRY VARCHAR2(20),
 CONSTRAINT PK_ADDRESSES PRIMARY KEY (ADDRESS_ID)-- "OUT-OF-LINE" CONSTRAINT CREATION
);

CREATE SEQUENCE ADDRESSES_SEQ
 START WITH 1
 INCREMENT BY 1
 MINVALUE 1
 MAXVALUE 1000000
 NOCACHE
 NOCYCLE
 NOORDER;

CREATE OR REPLACE TRIGGER ADDRESSES_TRG
BEFORE INSERT
ON ADDRESSES
FOR EACH ROW
WHEN (NEW.ADDRESS_ID IS NULL)
BEGIN
 SELECT ADDRESSES_SEQ.NEXTVAL
 INTO   :NEW.ADDRESS_ID
 FROM   DUAL;
END;
/

Wednesday, October 30, 2013

How to create a DDL trigger in a schema



CREATE TABLE AUDIT_DDL (
 d date,
 OSUSER varchar2(255),
 CURRENT_USER varchar2(255),
 HOST varchar2(255),
 TERMINAL varchar2(255),
 owner varchar2(30),
 type varchar2(30),
 name varchar2(30),
 sysevent varchar2(30));


connect uid/pwd
CREATE OR REPLACE TRIGGER audit_ddl_trg after ddl on schema
BEGIN
IF(ora_sysevent='TRUNCATE') THEN
 null; -- If we do not care about truncate
ELSE
 INSERT INTO audit_ddl(d, osuser,current_user,host,terminal,owner,type,name,sysevent)
 values(
  sysdate,
  sys_context('USERENV','OS_USER') ,
  sys_context('USERENV','CURRENT_USER') ,
  sys_context('USERENV','HOST') , 
  sys_context('USERENV','TERMINAL') ,
  ora_dict_obj_owner,
  ora_dict_obj_type,
  ora_dict_obj_name,
  ora_sysevent
  );
 END IF;
END;
/


Source: Don Burleson http://www.dba-oracle.com/t_ddl_triggers.htm