how to update :APP_USER detail in a field, apex5.0 - oracle-apex-5

Any idea how to add :APP_USER in an audit field?
Got a form when user input data in a order_comments field and click on apply changes then the audit field userid, datetime should be updated.
How to do that?

An age-old question.
The common method to capture the user for audit purposes is to use a before-row trigger on the tables.
For example, with these (common) audit fields:
CREATION_USER
CREATION_DT
LAST_UDPATE_USER
LAST_UPDATE_DT
You'd have a trigger on the table like this:
CREATE OR REPLACE TRIGGER MY_TABLE_RBIU_AUDIT
BEFORE INSERT OR UPDATE ON MY_TABLE
FOR EACH ROW
BEGIN
IF INSERTING THEN
:NEW.CREATION_USER := NVL(v('APP_USER'), USER);
:NEW.CREATION_DT := SYSDATE;
END IF;
IF UPDATING THEN
:NEW.LAST_UPDATE_USER := NVL(v('APP_USER'), USER);
:NEW.LAST_UPDATE_DT := SYSDATE;
END IF;
END;
/
If you don't want to use a trigger and are not using an API through which you do this, and for some weird reason want to do this within apex itself (hint: use the trigger or api), you can still opt to set the default value of the item to &APP_USER. (when using "Static Text")

Related

trigger to update specific column when insert/update happened in same table

I trying to write a trigger that will update a column when user insert or updates a row, within the same table.
Example:
insert into user(ID, F_NM, L_NM, EMAIL) values ('1', 'John','Doe','john.doe#market.org.com');
after the insert, i want to call: update user set ORG = 'market' where ID = '1'.
create or replace trigger user_change
after insert or update of EMAIL on USER
for each row
declare
NEW_ORG VARCHAR(10);
BEGIN
CASE
when :NEW.EMAIL like '$#market.org.com' then
NEW_ORG := 'market';
........
END CASE;
UPDATE USER set ORG = NEW_ORG where ID = :NEW.ID
END;
Calculating the new ORG work, but I can't get the update statement to work.
I get 'ORA-04091 table USER is mutating, trigger/funtion may not see it', figure its due to me inserting/updating the same record at same time. Tried adding 'pragma autonomous_transaction' and 'commit' to the trigger, the insert/update of fields works but the ORG does not get updated.
Also tried changing to INSTEAD OF INSERT OR UPDATE OF EMAIL but I keep getting 'ORA-04073 column list not valid for this trigger type'
create or replace trigger user_change
instead of insert or update of EMAIL on USER
while i get 'ORA-25002 cannot create instead of triggers on tables'
create or replace trigger user_change
instead of insert on USER
Why not simply turn the trigger to a before trigger, when you can set the value before it is written? This way, you don't need to run a new DML statement on the table, which avoid the "mutating" error.
create or replace trigger user_change
after insert or update of email on user
for each row
begin
if :new.email like '%#market.org.com' then
:new.org := 'market';
end if;
end;
Looks like your org column can be calculated virtual column. In this case it would be better to create user-defined deterministic pl/sql function that returns correct calculated value and add it to your table, for example:
Alter table t add org varchar2(30) generated always as (f_get_org(email))

Use v('APP_USER') as default value for column in Oracle Apex

