Oracle statement trigger cant access table values - oracle

When trying to access table values inside my statement trigger, I get the following error "ID XXX must be declared".
Table
CREATE TABLE testTable(
id INT,
store INT,
amount NUMBER
);
Trigger
CREATE OR REPLACE TRIGGER test_trigger
AFTER INSERT OR UPDATE ON testTable
DECLARE
myID INT;
myStore INT;
BEGIN
myID := id;
myStore := store;
IF amount < 3 THEN
DBMS_OUTPUT.PUT_LINE('DANGER');
END IF;
END;
Error
PLS-00201: ID 'id' must be declared
PLS-00201: ID 'store' must be declared
PLS-00201: ID 'amount' must be declared
I am new to oracle pl/sql and triggers and have zero clue how to solve this.

needs :new for new or update record
Try this.
CREATE OR REPLACE TRIGGER test_trigger
AFTER INSERT OR UPDATE
ON testTable
DECLARE
myID INT;
myStore INT;
BEGIN
myID := :new.id;
myStore := :new.store;
IF :new.amount < 3
THEN
DBMS_OUTPUT.PUT_LINE ('DANGER');
END IF;
END;

Related

Procedure is compiled but when calling procedure and passing parameters I would receive error

I have created procedure for name change but when calling procedure I would receive error:
Error report:
ORA-06550: line 4, column 5:
PLS-00306: wrong number or types of arguments in call to 'PRODCHANGE_SP'
procedure:
create or replace
PROCEDURE PRODCHANGE_SP
(
P_PRODID IN bb_product.idproduct%TYPE
, P_NAME IN OUT bb_product.productname%TYPE
) AS
BEGIN
UPDATE bb_product
SET productname = p_name
WHERE idproduct = p_prodid;
COMMIT;
END PRODCHANGE_SP;
BLOCK, calling procedure to check if is working:
DECLARE
lv_pname_txt bb_product.productname%TYPE;
BEGIN
prodchange_sp(1, 'CapressoBar Model #352', lv_pname_txt);
DBMS_OUTPUT.PUT_LINE(lv_pname_txt);
END;
You need to fetch the old value before you update it. After update, overwrite the out variable with the old value.
create or replace PROCEDURE PRODCHANGE_SP
(
P_PRODID IN bb_product.idproduct%TYPE,
P_NAME IN OUT bb_product.productname%TYPE
) AS
P_NAME_old bb_product.productname%TYPE;
BEGIN
SELECT productname
INTO P_NAME_old
FROM bb_product
WHERE idproduct = p_prodid;
UPDATE bb_product
SET productname = p_name
WHERE idproduct = p_prodid;
COMMIT;
P_NAME := P_NAME_old;
END PRODCHANGE_SP;
You need to pass the value in the same variable as it is IN OUT. Rewrite the code to invoke procedure as below.
DECLARE
lv_pname_txt bb_product.productname%TYPE;
BEGIN
lv_pname_txt := 'CapressoBar Model #352';
prodchange_sp(1, lv_pname_txt);
DBMS_OUTPUT.PUT_LINE(lv_pname_txt);
END;

How do I display a nested table in Oracle - PL/SQL?

