How to get rid of compilation errors in SQL? - oracle

I keep getting the Warning 'Procedure created with compilation errors'.
The code is the following.
BEGIN
CREATE OR REPLACE PACKAGE ABC IS
TYPE names_varray IS VARRAY(10) OF VARCHAR2(30);
TYPE salaries_varray IS VARRAY(10) OF NUMBER;
names names_varray;
salaries salaries_varray;
PROCEDURE BuildVarrays;
PROCEDURE DisplayEmpTop10SalariesInReverse;
END ABC;
CREATE OR REPLACE PACKAGE BODY ABC IS
CURSOR c_emp_top_salaries
IS
SELECT ROWNUM, ENAME, SAL
FROM (SELECT ENAME, SAL FROM EMP ORDER BY SAL DESC)
WHERE ROWNUM <= 10;
PROCEDURE BuildVarrays
IS
v_count NUMBER := 0;
BEGIN
FOR emp_rec IN c_emp_top_salaries LOOP
v_count := v_count + 1;
names(v_count) := emp_rec.ENAME;e
salaries(v_count) := emp_rec.SAL;
END LOOP;
END BuildVarrays;
PROCEDURE DisplayEmpTop10SalariesInReverse
IS
BEGIN
FOR i IN REVERSE 1..10 LOOP
DBMS_OUTPUT.PUT_LINE('Employee Name: ' || names(i) || ' Salary: ' || salaries(i));
END LOOP;
END DisplayEmpTop10SalariesInReverse;
END ABC;
BEGIN
ABC.BuildVarrays;
ABC.DisplayEmpTop10SalariesInReverse;
END;
END;
I have tried to rebuild the code, but I seem to be stuck. Being new to PL/SQL, I am unable to understand how to manage user-defined exceptions in procedures

Don't wrap it in BEGIN and END;
Use / on a new line as a PL/SQL statement terminator; and
Use BULK COLLECT rather than a cursor.
Like this:
CREATE OR REPLACE PACKAGE ABC IS
TYPE names_varray IS VARRAY(10) OF EMP.ENAME%TYPE;
TYPE salaries_varray IS VARRAY(10) OF EMP.SAL%TYPE;
names names_varray;
salaries salaries_varray;
PROCEDURE BuildVarrays;
PROCEDURE DisplayEmpTop10SalariesInReverse;
END ABC;
/
CREATE OR REPLACE PACKAGE BODY ABC IS
PROCEDURE BuildVarrays
IS
BEGIN
SELECT ENAME, SAL
BULK COLLECT INTO names, salaries
FROM (SELECT ENAME, SAL FROM EMP ORDER BY SAL DESC)
WHERE ROWNUM <= 10;
END BuildVarrays;
PROCEDURE DisplayEmpTop10SalariesInReverse
IS
BEGIN
FOR i IN REVERSE 1..10 LOOP
DBMS_OUTPUT.PUT_LINE('Employee Name: ' || names(i) || ' Salary: ' || salaries(i));
END LOOP;
END DisplayEmpTop10SalariesInReverse;
END ABC;
/
and then call it using:
BEGIN
DBMS_OUTPUT.ENABLE();
ABC.BuildVarrays;
ABC.DisplayEmpTop10SalariesInReverse;
END;
/
Which, for the table:
CREATE TABLE emp (ename, sal) AS
SELECT 'Alice', 100 FROM DUAL UNION ALL
SELECT 'Beryl', 500 FROM DUAL UNION ALL
SELECT 'Carol', 400 FROM DUAL UNION ALL
SELECT 'Debra', 900 FROM DUAL UNION ALL
SELECT 'Emily', 700 FROM DUAL UNION ALL
SELECT 'Fiona', 800 FROM DUAL UNION ALL
SELECT 'Gemma', 200 FROM DUAL UNION ALL
SELECT 'Hanna', 800 FROM DUAL UNION ALL
SELECT 'Ivory', 400 FROM DUAL UNION ALL
SELECT 'Jesse', 600 FROM DUAL UNION ALL
SELECT 'Kiera', 500 FROM DUAL;
Outputs:
Employee Name: Gemma Salary: 200
Employee Name: Ivory Salary: 400
Employee Name: Carol Salary: 400
Employee Name: Kiera Salary: 500
Employee Name: Beryl Salary: 500
Employee Name: Jesse Salary: 600
Employee Name: Emily Salary: 700
Employee Name: Hanna Salary: 800
Employee Name: Fiona Salary: 800
Employee Name: Debra Salary: 900
fiddle

