How do you reference APEX Application Items in Trigger - oracle

I am creating a BEFORE DELETE trigger to insert records into an archive table that are deleted through an APEX form on another table. I want to update two columns "UPDATED_BY" and "UPDATED"_DATE" based on the SYSDATE and :APP_USER (APEX Application Item). I'm getting an error related to the :APP_USER (PLS-00049: bad bind variable 'APP_USER'). Is there a specific syntax I have to use to reference APEX app items when creating the trigger?
CREATE OR REPLACE TRIGGER "ARCHIVE_DELETED_RECORDS"
BEFORE
delete on "RECORD_TABLE"
for each row
begin
INSERT INTO DELETED_RECORD_TABLE (
STAGING_ID,
SOURCE_FILE_ID,
DELETION_DATE,
DELETED_BY,
DELETED_FROM)
SELECT
STAGING_ID,
SOURCE_FILE_ID,
SYSDATE,
:APP_USER,
'Record Table 1'
FROM RECORD_TABLE
end;
/
ALTER TRIGGER "ARCHIVE_DELETED_RECORDS" ENABLE;

You can use the V() function to refer directly to APEX bind variables like :APP_USER in a database trigger, or really in any PL/SQL outside of APEX. APEX also generally uses package state to store global variables which need to be accessed externally. See here for some discussion.
In your case, instead of :APP_USER, you could use either V('APP_USER') or APEX_APPLICATION.G_USER. Documentation is here.
Note that if records are deleted from your table outside of APEX (for example, by a DBA running delete from RECORD_TABLE), the DELETED_BY field will be null. So you might want something like:
COALESCE(V('APP_USER'), SYS_CONTEXT ('USERENV', 'OS_USER'), USER)
To prefer the APEX user if that's available, or the OS user if that's available, or finally the current schema user if nothing else is available.

Related

Trigger on a table that uses Merge Insert Update as Incremental strategy

Trigger under question is for Table which has ETL in ODI but user also has option to edit certain **columns **
if they want to adjust them. This is done using APEX
Trigger is used to change two columns : Changed_by and Change_on.
Both indicating Changes done on APEX PAGE only.
The issue comes when ODI load is run and is MERGE INSERT UPDATE , Trigger thinks its updating and changes the above two columns to "NULL" as its a manual update done by ODI and not on APEX.
Solution
For each Editable Column, there should be a logic which checks NEW: <> :OLD, but as i have 15 columns need to write a lot of code.
Are there others way to achieve this ?
create or replace TRIGGER DW.TRG BEFORE
UPDATE ON DW.TABLE
REFERENCING
NEW AS new
OLD AS old
FOR EACH ROW
BEGIN
IF updating THEN
SELECT
SYSDATE,
v('APP_USER')
INTO
:new.changed_on_dt,
:new.changed_by
FROM
dual;
END IF;
END;
Check if an apex session exists for the current database session and only execute when it is the case.
create or replace TRIGGER DW.TRG BEFORE
UPDATE ON DW.TABLE
REFERENCING
NEW AS new
OLD AS old
FOR EACH ROW
BEGIN
IF SYS_CONTEXT('APEX$SESSION','APP_SESSION') IS NOT NULL AND updating THEN
:new.changed_on_dt := SYSDATE;
:new.changed_by := SYS_CONTEXT('APEX$SESSION','APP_USER');
END IF;
END;
Notes
avoid the SELECT FROM DUAL, you can just assign the values in the trigger.
The "V" functions are pretty slow. For a while there have been sys_context settings that store the session and user data. Those are a lot faster than a function call to the "V" function.
You could make it so that it never overwrites a non-null value with a null one:
IF v('APP_USER') IS NOT NULL
THEN
:new.changed_by := v('APP_USER');
:new.changed_on_dt := SYSDATE;
END IF;

Oracle_Trigger: Auto Update Of a Column Based Upon INSERT/UPDATE

