Performance Tuning In Plsql Function Query - sql

I have plsql function. And this function has query like as below:
select colum_name
from table_name
where filter_coloumn_name in ( select filter_coloumn_name from table_name_2);
My function is used by other query to return value from query. This query which use this function take more time. Even the site (front screen) down. Because table_name_2 has three million record. So that i must use some performance tuning method in my function. I change my function query like as below:
cursor my_cursor IS
select filter_coloumn_name from table_name_2;
TYPE cursor_array_type IS TABLE OF my_cursor%ROWTYPE INDEX BY BINARY_INTEGER;
m cursor_array_type;
TYPE cursor_table_type IS TABLE OF VARCHAR2(20) INDEX BY BINARY_INTEGER;
cursor_table_object cursor_table_type;
fetch_size NUMBER := 5000;
index_var number;
begin
index_var := 1;
open my_cursor;
loop
FETCH my_cursor BULK COLLECT
into m LIMIT fetch_size;
exit when my_cursor %notfound;
for i in 1 .. m.count loop
cursor_table_object (index_var) := m(i).filter_coloumn_name;
index_var := index_var + 1;
end loop;
end loop;
Close my_cursor;
select colum_name
from table_name
where filter_coloumn_name in (cursor_table_object);
select colum_name
from table_name
where filter_coloumn_name in (cursor_table_object);
Namely, i want fetch all values at one time, and then i want use this table object like as above. But i can't use table object with in condition expression in sql query.
I take PLS-00382: expression is of wrong type error.
I want to use this table object like as below:
select colum_name
from table_name
where filter_coloumn_name in ('bla', 'bla bla', 'bla bla bla');
Should i convert table object to the array ?

A suggestion, based on what you've said so far: how about using a table function? It makes it possible to use something like statement you wanted to use, i.e.
select colum_name from table_name where filter_coloumn_name in (cursor_table_object);
but which returns
PLS-00382: expression is of wrong type
Here's an example based on Scott's schema. Have a look:
SQL> create or replace function f_test
2 return sys.odcinumberlist
3 is
4 -- Cursor's SELECT should contain your 3-million-rows table
5 cursor cur_r is select deptno from dept where deptno < 30;
6 l_ret sys.odcinumberlist;
7 begin
8 open cur_r;
9 fetch cur_r bulk collect into l_ret;
10 close cur_r;
11 return l_ret;
12 end;
13 /
Function created.
SQL> -- How to use it?
SQL> select e.ename
2 from emp e join table(f_test) x on x.column_value = e.deptno;
ENAME
----------
MILLER
KING
CLARK
ADAMS
SCOTT
FORD
JONES
SMITH
8 rows selected.
SQL>

You must use exists instead of in, also table_name_2 must have index over filter_coloumn_name field.
Using cursor only what you get is full access to a big table.

Related

Store a list of certain numbers globally and use it as the value of parameter in a PL/SQL function