Related

Oracle View or Table Function that returns union of queries stored as text in another table

Let's say that I have a "Rules" table that has a column that contains oracle queries in a varchar2 column:
Row
Query
1
select 'Hardcoded_Tablename_1' tablename, request_id from table_1 where status >= 4 and resolve_date < *some date math goes here*
2
select 'Table_2' tablename, t2.request_id from table_2 t2 join table_1 t1 on t1.request_id = t2.parent_id where t1.status >= 4 and t1.resolve_date < *some date math goes here*
If this were never going to change, I'd just make a view with a union of these queries.
Our requirement is that we be able to add to or to modify these rules on-the-fly at the whims of leadership.
So, what I need is either:
a very smart view (I think impossible) that executes and unions all of these stored query strings
or
a table function that returns the results of the union of these stored query strings. (I think this is the more likely solution)
It will only ever be those two columns: The hardcoded name of the table and the ID of the record.
Can someone help get me started on this?
Thanks
You can use a PIPELINED function.
First create the types:
CREATE TYPE request_data IS OBJECT (tablename VARCHAR2(30), request_id NUMBER);
CREATE TYPE request_list IS TABLE OF request_data;
Then the function:
CREATE FUNCTION get_requests RETURN request_list PIPELINED
IS
BEGIN
FOR r IN (SELECT "QUERY" FROM table_name ORDER BY "ROW")
LOOP
DECLARE
c_cursor SYS_REFCURSOR;
v_tablename VARCHAR2(30);
v_request_id NUMBER;
BEGIN
OPEN c_cursor FOR r."QUERY";
LOOP
FETCH c_cursor INTO v_tablename, v_request_id;
EXIT WHEN c_cursor%NOTFOUND;
PIPE ROW (request_data(v_tablename, v_request_id));
END LOOP;
CLOSE c_cursor;
EXCEPTION
WHEN NO_DATA_NEEDED THEN
CLOSE c_cursor;
RETURN;
END;
END LOOP;
END;
/
Then, if you have the sample data:
CREATE TABLE table_name ("ROW", "QUERY") AS
SELECT 1, q'[select 'Hardcoded_Tablename_1' tablename, request_id from table_1 where status >= 4 and resolve_date < SYSDATE]' FROM DUAL UNION ALL
SELECT 2, q'[select 'Table_2' tablename, t2.request_id from table_2 t2 join table_1 t1 on t1.request_id = t2.parent_id where t1.status >= 4 and t1.resolve_date < SYSDATE]' FROM DUAL
CREATE TABLE table_1 (request_id, status, resolve_date) AS
SELECT 42, 4, SYSDATE - 1 FROM DUAL;
CREATE TABLE table_2 (request_id, parent_id) AS
SELECT 57, 42 FROM DUAL;
Then you can use the function in a table collection expression:
SELECT *
FROM TABLE(get_requests());
Which outputs:
TABLENAME
REQUEST_ID
Hardcoded_Tablename_1
42
Table_2
57
db<>fiddle here
One option might be a function that returns refcursor.
SQL> select * from rules;
CROW QUERY
---------- ----------------------------------------------------------------------------------------------------
1 select 'EMP' tablename, empno from emp where hiredate = (select max(hiredate) from emp)
2 select 'DEPT' tablename, d.deptno from emp e join dept d on d.deptno = e.deptno where e.hiredate = (
select min(hiredate) from emp)
Function creates union of all queries from the rules table and uses it as a source for the refcursor:
SQL> create or replace function f_test return sys_refcursor
2 is
3 l_rc sys_refcursor;
4 l_str clob;
5 begin
6 for cur_r in (select query from rules order by crow) loop
7 l_str := l_str || cur_r.query ||' union all ';
8 end loop;
9 l_str := rtrim(l_str, ' union all ');
10
11 open l_rc for l_str;
12 return l_rc;
13 end;
14 /
Function created.
Testing:
SQL> select f_test from dual;
F_TEST
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
TABL EMPNO
---- ----------
EMP 7876
DEPT 20
SQL>