I have an assignment where I'm supposed to create a user-defined function that will return all employees after 1968. My code is as follows:
First I create a new object, called emp_dobs, to hold the employees' firstname, lastname, and date of birth, using the same data types as the original employee table:
CREATE OR REPLACE TYPE emp_dobs AS OBJECT (
emp_fname VARCHAR2(20),
emp_lname VARCHAR2(20),
emp_dob DATE
);
/
Then I create emp_dobs_nested as a table of emp_dobs:
CREATE OR REPLACE TYPE emp_dobs_nested AS TABLE OF emp_dobs;
/
Lastly, I create a function that's supposed to return an emp_dobs_nested table:
CREATE OR REPLACE FUNCTION get_emp_dobs RETURN emp_dobs_nested
AS
dobs emp_dobs_nested;
BEGIN
SELECT emp_dobs(firstname, lastname, birthdate) BULK COLLECT INTO dobs
FROM employee
WHERE birthdate < TO_DATE('01-JAN-1968', 'DD-MON-YYYY');
RETURN dobs;
END;
/
There is a weird quirk with compiling emp_dob_nested, however, where Oracle SQL Developer will display a new tab labeled "Output Variables - Log," and only show EMP_FNAME and EMP_LNAME. Despite that, everything compiles.
Now I want to test the function and display its results to prove that it works, but when I try this:
DECLARE
dobs emp_dobs_nested;
BEGIN
dobs := get_emp_dobs;
DBMS_OUTPUT.PUT_LINE(dobs);
END;
/
I get this error:
Error report -
ORA-06550: line 5, column 5:
PLS-00306: wrong number or types of arguments in call to 'PUT_LINE'
ORA-06550: line 5, column 5:
PL/SQL: Statement ignored
06550. 00000 - "line %s, column %s:\n%s"
*Cause: Usually a PL/SQL compilation error.
*Action:
What am I missing here? Thanks.
You cannot pass the whole collection to DBMS_OUTPUT, rather you must loop through it and display individual columns at each index.
DECLARE
dobs emp_dobs_nested;
BEGIN
dobs := get_emp_dobs;
FOR i IN 1..dobs.COUNT
LOOP
DBMS_OUTPUT.PUT_LINE(dobs(i).emp_fname||','||dobs(i).emp_lname||','||dobs(i).emp_dob);
END LOOP;
END;
/
You can also use TABLE function to unnest a collection of objects into a relational resultset:
select * from table( get_emp_dobs )
Live demo: http://sqlfiddle.com/#!4/8cfb2/1
Another way of display is to use XML, try
DBMS_OUTPUT.PUT_LINE(XMLTYPE(dobs));
why not a create a view instead?
CREATE VIEW emp_view
AS
SELECT firstname, lastname, birthdate
FROM employee
WHERE birthdate < TO_DATE('01-JAN-1968', 'DD-MON-YYYY');
You can also try this,
DECLARE
dobs emp_dobs_nested;
i NUMBER := 0;
BEGIN
dobs := get_emp_dobs;
LOOP
i := dobs.NEXT(i);
DBMS_OUTPUT.PUT_LINE(dobs(i).emp_fname||','||dobs(i).emp_lname||','||dobs(i).emp_dob);
IF i = dobs.LAST THEN
EXIT;
END IF;
END LOOP;
END;
/

Oracle Compound Trigger Mutation Table