This might appear a simple query for most of you, but I am a beginner in Oracle DB.
A table has been created with below script-
CREATE TABLE PLAN_TABLE
(
PL_ID DECIMAL(10,0) PRIMARY KEY NOT NULL
,PL_NAME VARCHAR2(300) DEFAULT NULL
,UPDATED_TS TIMESTAMP DEFAULT SYSDATE NOT NULL
,DELETE_FLAG DECIMAL(10,0) DEFAULT 0 NOT NULL
);
The requirement is to have SYSDATE for UPDATED_TS for any new record inserted into the table and also in case when the DELETE_FLAG is updated to 1. Can it be done by trigger?
The below trigger was created-
CREATE OR REPLACE TRIGGER PT_BEFORE_INSERT_TR
BEFORE INSERT ON PLAN_TABLE
FOR EACH ROW
BEGIN
SELECT SYSDATE INTO :new.UPDATED_TS FROM DUAL;
dbms_output.put_line('Inserted');
END;
/
Below error was encountered while inserting record into the table-
error: ORA-04091: table I60_SCH04.PLAN_TABLE is mutating, trigger/function may not see it
Can you please help in letting me know that where am I committing the mistake? Is there any better way to achieve the requirement based upon INSERT/UPDATE?
The actual error you get is due to the fact that you try to select from a table that you actually are changing. To prevent the issue there are a couple of methods, but in you case things are really simple.
SYSDATE is a function, that you could call directly inside PL/SQL block (which a trigger actually is) and use the value returned to update the set the column value
CREATE OR REPLACE TRIGGER PT_BEFORE_INSERT_TR
BEFORE INSERT ON PLAN_TABLE
FOR EACH ROW
BEGIN
:new.UPDATED_TS := sysdate;
dbms_output.put_line('Inserted');
END;
/
OK, this covers the insert part.
For updating - once again, many options. One could be - change your trigger to BEFORE INSERT OR UPDATE ON PLAN_TABLE.
In this case whenever you issue update or insert - this trigger is fired for each row and updates the date column accordingly.
And of course you could use particular checks available in triggers, something like
IF INSERTING OR UPDATING('DELETE_FLAG') THEN
...
END IF;
and code in the logic you need.

How to create Oracle Trigger to record current SQL

I am currently doing a homework assignment and am getting stuck at a point.
I want to create a trigger for auditing, and Yes I do know FGA, but that's not what was asked.
My trigger needs to record the current SQL for auditing. This is what I have now.
Imagine I have my table already created to store the inserting value.
CREATE OR REPLACE TRIGGER ALL_SQL_AUDIT
AFTER UPDATE ON SCOTT.EMP
FOR EACH ROW
BEGIN
INSERT INTO ALL_SQL_AUDIT(TABLE_NAME, SQL_TEXT, SQL_TYPE, CHANGE_DATE, DB_USERNAME, OS_USERNAME)
SELECT 'SCOTT.EMP',
SYS_CONTEXT('USERENV','CURRENT_SQL'),
'UPDATE',
SYSDATE,
USER,
SYS_CONTEXT('USERENV','OS_USER')
FROM dual;
END;
/
Problem is SYS_CONTEXT('USERENV','CURRENT_SQL') never return anything, so does anyone know how to get the current DML that triggered this event?

Store the data of deleted record using trigger

I want to write a trigger which fires on deletion of a record from a table, and inserts a record in another table and uses the details of the record deleted.
Database : Oracle 10g
My trigger looked like this
CREATE or REPLACE TRIGGER myTrigger
AFTER DELETE
ON myTable
REFERENCING NEW AS old_tab
FOR EACH ROW
BEGIN
INSERT INTO ACTIVITYLOG values ('ADMIN',:old_tab.tabletID,'MIGRATION','ERROR','TEST','T','NIL',sysdate)
END;
here :old_tab.tabletID the tabletID is the column of the table myTable in which deletion is done.
I want to save the I and a log that it was deleted.
But when I try deleting a record I get the following error
Error code 4098, SQL state 42000: ORA-04098: trigger 'DB.MYTRIGGER' is
invalid and failed re-validation
P.S. Ran the trigger creation in NetBeans SQL Editor.
Here is the,
EDIT
STRUCTURE OF myTable (Table deletion occurs)
tabletID varchar2(15) PRIMARY KEY
tabletName varchar2(100)
STRUCTURE OF ACTIVITYLOG
username varchar2(15)
tabletKey varchar2(15)
page_ref varchar2(100)
errors varchar2(100)
remarks varchar2(100)
operationcode char(2)
lastupdateip varchar2(20)
lastupdatedate date
Sorry don't have access to SQL PLUS EDITOR.
You should use the :OLD values rather than the :NEW values. The :NEW values in a DELETE trigger (whether BEFORE or AFTER) are blank. This makes sense, because if you think about it the record has logically ceased to exist at this point.
However that is not a source of compilation errors.
"still the same error shows up on deletion. "
I suppose we could spend all day guessing what's wrong so let's stop now. You can discover the compilation errors with this simple query:
select * from user_errors
where name = 'MYTRIGGER'
and type = 'TRIGGER'
"I changed the :NEW to :OLD, and added a semicolan and ran it on SQL
PLUS, and that did the trick"
For the benefit of future here is a version of the trigger which will compile and which will correctly write the required values:
CREATE or REPLACE TRIGGER myTrigger
AFTER DELETE
ON myTable
REFERENCING OLD AS old_tab
FOR EACH ROW
BEGIN
INSERT INTO ACTIVITYLOG values ('ADMIN',:old_tab.tabletID,'MIGRATION','ERROR','TEST','T','NIL',sysdate);
END;
/
The problem is this:
REFERENCING NEW AS old_tab
You've redefined the NEW values with the label "old_tab". This is somewhat like adding #define FALSE TRUE to the top of a program.
Add a semicolon after the insert statement
Because you're using an AFTER DELETE trigger, you only need to access the :OLD values, e.g.:
CREATE or REPLACE TRIGGER myTrigger
AFTER DELETE
ON myTable
FOR EACH ROW
BEGIN
INSERT INTO ACTIVITYLOG values ('ADMIN',:OLD.tabletID,'MIGRATION','ERROR','TEST','T','NIL',sysdate);
END;

