use pipelined function to get result set from multi-tables join - oracle

i want to create a function to get selectivity of a table, the input parameter is owner and table name, i searched and find pipelined function can be used here, i used the following codes but i get error for creating function,
create or replace type ROW_TYPE as object
(
column_name varchar2(128),
num_rows number,
num_distinct number
);
/
create or replace type TABLE_TYPE as table of ROW_TYPE;
/
create or replace function getSelectivity(owner in varchar2, tab_name in varchar2)
return TABLE_TYPE
PIPELINED
IS
BEGIN
for myrow in(
select a.column_name,b.num_rows,a.num_distinct
from dba_tab_col_statistics a, dba_tables b
where a.owner = b.owner and a.table_name = b.table_name and a.owner=upper(owner) and
a.table_name =upper(tab_name)
)loop
pipe row(ROW_TYPE(myrow.column_name,myrow.num_rows,myrow.num_distinct));
end loop;
return;
end;
/
error:
SQL> show errors;
Errors for FUNCTION MOVIL.GETSELECTIVITY:
LINE/COL ERROR
-------- -------------------------------
9/88 PL/SQL: ORA-00918: undefined column
7/14 PL/SQL: SQL Statement ignored
12/20 PLS-00364: Invalid usage of Loop index variable 'MYROW'
12/2 PL/SQL: Statement ignored
but if i just query one single table, then everything is fine,
create or replace type ROW_TYPE as object
(
column_name varchar2(128),
num_distinct number
);
/
create or replace type TABLE_TYPE as table of ROW_TYPE;
/
create or replace function getSelectivity(owner in varchar2, tab_name in varchar2)
return TABLE_TYPE
PIPELINED
IS
BEGIN
for myrow in(
select a.column_name,a.num_distinct
from dba_tab_col_statistics a
where a.owner=upper(owner) and a.table_name =upper(tab_name)
)loop
pipe row(ROW_TYPE(myrow.column_name,myrow.num_distinct));
end loop;
return;
end;
/
does pipelined function do not support multi-tables joining ?

You can create a view on the tables like the below and use it in your function
create or replace view test_db_view as select
a.column_name,b.num_rows,a.num_distinct,
a.table_name,a.owner
from all_tab_col_statistics a, all_tables b
where a.owner = b.owner and a.table_name = b.table_name
create or replace function getSelectivity(owner in varchar2, tab_name in varchar2)
return TABLE_TYPE
PIPELINED
IS
BEGIN
for myrow in( select * from test_db_view a where a.owner=upper(owner) and
a.table_name =upper(tab_name)
)loop
pipe row(ROW_TYPE(myrow.column_name,myrow.num_rows,myrow.num_distinct));
end loop;
return;
end;
OR you can try using p_owner instead of owner in function getSelectivity parameter

Related

Writing Oracle stored procedure with Oracle table as Input parameter

