can plsql function return object - oracle

i have encountered a problem while writing the following code:
create or replace function getashish(dept varchar2) return emp3 as
emp5 emp3;
str varchar2(300);
begin
str := 'select e.last_name,l.city,e.salary from employees e join departments d
on e.department_id = d.department_id join locations l on d.location_id=l.location_id where
d.department_name = :dept';
execute immediate str bulk collect into emp5 using dept;
end;
emp 3 is table of an object as defined below:
create or replace type emp1 as object (lname varchar2(10),city varchar2(10),sal number(10));
create or replace type emp3 as table of emp1;
i am getting the following error while executing the function: SQL Error:
ORA-00932: inconsistent datatypes: expected - got -
thank you for your anticipated help and support.

try declaring emp5 as a datatype of emp1.
emp5 emp1 := emp1();

Please try the below, (added emp1() to your select query inside function and returned emp5)
CREATE OR REPLACE FUNCTION getashish(
dept VARCHAR2)
RETURN emp3
AS
emp5 emp3 := emp3();
str VARCHAR2(300);
BEGIN
str := 'select emp1(e.last_name,l.city,e.salary) from employees e join departments d
on e.department_id = d.department_id join locations l on d.location_id=l.location_id where
d.department_name = :dept';
EXECUTE immediate str bulk collect INTO emp5 USING dept;
RETURN emp5;
END;
/
and the caller block
SELECT * FROM TABLE( CAST(getashish('IT') AS emp3))
UNION
SELECT * FROM TABLE( CAST(getashish('FINANCE') AS emp3));
the function returns a Table, and hence cant be used in SELECT clause, since it is supposed to return one row only, if have to be used in SELECT. Hope you got the concept!
EDIT: Whatever I did!
create table employees
(last_name varchar2(10),salary number,department_id varchar2(10));
create table locations
(location_id varchar2(10),city varchar2(10));
drop table employees;
create table departments
(department_id varchar2(10),location_id varchar2(10),department_name varchar2(10));
insert into employees values ('ASHISH',6000000,'D1');
insert into employees values ('MAHESH',5000000,'D2');
insert into departments values('D1','L1','IT');
insert into departments values('D2','L2','FINANCE');
insert into locations values('L1','Gurgoan');
insert into locations values('L2','Chennai');
commit;
create or replace type emp1 as object (lname varchar2(10),city varchar2(10),sal number(10));
/
create or replace type emp3 as table of emp1;
/
CREATE OR REPLACE FUNCTION getashish(
dept VARCHAR2)
RETURN emp3
AS
emp5 emp3 := emp3();
str VARCHAR2(300);
BEGIN
str := 'select emp1(e.last_name,l.city,e.salary) from employees e join departments d
on e.department_id = d.department_id join locations l on d.location_id=l.location_id where
d.department_name = :dept';
EXECUTE immediate str bulk collect INTO emp5 USING dept;
RETURN emp5;
END;
/
SELECT * FROM TABLE( CAST(getashish('IT') AS emp3))
UNION
SELECT * FROM TABLE( CAST(getashish('FINANCE') AS emp3));
SQL> SELECT * FROM TABLE( CAST(getashish('IT') AS emp3))
2 UNION
3 SELECT * FROM TABLE( CAST(getashish('FINANCE') AS emp3));
LNAME CITY SAL
---------- ---------- ----------
ASHISH Gurgoan 6000000
MAHESH Chennai 5000000

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>

Hackerrank Question PLSQL Employees not assigned to any department

