Accessing old values without :OLD and in a trigger - oracle

As discussed here, I'm unable to use :OLD and :NEW on columns with collation other than USING_NLS_COMP. I'm trying to find a way around this but haven't been successful so far.
What I can do is access the new rows by using a compound trigger, saving IDs of changed rows in AFTER EACH ROW and then looping over these and selecting rows in AFTER STATEMENT. The same doesn't apply for old values unfortunately.
Here is an example of what isn't working:
CREATE TABLE example_table (
id VARCHAR2(10),
name NVARCHAR2(100)
);
CREATE TABLE log_table (
id VARCHAR2(10),
new_name NVARCHAR2(100),
old_name NVARCHAR2(100)
);
CREATE OR REPLACE TRIGGER example_trigger
AFTER UPDATE ON example_table
FOR EACH ROW BEGIN
INSERT INTO log_table VALUES(:old.id, :new.name, :old.name);
END;
INSERT INTO example_table VALUES('01', 'Daniel');
-- this works as expected
UPDATE example_table SET name = ' John' WHERE id = '01';
SELECT * FROM log_table;
DROP TABLE example_table;
CREATE TABLE example_table (
id VARCHAR2(10),
-- this is the problematic part
name NVARCHAR2(100) COLLATE XCZECH_PUNCTUATION_CI
);
INSERT INTO example_table VALUES('01', 'Daniel');
-- here nothing is inserted into log_example, if you try to
-- recompile the trigger you'll get error PLS-00049
UPDATE example_table SET name = ' John' WHERE id = '01';
SELECT * FROM log_table;
DROP TABLE example_table;
DROP TABLE log_table;
DROP TRIGGER example_trigger;
If somebody has a solution for this it would be most appreciated.

Related

Invalid identifier in trigger in my Oracle database

I want to add an entry to the table, created a trigger so that it generates an id. But I get an error.
create or replace TRIGGER EMPLOYEE_ON_INSERT
BEFORE INSERT ON "EMPLOYEE"
FOR EACH ROW
BEGIN
SELECT next_id("SEQ$SYS_BUYER_SEQUENCE".nextval, 'EMPLOYEE')
INTO :NEW."ID"
FROM dual;
END;
INSERT INTO EMPLOYEE (ID, FIRST_NAME, LAST_NAME)
SELECT KPFUDB.EMPLOYEE_ON_INSERT(), 'Ivanov', ' Ivan'
FROM dual;
You want:
create or replace TRIGGER EMPLOYEE_ON_INSERT
BEFORE INSERT ON EMPLOYEE
FOR EACH ROW
BEGIN
:NEW.ID := SEQ$SYS_BUYER_SEQUENCE.NEXTVAL;
END;
/
then do not include the ID column in the INSERT as the trigger will overwrite any value you provide (or the default if you do not provide a value):
INSERT INTO EMPLOYEE (FIRST_NAME, LAST_NAME)
SELECT 'Ivanov',' Ivan' from dual;
Or, from Oracle 12, you do not need a trigger and can use an IDENTITY column:
CREATE TABLE employee(
id NUMBER
GENERATED ALWAYS AS IDENTITY
PRIMARY KEY,
first_name VARCHAR2(20),
last_name VARCHAR2(20)
);
And a similar INSERT statement.
db<>fiddle here

How to maintain stock_on_quantity on item_master table whenever an item is purchased or sold?

Minimum 3 stocks in item_master table should be maintained.
Table: item_master
create table item_master
(
item_no number(5) primary key,
name varchar(10),
stock_on_hand number(5) default 0
);
Table: item_detail
create table item_detail
(
item_no number(5) references item_master,
operation varchar(10) check(operation in('Sales','Purchase')),
quantity number(5)
);
insert into item_master values(101,'Chair',5);
insert into item_master values(102,'Sofa',4);
insert into item_master values(103,'Table',6);
I have written this trigger for maintaining minimum 3 stocks.
create or replace trigger tristk
before insert or update or delete on item_master
for each row
declare
stk number(5);
begin
select stock_on_hand into stk from item_master where item_no = :new.item_no;
if(stk<=3) then
raise_application_error(-20000,'Not enough stock');
end if;
end;
/
I have written Another trigger to automatically update item_master whenever I insert or update or delete in item_detail
CREATE OR REPLACE TRIGGER triitem AFTER
INSERT OR UPDATE OR DELETE ON item_detail
FOR EACH ROW
BEGIN
UPDATE item_master
SET
stock_on_hand =
CASE :new.operation
WHEN 'Sales' THEN stock_on_hand -:new.quantity
WHEN 'Purchase' THEN stock_on_hand +:new.quantity
END
WHERE item_no =:new.item_no;
END;
/
The error with your second trigger is that you are not supposed to run queries on the trigger owner as database has not seen the changes done by your dml that raised the trigger.
I would recommend you to avoid Triggers to implement business logic altogether if possible.
The solution to your problem is to simply refer to all the columns using the :NEW prefix. I have also simplified your update statement.
CREATE OR REPLACE TRIGGER triitem AFTER
INSERT OR UPDATE OR DELETE ON item_detail
FOR EACH ROW
BEGIN
UPDATE item_master
SET
stock_on_hand =
CASE :new.operation
WHEN 'Sales' THEN stock_on_hand -:new.quantity
WHEN 'Purchase' THEN stock_on_hand +:new.quantity
END
WHERE item_no =:new.item_no;
END;
/