How to write Oracle stored procedure with a table (X) as input parameter and that table X is used inside procedure to join with another table Y?
Table X will have thousands of records.
Not looking to pass table name as varchar and then using dynamic SQL (so, this option is out of picture)
From 19.6 you can create a SQL macro. This returns a string with your query fragment.
At parse time the database will do a find/replace of the table parameter with the table you've passed it:
create or replace function f ( tab dbms_tf.table_t )
return varchar2 sql_macro as
begin
return 'select * from tab
join ( select level rn from dual connect by level <= 2 )
on c1 = rn';
end f;
/
create table t1 (
c1 int
);
create table t2 (
c1 int
);
insert into t1 values ( 1 );
insert into t2 values ( 2 );
select * from f ( t1 );
C1 RN
1 1
select * from f ( t2 );
C1 RN
2 2
There's another approach you might find interesting: pass a cursor variable to pipelined table function, invoke it in SQL, allowing you literally pass the contents of the table (select * from...), bulk collect into collection, then join the collection with your other table!
DROP TYPE tickertype FORCE;
DROP TYPE tickertypeset FORCE;
DROP TABLE stocktable;
DROP TABLE tickertable;
CREATE TABLE stocktable
(
ticker VARCHAR2 (20),
trade_date DATE,
open_price NUMBER,
close_price NUMBER
)
/
BEGIN
FOR indx IN 1 .. 100
LOOP
INSERT INTO stocktable
VALUES ('STK' || indx,
SYSDATE,
indx,
indx + 15);
END LOOP;
COMMIT;
END;
/
CREATE TABLE tickertable
(
ticker VARCHAR2 (20),
pricedate DATE,
pricetype VARCHAR2 (1),
price NUMBER
)
/
CREATE TYPE tickertype AS OBJECT
(
ticker VARCHAR2 (20),
pricedate DATE,
pricetype VARCHAR2 (1),
price NUMBER
);
/
BEGIN
FOR indx IN 1 .. 100
LOOP
INSERT INTO tickertable
VALUES ('STK' || indx,
SYSDATE,
'O',
indx);
END LOOP;
COMMIT;
END;
/
CREATE TYPE tickertypeset AS TABLE OF tickertype;
/
CREATE OR REPLACE PACKAGE refcur_pkg
AUTHID DEFINER
IS
TYPE refcur_t IS REF CURSOR
RETURN stocktable%ROWTYPE;
TYPE dataset_tt IS TABLE OF stocktable%ROWTYPE;
END refcur_pkg;
/
CREATE OR REPLACE FUNCTION pipeliner (dataset refcur_pkg.refcur_t)
RETURN tickertypeset
PIPELINED
AUTHID DEFINER
IS
l_row_as_object tickertype
:= tickertype (NULL,
NULL,
NULL,
NULL);
l_dataset refcur_pkg.dataset_tt;
l_count PLS_INTEGER;
BEGIN
FETCH dataset BULK COLLECT INTO l_dataset;
CLOSE dataset;
/* Let's do a join with another table. */
SELECT COUNT (*) into l_count
FROM TABLE (l_dataset) st, tickertable tt
WHERE st.ticker = tt.ticker;
DBMS_OUTPUT.put_line ('Count = ' ||l_count);
l_row_as_object.ticker := 'ABC';
PIPE ROW (l_row_as_object);
RETURN;
END;
/
BEGIN
FOR rec
IN (SELECT * FROM TABLE (pipeliner (CURSOR (SELECT * FROM stocktable))))
LOOP
DBMS_OUTPUT.put_line (rec.ticker);
END LOOP;
END;
/
I see this output:
Count = 100
ABC
Create a table type in the SQL scope:
CREATE TYPE string_list AS TABLE OF VARCHAR2(5);
Then use that as the parameter for your stored procedure and join it to another table using a Table Collection Expression:
CREATE PROCEDURE test_proc(
p_list IN string_list
)
IS
v_cursor SYS_REFCURSOR;
v_string VARCHAR2(10);
BEGIN
OPEN v_cursor FOR
SELECT d.*
FROM DUAL d
INNER JOIN TABLE( p_list ) t
ON ( d.DUMMY = t.COLUMN_VALUE );
-- do something with the cursor.
LOOP
FETCH v_cursor into v_string;
EXIT WHEN v_cursor%NOTFOUND;
DBMS_OUTPUT.PUT_LINE( v_string );
END LOOP;
END;
/
Then you can call it:
BEGIN
test_proc( string_list( 'X', 'Y', 'Z' ) ) ;
END;
/
and it outputs:
X
db<>fiddle here

PLS-00306: wrong number or types of arguments in call to 'ADD_MONTHS'