I have certain numbers eg., (1,2,3,7,8) which need to be stored in an object (let's say, l_list) and this l_list will used as the value of the parameter in a function while calling a select query.
Storing data globally:
l_list := (1,2,3,7,8);
Creating a function:
create or replace function f_get_result (p_list number)
return varchar2
as
l_result varchar2(100);
Begin
select l_code
into l_result
from table_1;
return l_result;
end f_get_result;
Calling the function in select query:
select f_get_result(l_list) from dual;
Till now I have tried to define the datatype of the parameter as type table and varray. But, it didn't work.
Here is the code which I used:
-- creating a type ---
CREATE OR REPLACE TYPE num_array AS TABLE OF varchar2(100);
-- the function ---
create or replace function fn_id_exp(p_branch in num_array)
return varchar2
is
txt varchar2(1000);
txt_1 varchar2(1000);
begin
for i in 1..p_branch.count loop
select branch_name into txt_1 from tbl_branch
where branch_code = p_branch(i);
txt := txt ||txt_1||chr(10);
end loop;
return txt;
end;
-- the select query ---
select fn_id_exp(num_array('100','200')) from dual;
expectation: select fn_id_exp(l_list) from dual; , need to use this much, num_array should not be there,
where l_list is an object which is storing certain numbers like (100,200,300).
Please, help me in this, I am bit new in this. Can somebody, give me an an appropriate example regards this?
Note: It doesn't matter where and what datatype these number are getting stored. Requirement is just to call all the numbers(1,2,3,7,8) in this:
select fn_id_exp(l_list) from dual;
Another option, which doesn't require you to use your own type, is to use Oracle's built-in types.
Here's an example; function returns employees that work in departments passed as a collection.
SQL> create or replace function f_test (par_deptno in sys.odcinumberlist)
2 return sys.odcivarchar2list
3 is
4 retval sys.odcivarchar2list;
5 begin
6 select ename
7 bulk collect into retval
8 from emp
9 where deptno in (select * from table(par_deptno));
10
11 return retval;
12 end;
13 /
Function created.
Testing:
SQL> select * from table(f_test(sys.odcinumberlist(10, 20)));
COLUMN_VALUE
--------------------------------------------------------------------------------
MILLER
KING
CLARK
FORD
ADAMS
SCOTT
JONES
SMITH
8 rows selected.
SQL>
Note that such a result might be more convenient than returning a string which "mimics" rows in a table (whose values are separated by line feed character), as you'd still might need to split them into rows if you wanted to further process the result.
You said:
expectation: select fn_id_exp(l_list) from dual; , need to use this much, num_array should not be there
But - why not? What's wrong with num_array (or whichever type name you'd use)? That's the syntax, you should follow it if you want to get the result. Silly requirements, in my opinion.
Anyway: you could pass a string of e.g. comma-separated numeric values such as in this example; you'll have to split them to rows. See if it helps.
SQL> create or replace function f_test (par_deptno in varchar2)
2 return sys.odcivarchar2list
3 is
4 retval sys.odcivarchar2list;
5 begin
6 select ename
7 bulk collect into retval
8 from emp
9 where deptno in (select regexp_substr(par_deptno, '[^,]+', 1, level)
10 from dual
11 connect by level <= regexp_count(par_deptno, ',') + 1
12 );
13
14 return retval;
15 end;
16 /
Function created.
Testing:
SQL> select * from table(f_test('10, 20'));
COLUMN_VALUE
--------------------------------------------------------------------------------
SMITH
JONES
CLARK
SCOTT
KING
ADAMS
FORD
MILLER
8 rows selected.
SQL>
Use a collection:
CREATE TYPE number_array AS TABLE OF NUMBER;
Then:
CREATE FUNCTION fn_id_exp(
p_branch in number_array
) return varchar2
IS
txt varchar2(1000);
BEGIN
SELECT LISTAGG(branch_name, CHR(10)) WITHIN GROUP (ORDER BY rn)
INTO txt
FROM (SELECT ROWNUM AS rn,
COLUMN_VALUE AS id
FROM TABLE(p_branch)) i
INNER JOIN tbl_branch b
ON (i.id = b.branch_code);
RETURN txt;
END;
/
Then:
select fn_id_exp(number_array(100,200)) from dual;
Given the sample data:
CREATE TABLE tbl_branch (branch_code, branch_name) AS
SELECT 100, 'Leafy Stem' FROM DUAL UNION ALL
SELECT 200, 'Big Twig' FROM DUAL;
Outputs:
FN_ID_EXP(NUMBER_ARRAY(100,200))
Leafy StemBig Twig
However
It may be simpler to not use a function and just use a query:
SELECT branch_name
FROM tbl_branch
WHERE branch_code IN (100, 200)
ORDER BY branch_code;
or, if you want to use the array::
SELECT branch_name
FROM tbl_branch
WHERE branch_code MEMBER OF number_array(100, 200)
ORDER BY branch_code;
or, pass in an array as a bind parameter (via JDBC, etc.) then:
SELECT branch_name
FROM tbl_branch
WHERE branch_code MEMBER OF :your_array
ORDER BY branch_code;
Which output:
BRANCH_NAME
Leafy Stem
Big Twig
fiddle
Update
If you want a static array then you can create a package:
CREATE PACKAGE static_data IS
FUNCTION getList RETURN NUMBER_ARRAY;
END;
/
With the body:
CREATE PACKAGE BODY static_data IS
list NUMBER_ARRAY;
FUNCTION getList RETURN NUMBER_ARRAY
IS
BEGIN
RETURN list;
END;
BEGIN
list := NUMBER_ARRAY(100, 200);
END;
/
then you can use it in your query:
select fn_id_exp(static_data.getlist()) from dual;
or
SELECT branch_name
FROM tbl_branch
WHERE branch_code MEMBER OF static_data.getlist()
ORDER BY branch_code;
fiddle