Trigger DML in oracle database

I have problem with my dml trigger on oracle database.I want to launch trigger when i update first_name or last name on employees table in hr schema. During execution trigger i Have error ORA-00060: Please help. Idon't have any ideas how can i fix it.enter image description here
CREATE OR replace TRIGGER up_sal
BEFORE UPDATE OF first_name, last_name ON employees
FOR EACH ROW
DECLARE
PRAGMA autonomous_transaction;
var_sal employees.salary%TYPE;
var_avg NUMBER;
var_emp_id NUMBER;
BEGIN
SELECT salary
INTO var_sal
FROM employees
WHERE first_name = :NEW.first_name
OR last_name = :NEW.last_name;
SELECT Avg(salary)
INTO var_avg
FROM employees
WHERE department_id IN( :OLD.department_id );
IF var_sal < var_avg THEN
var_emp_id := :OLD.employee_id;
UPDATE employees
SET salary = var_avg * 1.1
WHERE employee_id = var_emp_id;
COMMIT;
END IF;
END;
You can use:
CREATE TRIGGER up_sal
BEFORE UPDATE OF first_name, last_name ON employees
FOR EACH ROW
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
var_avg employees.salary%TYPE;
BEGIN
SELECT Avg(salary)
INTO var_avg
FROM employees
WHERE department_id IN( :OLD.department_id );
IF :NEW.salary < var_avg THEN
:NEW.salary := var_avg * 1.1;
END IF;
COMMIT;
END;
/
Which, for the sample data:
CREATE TABLE employees (
department_id NUMBER,
first_name VARCHAR2(20),
last_name VARCHAR2(20),
salary NUMBER
);
INSERT INTO employees (department_id, first_name, last_name, salary)
SELECT 1, 'Alice', 'Abbot', 90 FROM DUAL UNION ALL
SELECT 1, 'Betty', 'Baron', 95 FROM DUAL UNION ALL
SELECT 1, 'Carol', 'Count', 105 FROM DUAL UNION ALL
SELECT 1, 'Debra', 'Duke', 110 FROM DUAL;
Then if you do:
UPDATE employees
SET first_name = TRIM(first_name),
last_name = TRIM(last_name);
Then:
SELECT * FROM employees;
Outputs:
DEPARTMENT_ID
FIRST_NAME
LAST_NAME
SALARY
1
Alice
Abbot
110
1
Betty
Baron
110
1
Carol
Count
105
1
Debra
Duke
110
db<>fiddle here

converting output of a Query into xml