I am trying to use v('APP_USER') as default value for a column. I get null when I use it in select like
select v('APP_USER') from dual;
But when I use it as default in column, like below, I am getting error.
create table test_table (col_1 varchar2(50) default NVL(v('APP_USER'), SYS_CONTEXT('USERENV','OS_USER')));
Error
create table test_table (col_1 varchar2(50) default NVL(v('APP_USER'), SYS_CONTEXT('USERENV','OS_USER')))
Error report -
ORA-04044: procedure, function, package, or type is not allowed here
04044. 00000 - "procedure, function, package, or type is not allowed here"
*Cause: A procedure, function, or package was specified in an
inappropriate place in a statement.
*Action: Make sure the name is correct or remove it.
Can anyone explain this or have a turnaround for this ??
There are other options than V('APP_USER'). Since Apex 5, the APP_USER is stored in the sys_context and that is a lot more performant than the V() function. It is available as SYS_CONTEXT('APEX$SESSION','APP_USER').
It also works as a default value for tables:
create table test_table
(col_1 VARCHAR2(100) DEFAULT SYS_CONTEXT('APEX$SESSION','APP_USER'));
Table TEST_TABLE created.
That being said, the best practice for audit columns is a trigger that populates the the 4 audit columns (as #Littlefoot suggested). Have a look at quicksql (under SQL Workshop > Utilities or on livesql.oracle.com). You can have it generate the triggers for you if you set "include Audit columns" and "Apex Enabled". An example of such a generated trigger is:
create or replace trigger employees_biu
before insert or update
on employees
for each row
begin
if inserting then
:new.created := sysdate;
:new.created_by := nvl(sys_context('APEX$SESSION','APP_USER'),user);
end if;
:new.updated := sysdate;
:new.updated_by := nvl(sys_context('APEX$SESSION','APP_USER'),user);
end employees_biu;
/
One option is to use a database trigger, e.g.
CREATE OR REPLACE TRIGGER trg_biu_test
BEFORE INSERT OR UPDATE ON test
FOR EACH ROW
BEGIN
:new.col1 := v ('APP_USER');
END;
/

Turn table attributes into a constant

I have been trying for a few hours to wrap my head around this, and I am not sure if it is possible. I am currently working on a few triggers for a database, and one of them states that in a specific table, only one attribute can be modified. The rest cant be changed after the original input.
Is this actually possible ?
In Oracle this is quite easy, I do this all the time. I have many tables with a CREATED_DT, CREATED_BY column that I only want set when the record is created. MODIFIED_DT, MODIFIED_BY are set with each change to the record. The trigger appears as follows:
CREATE OR REPLACE TRIGGER myschema.BILLING_SUMMARY_CHARGE_TRG
BEFORE INSERT OR UPDATE
ON myschema.billing_summary_charge
REFERENCING NEW AS new OLD AS old
FOR EACH ROW
BEGIN
:new.modified_dt := SYSDATE;
:new.modified_by := USER;
IF INSERTING
THEN
:new.i_charge_pk := billing_summary_charge_seq_pk.NEXTVAL;
:new.created_dt := :new.modified_dt;
:new.created_by := :new.modified_by;
END IF;
-- Don't allow creation columns to change on an update
IF UPDATING
THEN
:new.i_charge_pk := :old.i_charge_pk;
:new.created_dt := :old.created_dt;
:new.created_by := :old.created_by;
END IF;
END billing_summary_charge_trg;
In the above example, columns i_charge_pk, created_dt, and created_by are initialized when the record is inserted. They are assigned their old values when the record is updated, preventing any possible changes by the application.

Need to create a trigger

select id, status, name , status_change_dt from account;
whenever status changes to any other status status_change_dt should be updated to sysdate
create or replace
TRIGGER ADD_DT
after UPDATE of status ON account
for each row
BEGIN
:new.status_change_dt := sysdate ;
END;
I am getting error in creating this trigger. Thank you in advance.
You can't change the new value after the update.
Even error is pretty clear on this:
ORA-04084: cannot change NEW values for this trigger type
Use BEFORE instead of AFTER.
Try this:
create or replace
TRIGGER ADD_DT
before UPDATE of status ON account
for each row
BEGIN
:new.status_change_dt := sysdate ;
END;
EDIT:
If you have another table
account_dtl (ref_id references account(id), status_2);
And account table has one more column to keep track of status_2 changes, use this:
create or replace
TRIGGER ADD_DT_Status_2
before UPDATE of status_2 ON account_dtl
for each row
BEGIN
update account
set status2_change_dt := sysdate
where id = :new.ref_id;
END;
Although it would make more sense to keep track of status_2 changes in its own table i.e. account_dtl

Count(*) not working properly

I create the trigger A1 so that an article with a certain type, that is 'Bert' cannot be added more than once and it can have only 1 in the stock.
However, although i create the trigger, i can still add an article with the type 'Bert'. Somehow, the count returns '0' but when i run the same sql statement, it returns the correct number. It also starts counting properly if I drop the trigger and re-add it. Any ideas what might be going wrong?
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;
This is the insert statement I use:
INSERT INTO mytable VALUES('Chip',1,9,1,'Bert');
A couple of points. First, you are misusing the autonomous transaction pragma. It is meant for separate transactions you need to commit or rollback independently of the main transaction. You are using it to rollback the main transaction -- and you never commit if there is no error.
And those "unforeseen consequences" someone mentioned? One of them is that your count always returns 0. So remove the pragma both because it is being misused and so the count will return a proper value.
Another thing is don't have commits or rollbacks within triggers. Raise an error and let the controlling code do what it needs to do. I know the rollbacks were because of the pragma. Just don't forget to remove them when you remove the pragma.
The following trigger works for me:
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;
However, it's not a good idea for a trigger on a table to separately access that table. Usually the system won't even allow it -- this trigger won't execute at all if changed to "after". If it is allowed to execute, one can never be sure of the results obtained -- as you already found out. Actually, I'm a little surprised the trigger above works. I would feel uneasy using it in a real database.
The best option when a trigger must access the target table is to hide the table behind a view and write an "instead of" trigger on the view. That trigger can access the table all it wants.
You need to do an AFTER trigger, not a BEFORE trigger. Doing a count(*) "BEFORE" the insert occurs results in zero rows because the data hasn't been inserted yet.