Get column names of the result of a query Oracle SQL

I need a query that will get the column names of the result of another query. The other query can be anything - I can't make any assumptions about it but it will typically be some SELECT statement.
For example, if I have this table Members
Id | Name | Age
---|------|----
1 | John | 25
2 | Amir | 13
And this SELECT statement
SELECT Name, Age FROM Members
Then the result of the query I'm trying to write would be
Name
Age
In SQL Server, there is a function - sys.dm_exec_describe_first_result_set - that does this but I can't find an equivalent in Oracle.
I tried to use this answer but I can't use CREATE TYPE statements because of permissions issues and I probably can't use CREATE FUNCTION statements for the same reason.
Suppose you have a query like this:
select *
from (select deptno, job, sal from scott.emp)
pivot (avg(sal) as avg_sal for job in
('ANALYST' as analyst, 'CLERK' as clerk, 'SALESMAN' as salesman)
)
order by deptno
;
This produces the result:
DEPTNO ANALYST_AVG_SAL CLERK_AVG_SAL SALESMAN_AVG_SAL
---------- --------------- ------------- ----------------
10 1300
20 3000 950
30 950 1400
Notice the column names (like ANALYST_AVG_SAL) - they don't appear exactly in that form anywhere in the query! They are made up from two separate pieces, put together with an underscore.
Now, if you were allowed to create views (note that this does not create any data in your database - it just saves the text of a query), you could do this:
Create the view (just add the first line of code to what we already had):
create view q201028_vw as
select *
from (select deptno, job, sal from scott.emp)
pivot (avg(sal) as avg_sal for job in
('ANALYST' as analyst, 'CLERK' as clerk, 'SALESMAN' as salesman)
)
order by deptno
;
(Here I assumed you have some way to identify the query, an id like Q201028, and used that in the view name. That is not important, unless you need to do this often and for a large number of queries at the same time.)
Then you can find the column names (and also their order, and - if needed - their data type, etc.) by querying *_TAB_COLUMNS. For example:
select column_id, column_name
from user_tab_columns
where table_name = 'Q201028_VW'
order by column_id
;
COLUMN_ID COLUMN_NAME
---------- --------------------
1 DEPTNO
2 ANALYST_AVG_SAL
3 CLERK_AVG_SAL
4 SALESMAN_AVG_SAL
Now you can drop the view if you don't need it for anything else.
As an aside: The "usual" way to "save" queries in the database, in Oracle, is to create views. If they already exist as such in your DB, then all you need is the last step I showed you. Otherwise, were is the "other query" (for which you need to find the columns) coming from in the first place?
I would use the dbms_sql package and the following code example should show you how to start:
DECLARE
cursorID INTEGER;
status INTEGER;
colCount INTEGER;
rowCount INTEGER;
description dbms_sql.desc_tab;
colType INTEGER;
stringValue VARCHAR2(32676);
sqlCmd VARCHAR2(32767);
BEGIN
-- open cursor
cursorID := dbms_sql.open_cursor;
-- parse statement
dbms_sql.parse(cursorID, 'select * from user_tables', dbms_sql.native);
-- describe columns
dbms_sql.describe_columns(cursorID, colCount, description);
-- cursor close
dbms_sql.close_cursor(cursorID);
-- open cursor
cursorID := dbms_sql.open_cursor;
-- assemble a new select only using up to 5 the "text" columns
FOR i IN 1 .. description.COUNT LOOP
IF (i > 5) THEN
EXIT;
END IF;
IF (description(i).col_type IN (1, 112)) THEN
IF (sqlCmd IS NOT NULL) THEN
sqlCmd := sqlCmd || ', ';
END IF;
sqlCmd := sqlCmd || description(i).col_name;
END IF;
END LOOP;
sqlCmd := 'SELECT ' || sqlCmd || ' FROM user_tables';
dbms_output.put_line(sqlCmd);
-- parse statement
dbms_sql.parse(cursorID, sqlCmd, dbms_sql.native);
-- describe columns
dbms_sql.describe_columns(cursorID, colCount, description);
-- define columns
FOR i IN 1 .. description.COUNT LOOP
dbms_sql.define_column(cursorID, i, stringValue, 4000);
END LOOP;
-- execute
status := dbms_sql.execute(cursorID);
-- fetch up to 5 rows
rowCount := 0;
WHILE (dbms_sql.fetch_rows(cursorID) > 0) LOOP
rowCount := rowCount + 1;
IF (rowCount > 5) THEN
EXIT;
END IF;
dbms_output.put_line('row # ' || rowCount);
FOR i IN 1 .. description.COUNT LOOP
dbms_sql.column_value(cursorID, i, stringValue);
dbms_output.put_line('column "' || description(i).col_name || '" = "' || stringValue || '"');
END LOOP;
END LOOP;
-- cursor close
dbms_sql.close_cursor(cursorID);
END;
/
As astentx suggested, you can use a common table expression function to package the PL/SQL code into a SQL statement. This solution is just a single SQL statement, and requires no non-default privileges and does not create any permanent objects.
(The only downside is that not all SQL tools understand these kinds of WITH clauses, and they may throw an error expecting a different statement terminator.)
SQL> create table members(id number, name varchar2(100), age number);
Table created.
SQL> with function get_result_column_names(p_sql varchar2) return sys.odcivarchar2list is
2 v_cursor_id integer;
3 v_col_cnt integer;
4 v_columns dbms_sql.desc_tab;
5 v_column_names sys.odcivarchar2list := sys.odcivarchar2list();
6 begin
7 v_cursor_id := dbms_sql.open_cursor;
8 dbms_sql.parse(v_cursor_id, p_sql, dbms_sql.native);
9 dbms_sql.describe_columns(v_cursor_id, v_col_cnt, v_columns);
10
11 for i in 1 .. v_columns.count loop
12 v_column_names.extend;
13 v_column_names(v_column_names.count) := v_columns(i).col_name;
14 end loop;
15
16 dbms_sql.close_cursor(v_cursor_id);
17
18 return v_column_names;
19 exception when others then
20 dbms_sql.close_cursor(v_cursor_id);
21 raise;
22 end;
23 select *
24 from table(get_result_column_names(q'[select name, age from members]'));
25 /
COLUMN_VALUE
--------------------------------------------------------------------------------
NAME
AGE