Need to populate table with Foreign and Primary key

I need to create three tables from all_tab_col system table such that schema details are in one schema_detail table, table details are in table_detail table and column details are in col_table. These three tables are to be populated simultaneously through a stored procedure, with PK(generated using SEQUENCE) in schema_detail is FK in table_detail table and PK(generated using SEQUENCE) in table_detail is FK in col_detail table.
SQL is a set based language, so I would be tempted to solve your task with three set bases steps.
Some mock up tables (just add columns for the details you are interested in):
CREATE TABLE schema_detail (
schema_id NUMBER GENERATED ALWAYS AS IDENTITY NOT NULL,
schema_name VARCHAR2(128 BYTE) NOT NULL,
CONSTRAINT schema_detail_pk PRIMARY KEY (schema_id)
);
CREATE TABLE table_detail (
schema_id NUMBER,
table_id NUMBER GENERATED ALWAYS AS IDENTITY NOT NULL,
table_name VARCHAR2(128 BYTE) NOT NULL,
CONSTRAINT table_detail_pk PRIMARY KEY (table_id),
CONSTRAINT table_detail_fk FOREIGN KEY (schema_id)
REFERENCES schema_detail(schema_id)
ON DELETE CASCADE
);
CREATE INDEX table_detail_schema_idx ON table_detail(schema_id);
CREATE INDEX table_detail_name_idx ON table_detail(table_name);
CREATE TABLE col_detail (
table_id NUMBER,
col_id NUMBER GENERATED ALWAYS AS IDENTITY NOT NULL,
col_name VARCHAR2(128 BYTE) NOT NULL,
CONSTRAINT col_detail_pk PRIMARY KEY (col_id),
CONSTRAINT col_detail_fk FOREIGN KEY (table_id)
REFERENCES table_detail(table_id)
ON DELETE CASCADE
);
CREATE INDEX col_detail ON col_detail(table_id);
I'd fill the table schema_detail first. PK is generated automatically:
INSERT INTO schema_detail(schema_name)
SELECT DISTINCT c.owner FROM all_tab_columns c ORDER BY owner;
SCHEMA_ID SCHEMA_NAME
1 APPQOSSYS
2 AUDSYS
3 CTXSYS
...
Next, I'd fill the tables. The schema_id needs to be looked up the the schema_detail table. Again, we let the PKs be generated automatically:
INSERT INTO table_detail(schema_id, table_name)
SELECT DISTINCT s.schema_id, c.table_name
FROM all_tab_columns c
JOIN schema_detail s ON c.owner = s.schema_name
ORDER BY table_name;
SCHEMA_ID TABLE_ID TABLE_NAME
1 8403 WLM_CLASSIFIER_PLAN
1 8404 WLM_FEATURE_USAGE
1 8405 WLM_METRICS_STREAM
...
And last, I'd fill the columns:
INSERT INTO col_detail(table_id, col_name)
SELECT DISTINCT t.table_id, c.column_name
FROM all_tab_columns c
JOIN table_detail t ON c.table_name = t.table_name
JOIN schema_detail s ON c.owner = s.schema_name
ORDER BY s.schema_id, t.table_id, c.column_name;
Does this solve your question or do you need a PL/SQL procedure?
In case you insist on a PL/SQL stored procedure, I would code it along the lines of:
CREATE OR REPLACE PROCEDURE myproc IS
schema_id schema_detail.schema_id%type;
table_id table_detail.table_id%type;
FUNCTION lookup_insert_schema (p_schema_name VARCHAR2)
RETURN NUMBER
IS
sid schema_detail.schema_id%type;
BEGIN
BEGIN
SELECT schema_id INTO sid
FROM schema_detail
WHERE schema_name = p_schema_name;
EXCEPTION WHEN NO_DATA_FOUND THEN
INSERT INTO schema_detail (schema_name)
VALUES (p_schema_name)
RETURNING schema_id INTO sid;
END;
RETURN sid;
END lookup_insert_schema;
-- lookup p_table_name in table table_detail, if not found, insert it
FUNCTION lookup_insert_table (p_schema_id NUMBER, p_table_name VARCHAR)
RETURN NUMBER
IS
tid table_detail.table_id%type;
BEGIN
BEGIN
SELECT table_id INTO tid
FROM table_detail
WHERE schema_id = p_schema_id
AND table_name = p_table_name;
EXCEPTION WHEN NO_DATA_FOUND THEN
INSERT INTO table_detail (schema_id, table_name)
VALUES (p_schema_id, p_table_name)
RETURNING table_id INTO tid;
END;
RETURN tid;
END lookup_insert_table;
BEGIN
FOR r IN (SELECT * FROM all_tab_columns)
LOOP
schema_id := lookup_insert_schema(r.owner);
table_id := lookup_insert_table(schema_id, r.table_name);
INSERT INTO col_detail (table_id, col_name)
VALUES (table_id, r.column_name);
END LOOP;
END myproc;
/

