Trigger IF Inserting Loop /Oracle - oracle

I am trying to Insert new row in Table StavkaUgovora (ContractItem) and trigger/procedure should calculate (+/-) jedinicna_cena (price) and kolicina (amount) into the ukupna_cena (total_price) at table Ugovor(contract). I already have update and delete and that works fine, but for inserting statement script go into the loop and multiplicate data without stopping. Where i wrong?
CREATE TABLE StavkaUgovora (
broj_stavke INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
broj_ugovora INT not null,
kolicina NUMBER,
tip_usluge VARCHAR2(255),
jedinicna_cena NUMBER,
CONSTRAINT stavka_ugovora_broj_ugovora
FOREIGN KEY(broj_ugovora)
REFERENCES Ugovor(broj_ugovora));
CREATE TABLE Ugovor (
broj_ugovora INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
broj_ponude INT not null,
datum_sklapanja DATE,
ukupna_cena NUMBER,
CONSTRAINT broj_ponude_ugovor
FOREIGN KEY(broj_ponude)
REFERENCES Ponuda(broj_ponude)
);
CREATE OR REPLACE PROCEDURE Zbir (brojUgovoraIN NUMBER, cenaSaberiIN NUMBER, cenaOduzmiIN
NUMBER)
AS suma UGOVOR.broj_ugovora%type;
pragma autonomous_transaction;
BEGIN suma:=0;
EXECUTE IMMEDIATE 'ALTER TRIGGER ZABRANAIZMENEUKUPNECENE DISABLE';
SELECT ukupna_cena INTO suma
FROM UGOVOR
WHERE broj_ugovora=brojUgovoraIN;
UPDATE UGOVOR
SET ukupna_cena=suma + cenaSaberiIN - cenaOduzmiIN
WHERE broj_ugovora=brojUgovoraIN;
EXECUTE IMMEDIATE 'ALTER TRIGGER ZABRANAIZMENEUKUPNECENE ENABLE';
COMMIT;
END;
CREATE OR REPLACE TRIGGER "SIFRAUGOVORA"
BEFORE INSERT OR UPDATE OR DELETE ON STAVKAUGOVORA
FOR EACH ROW
DECLARE
brojUgovora NUMBER;
cenaSaberi NUMBER;
cenaOduzmi NUMBER;
BEGIN
IF INSERTING
THEN
BEGIN
brojUgovora := :NEW.broj_ugovora;
cenaSaberi := :NEW.jedinicna_cena * :NEW.kolicina;
cenaOduzmi := 0;
END;
ELSIF UPDATING
THEN
BEGIN
brojUgovora := :NEW.broj_ugovora;
cenaSaberi := :NEW.jedinicna_cena * :NEW.kolicina;
cenaOduzmi := :OLD.jedinicna_cena * :OLD.kolicina;
END;
ELSE
BEGIN
brojUgovora := :OLD.broj_ugovora;
cenaSaberi := 0;
cenaOduzmi := :OLD.jedinicna_cena * :OLD.kolicina;
END;
END IF;
BEGIN
Zbir(brojUgovora, cenaSaberi, cenaOduzmi);
END;
END;