Has someone completed the hackerrank PLSQL for the practice "Employees not assigned to any department"? Hackerrank can be a little tricky with the output and I am really stuck on this one. Can you please give a look at the code and let me know if I am missing anything?
The problem is as follows:
Write a PLSQL block which should assign department_id and department_manager to the Active employees who does not belong to any department. Department which you assign to the Active employees should not have any employees tagged to it. (Please check the attached image for the Schema tables and the Output format)
This is the code I have:
SET SERVEROUTPUT ON;
DECLARE
DEPTID DEPT.DEPT_ID%TYPE;
MGRID EMP.MGR_ID%TYPE;
BEGIN
SELECT DEPT_ID, DEPT_HEAD
INTO DEPTID, MGRID
FROM DEPT D
WHERE DEPT_ID NOT IN (SELECT DEPT_ID FROM EMP E WHERE D.DEPT_ID= E.DEPT_ID);
UPDATE EMP E
SET DEPT_ID = DEPTID, MGR_ID= MGRID
WHERE DEPT_ID IS NULL
AND EMP_STATUS='Active';
DECLARE
CURSOR C IS
SELECT E.EMP_ID, E.EMP_FNAME, E.EMP_LNAME, E.EMP_STATUS, D.DEPT_NAME, T2.EMP_FNAME MNG_FNAME, T2.EMP_LNAME MNG_LNAME
FROM EMP E
INNER JOIN DEPT D ON D.DEPT_ID=E.DEPT_ID
INNER JOIN EMP T2 ON E.MGR_ID = T2.EMP_ID
WHERE E.EMP_STATUS='Active'
AND E.DEPT_ID=DEPTID;
BEGIN
DBMS_OUTPUT.PUT_LINE('emp_Id'||'emp_fname'||'emp_Lname'||'emp_Status'||'Dept_name'||'Mgr_name');
FOR I IN C LOOP
DBMS_OUTPUT.PUT_LINE(i.emp_id||''||i.emp_fname||''||i.emp_lname||''||i.emp_status||''||i.dept_name||''||concat(i.mng_fname||'',i.mng_lname));
END LOOP;
END;
END;
/

How to change alias to table name automatically in case of oracle?

We are currently analyzing hundreds of queries.
E.g
SELECT a.id,
a.name,
a.hobby,
b.desc
FROM tablename a,
table2 b
WHERE a.id = b.id;
or
SELECT id,
NAME,
hobby
FROM tablename;
above these can be change like below?
SELECT tablename.id,
tablename.name,
tablename.hobby,
table2.desc
FROM tablename tablename,
table2 table2
WHERE tablename.id = table2.id;
or
SELECT tablename.id,
tablename.name,
tablename.hobby
FROM tablename tablename;
Do you know any tools or methods that have the ability to change them?
Here is the Ananymous block for another query :
declare
v_query varchar2(4000) :='SELECT a.id,
a.name,
a.hobby,
b.desc
FROM tablename a,
table2 b
WHERE a.id = b.id';
tab_list varchar2(4000);
v_newquery varchar2(4000);
v_tab_alias varchar2(100);
v_tabname varchar2(500);
v_alias varchar2(40);
tab_cnt NUMBER :=0;
begin
-- table list
select replace(REGEXP_SUBSTR(regexp_replace(v_query,'FROM|WHERE','#'),'[^#]+',1,2)||chr(10),chr(10),null)
into tab_list
from dual;
-- no of tables
select REGEXP_COUNT(TRIM(REGEXP_SUBSTR(regexp_replace(v_query,'FROM|WHERE','#'),'[^#]+',1,2)),',')+1
into tab_cnt
from dual;
For i in 1..tab_cnt
LOOP
select TRIM(REGEXP_SUBSTR(tab_list,'[^,]+',1,i))
into v_tab_alias
from dual;
-- replace alias tablename for the column list
select TRIM(REGEXP_SUBSTR(v_tab_alias,'[^ ]+',1,1))
into v_tabname
from dual;
select TRIM(REGEXP_SUBSTR(v_tab_alias,'[^ ]+',1,2))
into v_alias
from dual;
select regexp_replace(v_query,v_alias||'\.',v_tabname||'.')
into v_newquery
from dual;
v_query:= v_newquery;
-- replace alias tablename in FROM clause
select regexp_replace(v_query,v_alias||'\,',v_tabname||',')
into v_query
from dual;
END LOOP;
-- replace alias last tablename before WHERE clause
select regexp_replace(v_query,v_tab_alias,v_tabname||' '||v_tabname)
into v_query
from dual;
DBMS_OUTPUT.PUT_LINE(v_query);
END;