I'm trying to create a compound trigger to avoid the mutation problem.
I've a table and a python's procedure that perfoms a transaction insert. The table has n fields.
What I´m trying to do is when a value of one of those fields is negative, then do not perform the operation , and insert the value from the previous record of the field (prior to insert) of the table. Another concern is that one of the fields is and id, to distinguish between sites.
For no, this is the code I've, Considering only one field (KWHGEN):
CREATE OR REPLACE TRIGGER "CIRCU3".D_measures_TP_test
--FOR INSERT OR UPDATE ON T_MEASURES_TP_NEW
FOR INSERT ON T_MEASURES_TP_NEW
COMPOUND TRIGGER
VAL_KWHGEN NUMBER(21,2);
VAL_autoin NUMBER (19,0);
AFTER EACH ROW IS
BEGIN
SELECT autoin, KWHGEN INTO VAL_ID_MED, VAL_KWHGEN FROM
(SELECT *
FROM T_measures_TP_NEW WHERE ID_site = :NEW.ID_site
ORDER BY TIMESTAMP DESC)
WHERE ROWNUM = 1;
IF :NEW.KWHGEN <0
THEN UPDATE T_MEASURES_TP_NEW SET KWHGEN = VAL_KWHGEN WHERE autoin = VAL_autoin;
END IF;
END AFTER EACH ROW;
END D_MEASURES_TP_test;
But the mutation error is following me ;-)
You have created trigger on T_MEASURES_TP_NEW and then updating same table T_MEASURES_TP_NEW within trigger. This will again call your trigger.
If the first select in trigger again returns negative value in VAL_KWHGEN then mutating error will follow you.
You defined only an AFTER EACH block, nothing else. This is the same as creating a row-level trigger (i.e. using FOR EACH ROW)
It must be like this (not tested):
CREATE OR REPLACE TRIGGER "CIRCU3".D_measures_TP_test
FOR INSERT ON T_MEASURES_TP_NEW
COMPOUND TRIGGER
VAL_KWHGEN NUMBER(21,2);
VAL_autoin NUMBER (19,0);
TYPE RowIdTableType IS TABLE OF ROWID;
TYPE KWHGENTableType IS TABLE OF T_MEASURES_TP_NEW.KWHGEN%TYPE;
RowIdTable RowIdTableType;
KWHGENTable KWHGENTableType;
BEFORE STATEMENT IS
BEGIN
RowIdTable := RowIdTable();
KWHGENTable := KWHGENTableType();
END BEFORE STATEMENT;
BEFORE EACH ROW IS
BEGIN
RowIdTable.EXTEND;
RowIdTable(RowIdTable.LAST) := :NEW.ROWID;
KWHGENTable.EXTEND;
KWHGENTable(RowIdTable.LAST) := :NEW.KWHGEN;
END BEFORE EACH ROW;
AFTER STATEMENT IS
BEGIN
FOR i IN RowIdTable.FIRST..RowIdTable.LAST LOOP
SELECT
DISTINCT MIN(autoin) OVER (ORDER BY TIMESTAMP DESC),
DISTINCT MIN(KWHGEN) OVER (ORDER BY TIMESTAMP DESC)
INTO VAL_ID_MED, VAL_KWHGEN
FROM T_measures_TP_NEW
WHERE ROWID = RowIdTable(i);
IF KWHGENTable(i) < 0
THEN UPDATE T_MEASURES_TP_NEW
SET KWHGEN = VAL_KWHGEN
WHERE autoin = VAL_autoin;
END IF;
END LOOP;
END AFTER STATEMENT;
END;
/
OK, I do have a solution:
1.- Create a package where record the new insert data (BEFORE)
create or replace PACKAGE PCK_MEDIDAS_TP AS
TYPE DATOS_MEDIDAS_TP IS RECORD(
v_id_sede NUMBER (10,0),
v_id_med NUMBER (10,0),
v_kwhGEN NUMBER (21,2),
v_timestamp TIMESTAMP
);
type T_MEDTP is table of DATOS_MEDIDAS_TP index by binary_integer;
tabla_medidas_tp T_MEDTP;
END PCK_MEDIDAS_TP;
2.- Create a procedure each row (BEFORE) to read the new insert data and then record them into de package's table.
create or replace TRIGGER "CIRCU3".D_MEDIDAS_TP_test
BEFORE INSERT ON T_MEDIDAS_TP_NEW
FOR EACH ROW
DECLARE
Indice binary_integer;
BEGIN
--AUTOINCREMENTAL DEL CAMPO ID_MEDIDAS
SELECT T_MEDIDAS_TP_NEW_SEQ.NEXTVAL INTO :NEW.id_MEDIDAS_OLD FROM DUAL;
Indice:= PCK_MEDIDAS_TP.tabla_medidas_tp.COUNT+1;
PCK_MEDIDAS_TP.tabla_medidas_tp(Indice).v_id_sede := :NEW.ID_SEDE;
PCK_MEDIDAS_TP.tabla_medidas_tp(Indice).v_id_med := :NEW.ID_MEDIDAS;
PCK_MEDIDAS_TP.tabla_medidas_tp(Indice).v_kwhGEN := :NEW.KWHGEN;
PCK_MEDIDAS_TP.tabla_medidas_tp(Indice).v_timestamp := :NEW.TIMESTAMP;
IF :NEW.KWHGEN <0 THEN
DBMS_OUTPUT.put_line('first trigger:' ||:NEW.ID_MEDIDAS||','||:NEW.ID_SEDE||','||:NEW.TIMESTAMP);
-- INSERT INTO TEST_TRIGGER VALUES ('100', :NEW.KWHGEN, SYSDATE);
--ELSE DBMS_OUTPUT.PUT_LINE('¿?');
END IF;
END;
3.- Create a statement procedure (AFTER) where you can check your condition, in my case if kwhgen <0. If is true, I'll read the previous record in the original tbale and update the insert record with taht value.
create or replace TRIGGER D_MEDIDAS_TP_TEST_STATEMENT
AFTER INSERT ON T_MEDIDAS_TP_NEW
DECLARE
Indice binary_integer;
s_id_sede NUMBER (10,0);
s_id_med NUMBER (10,0);
s_kwhGEN NUMBER (21,2);
s_timestamp TIMESTAMP;
BEGIN
FOR Indice in 1..PCK_MEDIDAS_TP.tabla_medidas_tp.count LOOP
DBMS_OUTPUT.put_line('second trigger: kwhgen: '||PCK_MEDIDAS_TP.tabla_medidas_tp(Indice).v_kwhGEN||', id_sede: '||PCK_MEDIDAS_TP.tabla_medidas_tp(Indice).v_id_sede);
IF PCK_MEDIDAS_TP.tabla_medidas_tp(Indice).v_kwhGEN <0 THEN
DBMS_OUTPUT.put_line('second trigger: v_kwhGEN is negative');
SELECT prev_KWHGEN INTO s_kwhgen
from(
SELECT LEAD (KWHGEN,1) over (ORDER BY id_medidas desc) as prev_KWHGEN
FROM T_MEDIDAS_TP_NEW WHERE ID_SEDE = PCK_MEDIDAS_TP.tabla_medidas_tp(Indice).v_id_sede
ORDER BY id_medidas DESC) where rownum =1;
INSERT INTO TEST_TRIGGER VALUES ('100', '5555', SYSDATE);
DBMS_OUTPUT.put_line('second trigger. KWHGEN: '||s_kwhGEN);
DBMS_OUTPUT.put_line('UPDATE');
UPDATE T_MEDIDAS_TP_NEW SET KWHGEN = S_KWHGEN WHERE ID_MEDIDAS = PCK_MEDIDAS_TP.tabla_medidas_tp(Indice).v_id_med;
else DBMS_OUTPUT.put_line('¿?');
END IF;
END LOOP;
PCK_MEDIDAS_TP.tabla_medidas_tp.delete; -- vaciamos la tabla
END;