I have created the below procedure to only retain the last two months data only and delete the rest one against a table in oracle, below is the procedure but i am getting exception, please advise how to overcome from this
create or replace package TEST_TABLE AS
PROCEDURE TEST_TABLE;
END TEST_TABLE;
create or replace PACKAGE BODY TEST_TABLE AS
PROCEDURE TEST_TABLE IS
BEGIN
FOR cc IN
(
SELECT partition_name, high_value
FROM user_tab_partitions
WHERE table_name = 'TEST_TABLE'
)
LOOP
BEGIN
IF sysdate >= ADD_MONTHS(cc.high_value,2) THEN
EXECUTE IMMEDIATE
'ALTER TABLE TEST_TABLE DROP PARTITION ' || cc.partition_name;
Dbms_Output.Put_Line('Dropping partition is completed.');
END IF;
END;
END LOOP;
EXCEPTION WHEN Others THEN Dbms_Output.Put_Line( SQLERRM );
END TEST_TABLE;
END TEST_TABLE;
The error that I am getting is:
Error(12,6): PL/SQL: Statement ignored
Error(12,20): PLS-00306: wrong number or types of arguments in call to 'ADD_MONTHS'
Firstly, It's insane to call table name, package name and procedure name all by TEST_TABLE as being done by you, as if there's no other name available. I've named them appropriately.
HIGH_VALUE cannot be directly used in DATE related functions as it's of LONG TYPE. There's a simple method to convert it to date using dynamic SQL(EXECUTE IMMEDIATE)
CREATE OR replace PACKAGE BODY PKG_test_table AS
PROCEDURE pr_test_table
IS
v_high_value DATE;
BEGIN
FOR cc IN (
SELECT partition_name,
high_value
FROM user_tab_partitions
WHERE table_name = 'TEST_TABLE'
) LOOP
BEGIN
EXECUTE IMMEDIATE 'BEGIN :v_high_val := '|| cc.high_value || '; END;'
USING OUT v_high_value;
IF
SYSDATE >= add_months(v_high_value,2)
THEN
EXECUTE IMMEDIATE 'ALTER TABLE TEST_TABLE DROP PARTITION '
|| cc.partition_name;
dbms_output.put_line('Dropping partition is completed.');
END IF;
END;
END LOOP;
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line(sqlerrm);
END pr_TEST_TABLE;
END PKG_test_table;
/
Calling the procedure
BEGIN
PKG_test_table.pr_test_table;
END;
/
Your procedure does not accept any parameter. You can't pass any arguments to it.
The HIGH_VALUE column from USER_TAB_PARTITIONS is a long data type, I'm not going copy code from another web site, but if you google "oracle convert high value to date" you should get some ideas on how to create a function that you can use to convert the 'long' to a date.
My reputation is too low to post this as a comment, so I added it as an answer, it should help though it is not a good answer :(
As the error says it all ADD_MONTHS takes a DATE and you are passing in as LONG.
Try something like this and it should be ok.
Example:
DECLARE
DT LONG(1000) := 'TO_DATE('||''''||'2018-08-01 00:00:00'||''''||',' ||''''|| 'SYYYY-MM-DD HH24:MI:SS'||''''||','||''''||'NLS_CALENDAR=GREGORIAN'||''''||')';
BEGIN
DBMS_OUTPUT.PUT_LINE(DT);
EXECUTE IMMEDIATE
'BEGIN
DBMS_OUTPUT.PUT_LINE(TO_CHAR(ADD_MONTHS('||DT||',2),'||''''||'YYYY-MM-DD HH24:MI:SS'||''''||
')); END;';
END;
Output:
TO_DATE('2018-08-01 00:00:00','SYYYY-MM-DD HH24:MI:SS','NLS_CALENDAR=GREGORIAN')
2018-10-01 00:00:00
Oracle does not allow functions over long such as cast, substr, add_months over long type however … read below.
Long type
describe user_tab_partitions;
...
SUBPARTITION_COUNT NUMBER
HIGH_VALUE LONG
HIGH_VALUE_LENGTH NUMBER
...
Function to convert long to varchar2
FUNCTION long_to_varchar2 ( p_table_owner IN VARCHAR2,p_table_name IN VARCHAR2, p_partition_name IN VARCHAR2) RETURN VARCHAR2
is
l_tmp long;
BEGIN
select high_value
into l_tmp
from all_tab_partitions
where table_owner = p_table_owner
and table_name = p_table_name
and partition_name = p_partition_name ;
RETURN l_tmp;
END long_to_varchar2;
3.Use your new function
select tpar."OWNER",tpar."TABLE_NAME",tpar."PART_MIN",tpar."PART_MIN_HV",tpar."PART_MAX",tpar."PART_MAX_HV",tpar."NR_PART"
,pkey.column_name as partitioned_by
,ptab.partitioning_type as partition_type
,ptab.status
from
(select p1.table_owner as owner
,p1.table_name
,pmin.partition_name as part_min
,to_date(substr(long_to_varchar2(p1.table_owner,p1.table_name,pmin.partition_name),11,10),'yyyy-mm-dd') as part_min_hv
,pmax.partition_name as part_max
,to_date(substr(long_to_varchar2(p1.table_owner,p1.table_name,pmax.partition_name),11,10),'yyyy-mm-dd') as part_max_hv
,p1.nr_part+1 as nr_part
from (select min(part.partition_position) as minp
,max(part.partition_position) as maxp
,count(*) as nr_part
,part.table_name
,part.table_owner
from all_tab_partitions part,
dba_tables tbls
where part.table_name=tbls.table_name
and part.table_owner=tbls.owner
and part.PARTITION_NAME <> 'P_CURRENT'
group by part.table_name, part.table_owner) p1
,all_tab_partitions pmin
,all_tab_partitions pmax
where p1.table_name = pmin.table_name
and p1.table_owner = pmin.table_owner
and p1.minp=pmin.partition_position
and p1.table_name = pmax.table_name
and p1.table_owner = pmax.table_owner
and p1.maxp = pmax.partition_position) tpar
,ALL_PART_KEY_COLUMNS pkey
,ALL_PART_TABLES ptab
where tpar.owner=pkey.owner
and tpar.table_name=pkey.name
and tpar.owner=ptab.owner
and tpar.table_name=ptab.table_name
and pkey.object_type='TABLE';
The only issue is that you will be doing an implicit varchar2 to date conversion and I see no way of doing it otherwise.

ORA-00932 facing while compiling the oracle function

I have made below code.
CREATE OR REPLACE TYPE CAL IS OBJECT(
EMPLOYEE_NAME VARCHAR2(30),
R_DATE DATE,
COMMENTS VARCHAR2(50)
);
CREATE OR REPLACE TYPE T_REC is table of cal;
create or replace function CALENDAR(v_team_name varchar2)
return t_rec
IS
v_rec t_rec;
v_COMM VARCHAR2(50);
v_name VARCHAR2(30);
BEGIN
FOR i in (Select ID,EMPLOYEE_NAME from EMPLOYEE where TEAM_NAME=v_team_name)
LOOP
v_name:=i.EMPLOYEE_NAME;
FOR k in (select EMP_ID, START_DT,END_DT-START_DT+1 DAYS,NAME,COMMENTS from EMP_ROTA where EMP_ID=i.ID)
LOOP
v_COMM:=k.NAME||', '||k.COMMENTS;
select t_rec(v_name,k.START_DT+level-1,v_COMM)
into v_rec
from dual connect by level < k.DAYS;
END LOOP;
END LOOP;
Return v_rec;
END;
Facing below error in compiling the function.
Compilation failed,line 14 (04:33:54)
PL/SQL: ORA-00932: inconsistent datatypes: expected UDT got CHARCompilation failed,line 14 (04:33:54)
PL/SQL: SQL Statement ignored
While using below query in function
select v_name,k.START_DT + level -1,v_COMM
into v_REC
from dual connect by level < k.DAYS;
Facing below error
PL/SQL: ORA-00947: not enough valuesCompilation failed,line 14 (04:50:49)
Trying small snippet of code stil facing ORA-00932.
create or replace function ROTA_CALENDAR
return t_CAL
IS
v_CAL t_CAL;
BEGIN
select t_cal('v_name',SYSDATE,'NAME')
into v_CAL
from dual;
Return v_CAL;
END;
what am I doing wrong?
I hope below snippet will help. Couple of things to take in consideration.
1 Don't use "" while any nomenclature
2 You have to select from Object type in the SELECT query as mentioned below.
3 Use BULK COLLECT instead of INTO only...
CREATE OR REPLACE
FUNCTION CALENDAR(
V_TEAM_NAME VARCHAR2)
RETURN t_rec
IS
v_rec t_rec;
v_COMM VARCHAR2(50);
v_name VARCHAR2(30);
BEGIN
FOR i IN
(
SELECT
EMPNO,
'AVRAJIT' ENAME
FROM
EMP
WHERE
JOB=v_team_name
)
LOOP
v_name:=i.ENAME;
FOR k IN
(
SELECT
EMPNO,
SYSDATE,
SYSDATE+1 DAYS,
ENAME,
JOB
FROM
EMP_V1
WHERE
EMPNO=i.EMPNO
)
LOOP
v_COMM:=k.ENAME||', '||k.JOB;
SELECT
CAL(V_NAME,SYSDATE+1,V_COMM) BULK COLLECT
INTO
v_rec
FROM
dual
CONNECT BY level < 10;
END LOOP;
END LOOP;
RETURN v_rec;
END;
-------------------------------OUTPUT-------------------------------------------
SELECT OBJECT_NAME,STATUS FROM ALL_OBJECTS
WHERE OBJECT_NAME = 'CALENDAR';
OBJECT_NAME STATUS
CALENDAR VALID
-------------------------------OUTPUT-------------------------------------------

passing in table name as plsql parameter

I want to write a function to return the row count of a table whose name is passed in as a variable. Here's my code:
create or replace function get_table_count (table_name IN varchar2)
return number
is
tbl_nm varchar(100) := table_name;
table_count number;
begin
select count(*)
into table_count
from tbl_nm;
dbms_output.put_line(table_count);
return table_count;
end;
I get this error:
FUNCTION GET_TABLE_COUNT compiled
Errors: check compiler log
Error(7,5): PL/SQL: SQL Statement ignored
Error(9,8): PL/SQL: ORA-00942: table or view does not exist
I understand that tbl_nm is being interpreted as a value and not a reference and I'm not sure how to escape that.
You can use dynamic SQL:
create or replace function get_table_count (table_name IN varchar2)
return number
is
table_count number;
begin
execute immediate 'select count(*) from ' || table_name into table_count;
dbms_output.put_line(table_count);
return table_count;
end;
There is also an indirect way to get number of rows (using system views):
create or replace function get_table_count (table_name IN varchar2)
return number
is
table_count number;
begin
select num_rows
into table_count
from user_tables
where table_name = table_name;
return table_count;
end;
The second way works only if you had gathered statistics on table before invoking this function.

Oracle: Returning multiple rows in a function

I am trying to create a function which would return multiple rows.
Following is my function and type
create or replace type emp_type
(
first_name varchar2(20)
, last_name varchar2(20)
, depart_name varchar2(20)
)
/
create or replace function get_employee
(loc in number)
return emp_type
as
emp_record emp_type;
begin
select a.first_name, a.last_name, b.department_name into emp_record.first_name,
emp_record.last_name,emp_record.depart_name
from employees a, departments b
where a.department_id=b.department_id and location_id=loc;
return(emp_record);
end;
And I used
select get_employee(5) from dual;
I am getting "exact fetch returns more than requested number of rows " error.
Later when I used rownum<2 in the select query I got "Reference to uninitialized composite".
Could you please help?
Thanks in Advance
If you want to return a sys_refcursor, there is no reason to declare the object type or to try to return an object type. Just return a sys_refcursor.
create or replace function get_employee
(p_loc in number)
return sys_refcursor
as
l_rc sys_refcursor;
begin
open l_rc
for select a.first_name, a.last_name, b.department_name
from employees a,
departments b
where a.department_id=b.department_id
and location_id=p_loc;
return l_rc;
end;

Resources