How to create dynamic sql for with sys_refcursor in oracle

I have function which returns sys_refcursor I give you my code's example.
function myfunc( p_city IN VARCHAR2,
p_order IN VARCHAR2)
RETURN SYS_REFCURSOR IS
v_result SYS_REFCURSOR;
begin
OPEN v_result FOR WITH all_prb AS(
select * from tableA ta inner join tableB tb)
'select * from all_prb ff where (:p_city is null or (LOWER(ff.city) like ''%''||:p_city||''%'') ) order by ' || p_order || 'asc' using p_city,p_city;
return v_result;
end myfunc;
and when i am trying to compile it i have ORA-00928: missing SELECT keyword and this error targets line where i have dynamic sql 'select * from all_prb ff where ...'
How can i fix it and how can i write correct dynamic sql ? I am writing dynamic sql for ordering .
I'm not sure why you're bothering with the with clause, it's simpler without a CTE; you just need to identify which table the city column is in:
function myfunc(p_city IN VARCHAR2,
p_order IN VARCHAR2)
RETURN SYS_REFCURSOR IS
v_result SYS_REFCURSOR;
begin
OPEN v_result FOR
'select * from tableA ta
inner join tableB tb on tb.some_col = ta.some_col
where :p_city is null or LOWER(ta.city) like ''%''||:p_city||''%''
order by ' || p_order || ' asc'
using p_city, p_city;
return v_result;
end myfunc;
/
I've guessed it's table A, just change the alias if it's the other one. You also need to specify the join condition between the two tables. (Also noticed I added a space before asc to stop that being concatenated into the order-by string).
This compiles without errors; when run I get ORA-00942: table or view does not exist which is reasonable. If I create dummy data:
create table tablea (some_col number, city varchar2(30));
create table tableb (some_col number);
insert into tablea values (1, 'London');
insert into tablea values (2, 'Londonderry');
insert into tablea values (3, 'East London');
insert into tablea values (4, 'New York');
insert into tableb values (1);
insert into tableb values (2);
insert into tableb values (3);
insert into tableb values (4);
then calling it gets:
select myfunc('lond', 'city') from dual;
SOME_COL CITY SOME_COL
---------- ------------------------------ ----------
3 East London 3
1 London 1
2 Londonderry 2
If you really want to stick with the CTE for some reason then (as #boneist said) that needs to be part of the dynamic statement:
OPEN v_result FOR
'with all_prb as (
select * from tableA ta
inner join tableB tb on tb.some_col = ta.some_col
)
select * from all_prb ff
where :p_city is null or LOWER(ff.city) like ''%''||:p_city||''%''
order by ' || p_order || ' asc'
using p_city, p_city;

PL\SQL Hot do I convert select value to bit flag?

I would like to have pl-sql like the following:
SELECT ID FROM some-table WHERE
(SELECT MAX(some-expression) FROM another-table = 1) OR
(some-table.ID IN SELECT (SELECT ID FROM one-more-table))
This pseudo-query would select all IDs from some-table if maximum value of some-expression equals 1 (or filter IDs by one-more-table values otherwise)
How do I properly implement this in PL-SQL ?
Thank you in advance!
Please find the below pl-sql:
DECLARE
v_Column1 NUMBER(4);
v_deptno NUMBER(2);
BEGIN
SELECT MAX(A.DEPTNO)
INTO v_deptno
FROM (SELECT DEPTNO FROM DEPT) A;
SELECT A.EMPNO
INTO v_Column1
FROM EMP A
WHERE A.DEPTNO=v_deptno
OR
A.EMPNO IN ( SELECT EMPNO FROM EMP_2);
END;
/

Resources