I have a table named emp with name, id, salary as column names.
My procedure takes id as input and it can be more than one id also. like('1','2','3',...)
Required: For each and every id the values should be returned as XML.
Supoose: For id=1 and id=2 as input my output should be
<row>
<name>name1</name>
<salary>salary1</salary>
</row>
<row>
<name>name2</name>
<salary>salary2</salary>
</row>
I am able to run the code to separate id's and get the output. But I am getting stuck at sending values as xml.
Code i tried:
procedure get_details(P_ID varchar2) as
begin
select * from emp where id in (
select regexp_substr(P_ID,'[^,]+', 1, level) from dual
connect BY regexp_substr(P_ID, '[^,]+', 1, level)
is not null);
end get_values;
Any input will be appreciated.
Edit regarding solutions: Both the solutions provided in db-fiddle are working perfectly fine.
Solution 1:
CREATE PROCEDURE get_details(
P_IDs IN varchar2
) IS
v_clob CLOB;
BEGIN
SELECT XMLELEMENT(
"root",
XMLAGG(
XMLELEMENT(
"row",
XMLFOREST(
name AS "name",
salary AS "salary"
)
)
)
).getClobVal()
INTO v_clob
FROM emp
where p_ids LIKE '%''' || id || '''%';
DBMS_OUTPUT.PUT_LINE( v_clob );
end get_details;
/
BEGIN
DBMS_OUTPUT.PUT_LINE( 'Your output:' );
get_details( '''1'',''3''' );
END;
/
Solution 2:
create or replace procedure get_details(p_id varchar2)
as
lo_xml_output clob;
begin
select to_clob(xmltype(cursor(select *
from emp
where id in
(select regexp_substr(p_id,'[^,]+', 1, level)
from dual
connect by regexp_substr(P_ID, '[^,]+', 1,level) is
not null))))
into lo_xml_output from dual;
DBMS_OUTPUT.PUT_LINE( lo_xml_output );
end get_details;
/
BEGIN
DBMS_OUTPUT.PUT_LINE('');
get_details('1,3');
END;
/
You can use the XML functions:
CREATE PROCEDURE get_details(
P_IDs IN varchar2
) IS
v_clob CLOB;
BEGIN
SELECT XMLELEMENT(
"root",
XMLAGG(
XMLELEMENT(
"row",
XMLFOREST(
name AS "name",
salary AS "salary"
)
)
)
).getClobVal()
INTO v_clob
FROM emp
where p_ids LIKE '%''' || id || '''%';
DBMS_OUTPUT.PUT_LINE( v_clob );
end get_details;
/
So, for the test data:
CREATE TABLE emp ( id, name, salary ) AS
SELECT 1, 'name1', 1000 FROM DUAL UNION ALL
SELECT 2, 'name2', 2000 FROM DUAL UNION ALL
SELECT 3, 'name3', 3000 FROM DUAL
The anonymous block:
BEGIN
get_details( '''1'',''3''' );
END;
/
Outputs:
<root><row><name>name1</name><salary>1000</salary></row><row><name>name3</name><salary>3000</salary></row></root>
db<>fiddle here
Another way will be by using xmltype,you can pass a query as a cursor to xmltype.
create or replace procedure get_details(p_id varchar2)
as
lo_xml_output clob;
begin
select to_clob(xmltype(cursor(select *
from emp
where id in
(select regexp_substr(p_id,'[^,]+', 1, level)
from dual
connect by regexp_substr(P_ID, '[^,]+', 1,level) is
not null))))
into lo_xml_output from dual;
DBMS_OUTPUT.PUT_LINE( lo_xml_output );
end get_details;
/
-- test
BEGIN
DBMS_OUTPUT.PUT_LINE('');
get_details('1,3');
END;
/
Db<>fiddle for your reference
P.S. we can also usedbms_xmlgen.getxml but then we need to build the SQL dynamically before passing it to dbms_xmlgen.getxml. you can see in the fiddle as well.

Adding conditionals to PL/SQL cursors

In the below PL/SQL program I have 2 cursors that are almost similar in functionality. I'm not sure, how I can retain the same functionality just by having one cursor and get rid of the get_all_employes cursor which doesn't have the where clause.
Thanks in advance for your time and help!
CREATE OR REPLACE PROCEDURE EMP_DETAILS(dept_id VARCHAR2) IS
CURSOR get_all_employes IS
SELECT EMP_ID, EMP_NAME, EMP_SAL, EMP_DEPT FROM EMP;
CURSOR get_employee_info IS
SELECT EMP_ID, EMP_NAME, EMP_SAL, EMP_DEPT FROM EMP WHERE EMP_DEPT = dept_id;
BEGIN
IF dept_id IS NULL THEN
FOR X IN get_all_employes;
DBMS_OUTPUT.PUT_LINE(X.EMP_ID || ' ' || X.EMP_NAME || ' ' || X.EMP_SAL || X.EMP_DEPT);
END LOOP;
ELSE
FOR Y IN get_all_employes;
DBMS_OUTPUT.PUT_LINE(Y.EMP_ID || ' ' || Y.EMP_NAME || ' ' || Y.EMP_SAL || Y.EMP_DEPT);
END LOOP;
END IF;
END EMP_DETAILS;
/
Use one cursor and include WHERE EMP_DEPT = dept_id OR dept_id IS NULL:
CREATE PROCEDURE EMP_DETAILS(
dept_id IN EMP.EMP_DEPT%TYPE
)
IS
CURSOR get_employee_info IS
SELECT EMP_ID,
EMP_NAME,
EMP_SAL,
EMP_DEPT
FROM EMP
WHERE EMP_DEPT = dept_id
OR dept_id IS NULL;
BEGIN
FOR Y IN get_employee_info LOOP
DBMS_OUTPUT.PUT_LINE(Y.EMP_ID || ' ' || Y.EMP_NAME || ' ' || Y.EMP_SAL || Y.EMP_DEPT);
END LOOP;
END EMP_DETAILS;
/
So, for the test data:
CREATE TABLE emp ( EMP_ID, EMP_NAME, EMP_SAL, EMP_DEPT ) AS
SELECT 1, 'a', 100, 1 FROM DUAL UNION ALL
SELECT 2, 'b', 200, 1 FROM DUAL UNION ALL
SELECT 3, 'c', 300, 2 FROM DUAL;
Then:
BEGIN
emp_details( NULL );
END;
/
Would output all the rows:
1 a 1001
2 b 2001
3 c 3002
and:
BEGIN
emp_details( 1 );
END;
/
would output only the rows for department 1:
1 a 1001
2 b 2001
db<>fiddle here
Try something like this (fyi your example code does not compile)
create or replace PROCEDURE EMP_DETAILS(dept_id VARCHAR2) IS
TYPE EmpCurTyp IS REF CURSOR;
emp_cv EmpCurTyp;
empRec EMP%ROWTYPE;
BEGIN
IF dept_id IS NULL THEN
open emp_cv for SELECT EMP_ID, EMP_NAME, EMP_SAL, EMP_DEPT FROM EMP;
ELSE
open emp_cv for SELECT EMP_ID, EMP_NAME, EMP_SAL, EMP_DEPT FROM EMP WHERE EMP_DEPT = dept_id;
END IF;
LOOP
FETCH emp_cv INTO empRec;
IF emp_cv%NOTFOUND
THEN
EXIT;
END IF;
DBMS_OUTPUT.PUT_LINE(empREc.EMP_ID || ' ' || empREc.EMP_NAME || ' ' || empREc.EMP_SAL || empREc.EMP_DEPT);
END LOOP;
CLOSE emp_cv;
END EMP_DETAILS;

Not enough values while compiling procedure

Im trying to get the compile the below program and im getting not enough values error. This program will give the first 5 maximum and mininum salary holding by employees.
select * from all_tables where table_name like 'EMP5%';
create or replace package ptest1 is
Type rec is record (emin number,salmin number,min_salary number,emax number,salmax number,max_salary number);
TYPE T71 is table of rec;
procedure min_max(inp number, oup out T71);
end;
/
create or replace package body ptest1 as
procedure min_max(inp number, oup out T71)
as
begin
select emin,salmin,min_salary,emax,salmax,max_salary
into oup
from
(select * from
(select empno emin,sal salmin,row_number() over(order by sal asc) min_salary from emp5 where sal is not null) where min_salary <= inp ) t1,
(select * from
(select empno emax,sal salmax,row_number() over(order by sal desc) max_salary from emp5 where sal is not null) where max_salary <= inp) t2
where t1.min_salary = t2.max_salary;
for i in oup.first..oup.last
loop
dbms_output.put_line('Employee number '||oup(i).emin);
end loop;
end;
end ptest1;
/

Resources