It works for me (after some modifications as you didn't post all tables/triggers involved).
Tables:
SQL> CREATE TABLE ugovor
2 (
3 broj_ugovora INT PRIMARY KEY,
4 broj_ponude INT NOT NULL,
5 datum_sklapanja DATE,
6 ukupna_cena NUMBER
7 );
Table created.
SQL> CREATE TABLE stavkaugovora
2 (
3 broj_stavke INT PRIMARY KEY,
4 broj_ugovora INT NOT NULL,
5 kolicina NUMBER,
6 tip_usluge VARCHAR2 (255),
7 jedinicna_cena NUMBER,
8 CONSTRAINT stavka_ugovora_broj_ugovora FOREIGN KEY (broj_ugovora)
9 REFERENCES ugovor (broj_ugovora)
10 );
Table created.
Procedure:
SQL> CREATE OR REPLACE PROCEDURE zbir (brojugovorain NUMBER,
2 cenasaberiin NUMBER,
3 cenaoduzmiin NUMBER)
4 AS
5 suma ugovor.broj_ugovora%TYPE;
6 PRAGMA AUTONOMOUS_TRANSACTION;
7 BEGIN
8 suma := 0;
9
10 --EXECUTE IMMEDIATE 'ALTER TRIGGER ZABRANAIZMENEUKUPNECENE DISABLE';
11 SELECT ukupna_cena
12 INTO suma
13 FROM ugovor
14 WHERE broj_ugovora = brojugovorain;
15
16 UPDATE ugovor
17 SET ukupna_cena = suma + cenasaberiin - cenaoduzmiin
18 WHERE broj_ugovora = brojugovorain;
19
20 --EXECUTE IMMEDIATE 'ALTER TRIGGER ZABRANAIZMENEUKUPNECENE ENABLE';
21 COMMIT;
22 END;
23 /
Procedure created.
Trigger:
SQL> CREATE OR REPLACE TRIGGER sifraugovora
2 BEFORE INSERT OR UPDATE OR DELETE
3 ON stavkaugovora
4 FOR EACH ROW
5 DECLARE
6 brojugovora NUMBER;
7 cenasaberi NUMBER;
8 cenaoduzmi NUMBER;
9 BEGIN
10 IF INSERTING
11 THEN
12 brojugovora := :new.broj_ugovora;
13 cenasaberi := :new.jedinicna_cena * :new.kolicina;
14 cenaoduzmi := 0;
15 ELSIF UPDATING
16 THEN
17 brojugovora := :new.broj_ugovora;
18 cenasaberi := :new.jedinicna_cena * :new.kolicina;
19 cenaoduzmi := :old.jedinicna_cena * :old.kolicina;
20 ELSE
21 brojugovora := :old.broj_ugovora;
22 cenasaberi := 0;
23 cenaoduzmi := :old.jedinicna_cena * :old.kolicina;
24 END IF;
25
26 DBMS_OUTPUT.put_line ('ugovor: ' || brojugovora);
27 zbir (brojugovora, cenasaberi, cenaoduzmi);
28 END;
29 /
Trigger created.
Testing: insert UGOVOR first and then COMMIT because the zbir procedure is declared as an autonomous transaction so - if you don't COMMIT - it won't see newly added rows into UGOVOR table.
SQL> SET SERVEROUTPUT ON
SQL>
SQL> INSERT INTO ugovor (broj_ugovora,
2 broj_ponude,
3 datum_sklapanja,
4 ukupna_cena)
5 VALUES (1,
6 1,
7 SYSDATE,
8 100);
1 row created.
SQL> COMMIT;
Commit complete.
Insert stavkaugovora:
SQL> INSERT INTO stavkaugovora (broj_stavke,
2 broj_ugovora,
3 kolicina,
4 tip_usluge,
5 jedinicna_cena)
6 VALUES (1,
7 1,
8 10,
9 'A',
10 5);
ugovor: 1
1 row created.
No errors, no "loop". The result:
SQL> SELECT * FROM ugovor;
BROJ_UGOVORA BROJ_PONUDE DATUM_SKLAPANJA UKUPNA_CENA
------------ ----------- ------------------- -----------
1 1 26.01.2022 08:21:21 150
SQL> SELECT * FROM stavkaugovora;
BROJ_STAVKE BROJ_UGOVORA KOLICINA TIP_USLUGE JEDINICNA_CENA
----------- ------------ ---------- ---------- --------------
1 1 10 A 5
SQL>

Thanks for answer! Everything works fine when put in comment autonomous transaction and manualy disable trigger. I use autonomous transaction to disable trigger ZabranaIzmeneUkupneCene which prohibits direct input values into ukupna_cena on table UGOVOR. I saw in your code autonomous transaction is in comment too.
Do you have advice how to disable trigger 'ZabranaIzmeneUkupneCene' while procedure 'Zbir' is active?
CREATE OR REPLACE TRIGGER ZabranaIzmeneUkupneCene
BEFORE UPDATE OF ukupna_cena ON UGOVOR
FOR EACH ROW
BEGIN
RAISE_APPLICATION_ERROR (-20001, 'Nije moguca promena ukupne cene direktno!');
END;

Related

I want to fetch column values from column name rollnoofstud of tableA using PL/SQL

DECLARE
TYPE norollno IS TABLE OF VARCHAR2(100);
rollno norollno;
BEGIN
BEGIN
SELECT token
BULK COLLECT INTO rollno
FROM tableA
WHERE columname='rollnoofstud';
EXCEPTION
WHEN NO_DATA_FOUND THEN
rollno := norollno();
END ;
IF rollno >0 THEN
FOR i IN rollno.FIRST..norollno.LAST
LOOP
<doSomeThing>
END LOOP;
END IF;
END;
I am trying this but I am not getting output. I doubt if my select statement is correct.
I don't have your table so I created one:
SQL> CREATE TABLE tablea
2 AS
3 SELECT ename AS token, 'rollnoofstud' AS columname
4 FROM emp
5 WHERE deptno = 10;
Table created.
Code you posted isn't that wrong; requires a little bit of fixing (see line #17, the way you check whether collection contains something (count it!); typo in FOR loop):
SQL> SET SERVEROUTPUT ON
SQL> DECLARE
2 TYPE norollno IS TABLE OF VARCHAR2 (100);
3
4 rollno norollno;
5 BEGIN
6 BEGIN
7 SELECT token
8 BULK COLLECT INTO rollno
9 FROM tableA
10 WHERE columname = 'rollnoofstud';
11 EXCEPTION
12 WHEN NO_DATA_FOUND
13 THEN
14 rollno := norollno ();
15 END;
16
17 IF rollno.COUNT > 0
18 THEN
19 FOR i IN rollno.FIRST .. rollno.LAST
20 LOOP
21 DBMS_OUTPUT.put_line (rollno (i));
22 END LOOP;
23 END IF;
24 END;
25 /
CLARK --> here's the result
KING
MILLER
PL/SQL procedure successfully completed.
SQL>
[EDIT: with your sample table and data:]
(note that there's no text datatype in Oracle!)
SQL> CREATE TABLE students
2 (
3 rollnostud INTEGER PRIMARY KEY,
4 name VARCHAR2 (10) NOT NULL,
5 gender VARCHAR2 (1) NOT NULL
6 );
Table created.
SQL> INSERT INTO students
2 VALUES (1, 'Ryan', 'M');
1 row created.
SQL> INSERT INTO students
2 VALUES (2, 'Joanna', 'F');
1 row created.
SQL> INSERT INTO students
2 VALUES (3, 'John', 'M');
1 row created.
Procedure:
SQL> SET SERVEROUTPUT ON
SQL>
SQL> DECLARE
2 TYPE norollno IS TABLE OF VARCHAR2 (100);
3
4 rollno norollno;
5 BEGIN
6 BEGIN
7 SELECT name
8 BULK COLLECT INTO rollno
9 FROM students;
10 EXCEPTION
11 WHEN NO_DATA_FOUND
12 THEN
13 rollno := norollno ();
14 END;
15
16 IF rollno.COUNT > 0
17 THEN
18 FOR i IN rollno.FIRST .. rollno.LAST
19 LOOP
20 DBMS_OUTPUT.put_line (rollno (i));
21 END LOOP;
22 END IF;
23 END;
24 /
Ryan
Joanna
John
PL/SQL procedure successfully completed.
SQL>

Need to fetch the table details using stored procedure when we give table name as input

CREATE TABLE test_table
(
col1 NUMBER(10),
col2 NUMBER(10)
);
INSERT INTO test_table
VALUES (1, 2);
I am writing a stored procedure wherein if I give a table name as an input, that should give me the table data and column details.
For example:
SELECT *
FROM <input_table_name>;
But this causes an error that the SQL command has not ended properly even though I have taken care of this.
My attempt:
CREATE OR REPLACE PROCEDURE sp_test(iv_table_name IN VARCHAR2,
p_out_cur OUT SYS_REFCURSOR)
AS
lv_str VARCHAR2(400);
lv_count NUMBER(1);
lv_table_name VARCHAR2(255):=UPPER(iv_table_name);
BEGIN
lv_str := 'SELECT * FROM '||lv_table_name;
SELECT COUNT(1) INTO lv_count FROM all_tables WHERE table_name = lv_table_name;
IF lv_count = 0 THEN
dbms_output.put_line('Table does not exist');
ELSE
OPEN p_out_cur FOR lv_str;
END IF;
END sp_test;
Tool used: SQL developer(18c)
In dynamic SQL, you do NOT terminate statement with a semicolon.
EXECUTE IMMEDIATE 'SELECT * FROM '||lv_table_name||';';
-----
remove this
Anyway, you won't get any result when you run that piece of code. If you really want to see table's contents, you'll have to switch to something else, e.g. create a function that returns ref cursor.
Sample data:
SQL> SELECT * FROM test_table;
COL1 COL2
---------- ----------
1 2
3 4
Procedure you wrote is now correct:
SQL> CREATE OR REPLACE PROCEDURE sp_test (iv_table_name IN VARCHAR2,
2 p_out_cur OUT SYS_REFCURSOR)
3 AS
4 lv_str VARCHAR2 (400);
5 lv_count NUMBER (1);
6 lv_table_name VARCHAR2 (255) := UPPER (iv_table_name);
7 BEGIN
8 lv_str := 'SELECT * FROM ' || lv_table_name;
9
10 SELECT COUNT (1)
11 INTO lv_count
12 FROM all_tables
13 WHERE table_name = lv_table_name;
14
15 IF lv_count = 0
16 THEN
17 DBMS_OUTPUT.put_line ('Table does not exist');
18 ELSE
19 OPEN p_out_cur FOR lv_str;
20 END IF;
21 END sp_test;
22 /
Procedure created.
Testing:
SQL> SET SERVEROUTPUT ON
SQL> DECLARE
2 l_rc SYS_REFCURSOR;
3 l_col1 NUMBER (10);
4 l_col2 NUMBER (10);
5 BEGIN
6 sp_test ('TEST_TABLE', l_rc);
7
8 LOOP
9 FETCH l_rc INTO l_col1, l_col2;
10
11 EXIT WHEN l_rc%NOTFOUND;
12
13 DBMS_OUTPUT.put_line (l_col1 || ', ' || l_col2);
14 END LOOP;
15 END;
16 /
1, 2 --> contents of the
3, 4 --> TEST_TABLE
PL/SQL procedure successfully completed.
SQL>
A function (instead of a procedure with the OUT parameter):
SQL> CREATE OR REPLACE FUNCTION sf_test (iv_table_name IN VARCHAR2)
2 RETURN SYS_REFCURSOR
3 AS
4 lv_str VARCHAR2 (400);
5 lv_count NUMBER (1);
6 lv_table_name VARCHAR2 (255) := UPPER (iv_table_name);
7 l_rc SYS_REFCURSOR;
8 BEGIN
9 lv_str := 'SELECT * FROM ' || lv_table_name;
10
11 SELECT COUNT (1)
12 INTO lv_count
13 FROM all_tables
14 WHERE table_name = lv_table_name;
15
16 IF lv_count = 0
17 THEN
18 raise_application_error (-20000, 'Table does not exist');
19 ELSE
20 OPEN l_rc FOR lv_str;
21 END IF;
22
23 RETURN l_rc;
24 END sf_test;
25 /
Function created.
Testing:
SQL> SELECT sf_test ('liksajfla') FROM DUAL;
SELECT sf_test ('liksajfla') FROM DUAL
*
ERROR at line 1:
ORA-20000: Table does not exist
ORA-06512: at "SCOTT.SF_TEST", line 18
SQL> SELECT sf_test ('TEST_TABLE') FROM DUAL;
SF_TEST('TEST_TABLE'
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
COL1 COL2
---------- ----------
1 2
3 4
SQL>

Dynamic SQL Stored Procedure in Oracle

Morning All, Hopefully someone can help. I'm an MSSQL specialist and have very little experience of Oracle PL/SQL. I've been asked to write an Oracle Stored Procedure which gets specific fields from the latest Monthly audit table. This is what I've come up with but it doesn't seem to run or output anywhere.
Can someone help?
CREATE OR REPLACE PROCEDURE GetLatestMonthAuditTable
AS
BEGIN
DECLARE
v_lastmonth date := interval '-1' month +systimestamp;
v_year varchar2(4) := extract(year from v_lastmonth);
v_month varchar2(2) := extract(month from v_lastmonth);
v_day varchar2(2) := extract(day from LAST_DAY(v_lastMonth));
v_sql varchar2(256) := 'SELECT ACT_CODE, CHANGE_BY, CHANGE_DATE FROM AUDIT_' || v_year || v_month || v_day;
BEGIN
EXECUTE IMMEDIATE v_sql;
END;
END;
You didn't actually run it - you just created a procedure.
Could've been like this:
Sample table:
SQL> CREATE TABLE AUDIT_20211031
2 AS
3 SELECT 1 act_code, 'Littlefoot' change_by, SYSDATE change_date FROM DUAL;
Table created.
Procedure (it is a good habit to display statement you'll run using dbms_output.put_line; once you make sure it is OK, remove that line):
SQL> CREATE OR REPLACE PROCEDURE GetLatestMonthAuditTable
2 AS
3 v_lastmonth DATE := INTERVAL '-1' MONTH + SYSTIMESTAMP;
4 v_year VARCHAR2 (4) := EXTRACT (YEAR FROM v_lastmonth);
5 v_month VARCHAR2 (2) := EXTRACT (MONTH FROM v_lastmonth);
6 v_day VARCHAR2 (2) := EXTRACT (DAY FROM LAST_DAY (v_lastMonth));
7 v_sql VARCHAR2 (200);
8 BEGIN
9 v_sql :=
10 'SELECT ACT_CODE, CHANGE_BY, CHANGE_DATE FROM AUDIT_'
11 || v_year
12 || v_month
13 || v_day;
14
15 DBMS_OUTPUT.put_line (v_sql);
16
17 EXECUTE IMMEDIATE v_sql;
18 END;
19 /
Procedure created.
Testing:
SQL> SET SERVEROUTPUT ON
SQL>
SQL> BEGIN
2 GetLatestMonthAuditTable;
3 END;
4 /
SELECT ACT_CODE, CHANGE_BY, CHANGE_DATE FROM AUDIT_20211031
PL/SQL procedure successfully completed.
SQL>
Now, your procedure doesn't do anything - it runs that select, but it isn't displayed anywhere on the screen.
If it were a function instead, you could return ref cursor and see something. For example:
SQL> CREATE OR REPLACE FUNCTION GetLatestMonthAuditTable
2 RETURN SYS_REFCURSOR
3 AS
4 v_lastmonth DATE := INTERVAL '-1' MONTH + SYSTIMESTAMP;
5 v_year VARCHAR2 (4) := EXTRACT (YEAR FROM v_lastmonth);
6 v_month VARCHAR2 (2) := EXTRACT (MONTH FROM v_lastmonth);
7 v_day VARCHAR2 (2) := EXTRACT (DAY FROM LAST_DAY (v_lastMonth));
8 v_sql VARCHAR2 (200);
9 rc SYS_REFCURSOR;
10 BEGIN
11 v_sql :=
12 'SELECT ACT_CODE, CHANGE_BY, CHANGE_DATE FROM AUDIT_'
13 || v_year
14 || v_month
15 || v_day;
16
17 OPEN rc FOR v_sql;
18
19 RETURN rc;
20 END;
21 /
Function created.
Testing the function:
SQL> SELECT GetLatestMonthAuditTable FROM DUAL;
GETLATESTMONTHAUDITT
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
ACT_CODE CHANGE_BY CHANGE_DATE
---------- ---------- -------------------
1 Littlefoot 16.11.2021 12:14:18
SQL>

I want to Create a function named VALIDATE_EMP which accepts employeeNumber as a parameter, Returns TRUE or FALSE depending on existence

Sample Data
create table Employees (emp_id number, emp_name varchar2(50), salary number, department_id number) ;
insert into Employees values(1,'ALex',10000,10);
insert into Employees values(2,'Duplex',20000,20);
insert into Employees values(3,'Charles',30000,30);
insert into Employees values(4,'Demon',40000,40);
Code :
create or replace function validate_emp(empno in number)
return boolean
is lv_count number
begin
select count(employee_id) into lv_count from hr.employees where employee_id = empno;
if lv_count >1 then
return true;
else
return false;
end;
I want to Create a function named VALIDATE_EMP which accepts empno as a parameter, Returns TRUE if the specified employee exists in the table name “Employeee” else FALSE.
missing semi-colon as terminator of the local variable declaration
if user you're connected to isn't hr, remove it (otherwise, leave it as is)
column name is emp_id, not employee_id
missing end if
When fixed, code compiles:
SQL> CREATE OR REPLACE FUNCTION validate_emp (empno IN NUMBER)
2 RETURN BOOLEAN
3 IS
4 lv_count NUMBER;
5 BEGIN
6 SELECT COUNT (emp_id)
7 INTO lv_count
8 FROM employees
9 WHERE emp_id = empno;
10
11 IF lv_count > 1
12 THEN
13 RETURN TRUE;
14 ELSE
15 RETURN FALSE;
16 END IF;
17 END;
18 /
Function created.
SQL>
How to call it? Via PL/SQL as Oracle's SQL doesn't have the Boolean datatype.
SQL> set serveroutput on
SQL> declare
2 result boolean;
3 begin
4 result := validate_emp(1);
5
6 dbms_output.put_line(case when result then 'employee exists'
7 else 'employee does not exist'
8 end);
9 end;
10 /
employee does not exist
PL/SQL procedure successfully completed.
SQL>
Maybe you'd rather return VARCHAR2; then you'd mimic Boolean, but you'd be able to use it in plain SQL:
SQL> CREATE OR REPLACE FUNCTION validate_emp (empno IN NUMBER)
2 RETURN VARCHAR2
3 IS
4 lv_count NUMBER;
5 BEGIN
6 SELECT COUNT (emp_id)
7 INTO lv_count
8 FROM employees
9 WHERE emp_id = empno;
10
11 IF lv_count > 1
12 THEN
13 RETURN 'TRUE';
14 ELSE
15 RETURN 'FALSE';
16 END IF;
17 END;
18 /
Function created.
SQL> select validate_emp(1) from dual;
VALIDATE_EMP(1)
-------------------------------------------------------------------
FALSE
SQL>

How to update ref cursor values in oracle?

I want to fetch limited no. of rows using refcursor. then I need to update same set of records. is it possible?
create or replace PROCEDURE myproc (
P_ROWCOUNT IN NUMBER,
OUT_TXN_IDS OUT OF_CR_TYPE,
P_CD_ERROR OUT NUMBER,
P_DS_ERROR OUT VARCHAR2
)
AS
V_TXNID NUMBER;
BEGIN
P_CD_ERROR := 0;
P_DS_ERROR := 'SUCCESS';
OPEN OUT_TXN_IDS for
SELECT id FROM table1 WHERE status='N' AND ROWNUM<=P_ROWCOUNT;
EXCEPTION
WHEN OTHERS THEN
P_CD_ERROR := sqlcode;
P_DS_ERROR := 'WF-ERROR - myproc - ' || substr(SQLERRM, 1, 200);
RETURN;
END myproc;
I need to update same records to status Y after refcursor returns. can we do this. please suggest
I don't have your tables nor data so I simplified it a little bit, but - it should work nonetheless.
Initial statuses:
SQL> SELECT status, count(*) FROM table1 group by status;
S COUNT(*)
- ----------
Y 7
N 7
Procedure: basically, you'd modify rows represented by ID returned by ref cursor.
SQL> DECLARE
2 out_txn_ids SYS_REFCURSOR;
3 p_rowcount NUMBER := 5;
4 l_id table1.id%TYPE;
5 BEGIN
6 OPEN out_txn_ids FOR SELECT id
7 FROM table1
8 WHERE status = 'N'
9 AND ROWNUM <= p_rowcount;
10
11 LOOP
12 FETCH out_txn_ids INTO l_id;
13
14 EXIT WHEN out_txn_ids%NOTFOUND;
15
16 UPDATE table1
17 SET status = 'Y'
18 WHERE id = l_id;
19 END LOOP;
20
21 CLOSE out_txn_ids;
22 END;
23 /
PL/SQL procedure successfully completed.
Result:
SQL> SELECT status, count(*) FROM table1 group by status;
S COUNT(*)
- ----------
Y 12
N 2
SQL>

Resources