Using define in simple sql statement

Is there a way of defining a section of SQL as a constant for repeated use in a simple oracle SQL statement. In a similar way that can be done in a script and in a similar way to the WITH statement?
Something like
Define meat as "foodtype<>'fruit' and foodtype<>'veg'"
select * from meals where meat
In SQL*Plus you could use DEFINE.
For example,
SQL> define l_str = 'ename = ''SCOTT'''
SQL>
SQL> select empno, deptno from emp where &l_str;
old 1: select empno, deptno from emp where &l_str
new 1: select empno, deptno from emp where ename = 'SCOTT'
EMPNO DEPTNO
---------- ----------
7788 20
SQL>
NOTE DEFINE is a SQL*Plus command. Define sets a user variable or displays its value.
However, not possible in plain SQL as the query won't parse and would throw an error. Since, during parse time, Oracle would expect it to be static, you cannot have it dynamic.
You can use Dynamic SQL in PL/SQL, such that you could have it as a string variable and use it repeatedly in the scope of the session.
For example,
SQL> DECLARE
2 var_str VARCHAR2(1000);
3 var_str1 VARCHAR2(1000);
4 v_empno emp.empno%type;
5 BEGIN
6 var_str := 'ename = ''SCOTT''';
7 var_str1 := 'select empno FROM emp WHERE '|| var_str;
8 EXECUTE IMMEDIATE var_str1 INTO v_empno;
9
10 dbms_output.put_line(v_empno);
11
12 END;
13 /
7788
PL/SQL procedure successfully completed.
SQL>
As per your question it seems that the condition should be dynamic. It can be acheived in PLSQL Block like below
DECLARE
v1 varchar2(100) := ' foodtype <> ''fruit'' and foodtype <> ''Veg''';
mealstyp meals%ROWTYPE;
BEGIN
EXECUTE IMMEDIATE 'select * from meals where'||v1 into mealstyp;
LOOP
--your type record
END LOOP;
END

Array in IN() clause oracle PLSQL