Insert, delete ,update when using Stored Procedure component

We have an application written in Delphi 2010 which connects to SQL Server Database. Now we're in the process of migrating to Oracle. With SQL Server it was very easy to perform insert, update, delete right from a dbgrid connected to a Stored Procedure.
It's because stored procedures in SQL Server can easily act as a table so that you can do any operation on it, providing it returns the necessary columns within the resultset. Now with Oracle I don't know how do do it. I connect a DBGrid to a DataSource, dataset of which is a Stored Procedure object,but I can't edit the grid. Just Select is possible.
What do I have to do to to achieve this?I use UniDac component suite to connect to Oracle database.
Oracle does not support such functionality. IOW, in Oracle you cannot edit result set provided by a stored procedure or include stored procedure into INSERT INTO <name>, UPDATE <name> or DELETE FROM <name>.
While it is traditional for SQL Server developers to "always" use stored procedures (due to many reasons), it is not traditional for Oracle developers. But it is possible with Oracle too. Search for "REF CURSOR" to see how to fetch data using SP. And use normal or packaged (preferred) SP to post updates to a DB. These procedure will receive old / new field values through arguments.
I cannot say precisely about UniDAC, I can say about AnyDAC. But I will expect UniDAC has similar functionality. To use SP for posting updates you will need to use TXxxUpdateSQL component.
OK,here I'm answering the question though I can see very few are dealing with Delphi recently. Let's say we have a stored proc in Oracle database:
CREATE OR REPLACE PROCEDURE GET_EMPLOYEES
(V_CUR IN OUT SYS_REFCURSOR)
AS
BEGIN
OPEN V_CUR FOR SELECT * FROM EMPLOYEES;
END GET_EMPLOYEES;
Now, in Delphi you pick a stored procedure component (probably from ODAC or UniDac component suite).Set its StoredProcName GET_EMPLOYEES. Then you can add all the fields that the procedure returns in a cursor.If you run the application and activate the stored procedure you'll be able to see all the records. But if you try to insert, modify or delete anything you'll fail to do so. Now, there's a very tricky thing. If you check, you'll see that ReadOnly property of all fields are set to True. Even after you set them to False nothing will change in the real database, although you can edit the DBGrid.
So, we've come to the main part. How did the old Delphi-SQL Server partnership work so that you could do any operation right from a DBGrid? Well, we must understand that there's no magic. If it's SQL, then SQL has only one way of INSERTING,UPDATING and DELETING records-it's with the appropriate SQL statements.With Delphi-SQL Server there seems to be an implicit SQL statement that we never paid attention. But with Oracle, we have to provide our own statements for each operation.
If you use UniDac or ODAC then there's SQLInsert,SQLUpdate,SQLDelete properties in a StoredProc object.If you want to insert a record through DBGrid, then you should edit its SQLInsert property to
INSERT INTO EMPLOYEES VALUES(:EMPLOYEEID,:EMPLOYEENAME)
where variables following : are corresponding to te fields of the stored procedure.They're simply bind variales.When updating and deleting though you'll need some unique value to represent a specific record. Primary key is one option(maybe the only option as I haven't been able to figure out how to use ROWID for the same purpose).So the sql statements for UPDATE and DELETE would be
DELETE FROM EMPLOYEES WHERE EMPLOYEEID=:EMPLOYEEID
and
UPDATE EMPLOYEES SET EMPLOYEENAME=:EMPLOYEENAME WHERE EMPLOYEEID=:EMPLOYEEID
P.S. I just found a way to use ROWID for update and delete statements. In your stored procedure if you choose ROWID too and give it an alias then you can construct your UPDATE and DELETE Statements like such:
UPDATE EMPLOYEES SET EMPLOYEENAME=:EMPLOYEENAME,..... WHERE ROWID=:RECORD_ROWID
DELETE FROM EMPLOYEES WHERE ROWID=:RECORD_ROWID
In the preceding statements RECORD_ROWID is the fieldname returned from stored procedure as a result of aliasing ROWID. If you use :ROWID instead you'll get "ORA-01745: invalid host/bind variable name" error. This is because in a binding variable a colon cannot be followed by a reserved word. And ROWID is a reserved word.

Resources