ora-06502 on writing into LONG variable

Here's a simplified request:
declare
long_var long;
begin
select long_column into long_var from my_table where id = 1;
end;
It works for most cases but when the long_column value is too big (I've found at least one entry - it has a value of about 5 megabytes) causes
ORA-06502: PL/SQL: numeric or value error
From the Oracle PL/SQL documentation
You cannot retrieve a value longer than 32,760 bytes from a LONG or LONG RAW column into a LONG or LONG RAW variable.
https://docs.oracle.com/cd/E11882_01/appdev.112/e25519/datatypes.htm#CJAEGDEB
You can use a CLOB but you must alter the column from LONG to CLOB
ALTER TABLE my_table MODIFY ( long_column CLOB );
Then you use:
declare
clob_var clob;
begin
select long_column into clob_var from my_table where id = 1;
end;
If you cannot change the table copy it over:
1) Create table with a CLOB
create table my_table_2 (clob_column clob, id number);
2) Copy it over
insert into my_table_2
select to_lob(long_column), id from my_table;
3) Access the new table
declare
clob_var clob;
begin
select clob_column into clob_var from my_table_2 where id = 1;
end;

Oracle PL/SQL return array of rows

Dear Oracle Developers,
I have searched and googled to find a solution for my problem but nothing helped me.
Situation :
TABLE : CUSTOMER(....);
My problem is : I want to create a stored procedure say get_customers to return an array of customer rows. I have no idea how to get this working.
I tried to create a type customer_rec and using a cursor to retrieve no more than maxRows
create or replace procedure get_customers(maxRows IN NUMBER, ??? OUT ????)
How to define the OUT parameter ?
How to retrieve the rows in the array using a cursor ?
Thanks a lot
I would like to answer this in a way that discourages passing around arrays, when passing around cursors is a more sound approach. It doesn't exactly answer the question as posed, but it is an answer. Thinking cursors instead of thinking arrays is more efficient and thus more scalable. Also, it can be much easier code to maintain.
create table customer (
customer_id number(2) primary key,
customer_name varchar2(200) );
insert into customer values (1, 'Customer One');
insert into customer values (2, 'Customer Two');
insert into customer values (3, 'Customer Three');
insert into customer values (4, 'Customer Four');
insert into customer values (5, 'Customer Five');
insert into customer values (6, 'Customer Six');
insert into customer values (7, 'Customer Seven');
CREATE OR REPLACE PACKAGE cursor_not_array IS
FUNCTION get_customers(p_max_records INTEGER, p_id_start INTEGER, p_id_end INTEGER DEFAULT NULL) RETURN SYS_REFCURSOR;
END cursor_not_array;
CREATE OR REPLACE PACKAGE BODY cursor_not_array IS
c_max_customer_id CONSTANT NUMBER(2) := 99;
FUNCTION get_customers(p_max_records INTEGER, p_id_start INTEGER, p_id_end INTEGER DEFAULT NULL) RETURN SYS_REFCURSOR IS
v_result SYS_REFCURSOR;
BEGIN
OPEN v_result FOR
SELECT customer_id,
customer_name
FROM customer
WHERE customer_id BETWEEN p_id_start AND nvl(p_id_end, c_max_customer_id)
ORDER BY customer_id;
RETURN v_result;
END;
END cursor_not_array;
You could create a package like this:
create or replace package customers is
type customers_array is table of customer%rowtype index by binary_integer;
procedure get_customers(maxRows IN NUMBER, customer_array OUT customers_array);
end customers;
create or replace package body customers is
procedure get_customers(maxRows IN NUMBER, customer_array OUT customers_array) is
cursor c_customers is
select *
from customers;
where rownum <= maxRows;
i number := 1;
begin
for r in c_customers loop
customer_array(i) := r;
i := i + 1;
end loop;
end get_customers;
end customers;
And then call the get_customers procedure from wherever you want to...
first time create VARRAY type.
'create TYPE CUSTARRAY is VARRAY(100) OF VARCHAR2(30);'
varray limit is depends on you.
then create procedure that return CUSTARRAY type parameter.
`create
procedure prc_get_arr(p_maxrow in number, p_customers out custarray)
as
my_cust custarray := custarray();
cursor c_cust is select name from CUSTOMER where rownum<p_maxrow;
v_customer varchar2(64);
begin
open c_cust;
loop
fetch c_cust into v_customer;
exit when c_cust%notfound;
my_cust.extend;
my_cust(my_cust.count) := v_customer;
end loop;
close c_cust;
p_customers:=my_cust;
end;`
Now call this procedure
DECLARE
P_MAXROW NUMBER;
p_customers custarray;
v_cnt number:=0;
begin
P_MAXROW := 22;
prc_get_arr( p_maxrow => p_maxrow, p_customers => p_customers );
v_cnt:=p_customers.count;
for i in p_customers.first..p_customers.last loop
dbms_output.put_line('P_CUSTOMERS = ' || p_customers(i));
end loop;
end;
You can, using the SYS_REFCURSOR on your function output.
First you have to define a collection :
TYPE customers_array IS TABLE OF customer%ROWTYPE
INDEX BY BINARY_INTEGER;
Then your procedure simply have to fetch the result into that collection.
You're procedure could be written as follow:
CREATE OR REPLACE PACKAGE your_pkg
AS
TYPE customers_array IS TABLE OF customer%ROWTYPE
INDEX BY BINARY_INTEGER;
PROCEDURE get_customers(pn_max_rows IN NUMBER,
pt_coustomers OUT customers_array);
END your_pkg;
CREATE OR REPLACE PACKAGE BODY your_pkg
AS
PROCEDURE get_customers(pn_max_rows IN NUMBER,
pt_coustomers OUT customers_array)
IS
BEGIN
SELECT *
BULK COLLECT INTO pt_coustomers
FROM customers
WHERE rownum <= pn_max_rows;
END get_customers;
END your_pkg;

Resources