I am passing String array(plcListchar) to Stored Procedure, i would like to use this String array in IN() clause.
i can not use plcListchar directly in IN() clause.
Let me show how i am crating plcListchar string array in JAVA.
String array[] = {"o", "l"};
ArrayDescriptor des = ArrayDescriptor.createDescriptor("CHAR_ARRAY", con);
ARRAY array_to_pass = new ARRAY(des,con,array);
callStmtProductSearch.setArray(4, array_to_pass);
for crating CHAR_ARRAY,
create or replace type CHAR_ARRAY as table of varchar2;
i want use plcListchar in IN clause. the following is my Stored Procedure.
CREATE OR REPLACE PROCEDURE product_search(
status IN varchar2,
plcList IN varchar2,
i_culture_id IN number,
plcListchar IN CHAR_ARRAY,
status_name OUT varchar2,
culture_code OUT varchar2)
AS
CURSOR search_cursor IS
SELECT p.status_name, p.culture_code
FROM PRISM_ITEM_cultures#prism p
WHERE p.publishable_flag=1
AND p.isroll =0
AND status = '-1'
AND p.plc_status IN (   );
BEGIN
OPEN search_cursor;
FETCH search_cursor INTO status_name, culture_code ;
CLOSE search_cursor;
END;
Could you please suggest me how to use, if you like to suggest any other logic, it is great.
Assuming that your collection is defined in SQL, not just in PL/SQL, you can use the TABLE operator (the definition you posted isn't syntactically valid-- you'd need to specify a length for the VARCHAR2)
AND p.plc_status IN (SELECT column_value
FROM TABLE( plcListchar ))
Since I don't have your tables, an example using the SCOTT schema
SQL> create type ename_tbl is table of varchar2(30);
2 /
Type created.
SQL> ed
Wrote file afiedt.buf
1 declare
2 l_enames ename_tbl := ename_tbl( 'KING', 'SMITH' );
3 begin
4 for i in (select *
5 from emp
6 where ename in (select column_value
7 from table( l_enames )))
8 loop
9 dbms_output.put_line( 'ENAME = ' || i.ename );
10 end loop;
11* end;
SQL> /
ENAME = KING
ENAME = SMITH
PL/SQL procedure successfully completed.

Can an SQL procedure return a table?

Can an Oracle SQL procedure return a table? I'm currently using a dbms_output to print out the outputs of two cursors which are in a loop, although this would look nicer if it was returning two columns instead. Would that be possible within a procedure?
A PL/SQL function can return a nested table. Provided we declare the nested table as a SQL type we can use it as the source of a query, using the the TABLE() function.
Here is a type, and a nested table built from it:
SQL> create or replace type emp_dets as object (
2 empno number,
3 ename varchar2(30),
4 job varchar2(20));
5 /
Type created.
SQL> create or replace type emp_dets_nt as table of emp_dets;
2 /
Type created.
SQL>
Here is a function which returns that nested table ...
create or replace function get_emp_dets (p_dno in emp.deptno%type)
return emp_dets_nt
is
return_value emp_dets_nt;
begin
select emp_dets(empno, ename, job)
bulk collect into return_value
from emp
where deptno = p_dno;
return return_value;
end;
/
... and this is how it works:
SQL> select *
2 from table(get_emp_dets(10))
3 /
EMPNO ENAME JOB
---------- ------------------------------ --------------------
7782 CLARK MANAGER
7839 KING PRESIDENT
7934 MILLER CLERK
SQL>
SQL Types offer us a great deal of functionality, and allow us to build quite sophisticated APIs in PL/SQL. Find out more.
I think that you can use Oracle Cursor for this (if your Oracle version supports it):
PROCEDURE myprocedure(
mycursor OUT SYS_REFCURSOR )
AS
BEGIN
OPEN mycursor FOR SELECT * FROM mytable;
END;
END;
This may also help:
DECLARE
TYPE t_emptbl IS TABLE OF scott.emp%rowtype;
v_emptbl t_emptbl;
ret_val t_emptbl;
--
Function getEmployeeList Return t_emptbl
IS
BEGIN
SELECT * bulk collect INTO v_emptbl FROM scott.emp;
-- Print nested table of records:
FOR i IN 1 .. v_emptbl.COUNT LOOP
DBMS_OUTPUT.PUT_LINE (v_emptbl(i).empno);
END LOOP;
RETURN v_emptbl;
END;
--
BEGIN
ret_val:= getEmployeeList;
END;
/