Is possible use a not updated column in trigger body?

I'm coding a DML Trigger for insert update rows in a new table. Trigger will fired only when one single column be updated but... Is possible use into trigger body other column outside UPDATE OF one_single_column clause?
AUDIT TABLE:
CREATE TABLE employees_salary_history(
user_name VARCHAR2(45) NOT NULL,
time_stamp date DEFAULT (sysdate),
employee_id NUMBER(6),
old_salary NUMBER(8, 2),
new_salary NUMBER(8, 2),
percente_raise_salary NUMBER(8, 2));
DML TRIGGER:
CREATE OR REPLACE TRIGGER update_salary_trg
AFTER
UPDATE
OF salary
ON employees23
FOR EACH ROW
DECLARE
v_user VARCHAR2(25);
BEGIN
SELECT user
INTO v_user
FROM dual;
INSERT INTO employees_salary_history
VALUES(
v_user,
sysdate,
employee_id, -- I would like add employee_id about employee that was updated,
:OLD.salary,
:NEW.salary,
TRUNC((:NEW.salary - :OLD.salary) / :OLD.salary * 100, 3)
);
END update_salary_trg;
Trigger works right when I omite "employee_id" column but I would like add employee_id as a reference about the salary updated. When I add employee_id column I receive the following error:
Errors: check compiler log
Errores para TRIGGER HR.UPDATE_SALARY_TRG:
LINE/COL ERROR
-------- ----------------------------------
8/3 PL/SQL: SQL Statement ignored
8/15 PL/SQL: ORA-00913: too many values
This is because the trigger cant reference the employee_id field. In your case New and OLD will be same . So just replace employee_id with either :OLD.employee_id or :NEW. employee_id as shown below. Hope it helps.
CREATE OR REPLACE TRIGGER update_salary_trg
AFTER
UPDATE
OF salary
ON employees23
FOR EACH ROW
DECLARE
v_user VARCHAR2(25);
BEGIN
SELECT user
INTO v_user
FROM dual;
INSERT INTO employees_salary_history --employees_salary_history
VALUES(
v_user,
sysdate,
:NEW.employee_id, -- I would like add employee_id about employee that was updated,
:OLD.salary,
:NEW.salary,
TRUNC((:NEW.salary - :OLD.salary) / :OLD.salary * 100, 3)
);
END update_salary_trg;
Yes it is possible to use any column of employees23-table in the trigger.
If the column was not changed then the values of :old and :new pseudorecords are the same.
So in your case just use :new.employee_id.
Next I recommend you to RTFM: PL/SQL Triggers. It also explains in the details when the pseudorecords are available and what values they hold.

How get another column value from the same row if we update any column of that row in oracle trigger?

TABLE NAME : TEST
COLUMNS : ID,NAME
I am using ORACLE database.
I have written one trigger after update.
If I update name column value then I want to get ID of that updated name.
Please give me any suggestion.
Thanks....
you can use :OLD check the below example
CREATE OR REPLACE TRIGGER EX_TRIGGER
AFTER UPDATE ON TAB1
BEGIN
SET VAR = :OLD.ID
END;
Oracle Setup:
CREATE TABLE table_name ( id INT, name VARCHAR2(20) );
CREATE TABLE table_name_log( prev_id INT, id INT, update_date DATE );
CREATE TRIGGER log_id
AFTER UPDATE
ON table_name
FOR EACH ROW
BEGIN
IF :old.name <> :new.name THEN
INSERT INTO table_name_log (
prev_id, id, update_date
) VALUES (
:old.id, :new.id, SYSDATE
);
END IF;
END;
/
INSERT INTO table_name
SELECT 1, 'Alf' FROM DUAL UNION ALL
SELECT 2, 'Ben' FROM DUAL UNION ALL
SELECT 3, 'Carl' FROM DUAL;
Query:
UPDATE table_name SET name = 'Ann' WHERE id = 1;
SELECT * FROM table_name_log;
Output
PREV_ID ID UPDATE_DATE
------- -- -------------------
1 1 2016-06-02 09:56:23

Resources