Simple Stored Procedure for learning - oracle

My journey is progressing and I'm learning rapidly. I can't get enough of this stuff... alas I am at a dead end here and need some help.
I am running Visual Studio, am connected to a database (that's filled with dummy data). I am able to run queries on it as expected. However, I'm learning about Procedures right now and I'm coming up with a problem.
I am trying to simply run a query that will select all from table.
CREATE PROCEDURE nearthetop()
BEGIN
SELECT * FROM RESULTS WHERE VOLUME = (SELECT MAX(VOLUME) FROM RESULTS WHERE VOLUME NOT In (SELECT Max(VOLUME) from RESULTS))
END;
When I run this inside Visual Studio I get an error:
EXECUTE FAIL:
CREATE PROCEDURE twofromtop() BEGIN SELECT * FROM RESULTS WHERE VOLUME = (SELECT MAX(VOLUME) FROM RESULTS WHERE VOLUME NOT In (SELECT Max(VOLUME) from RESULTS)) END
Message :
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'END' at line 3
When I remove BEGIN and END; from the procedure it creates it fine.
Why is that?
And then, once created, how do I "call" that procedure so as I can see the returned results?

In Oracle, when PL/SQL code uses a SELECT statement, you have to select into something - a local variable, a collection, whatever - or use a cursor loop and deal with cursor variable.
You're selecting row(s) whose volume value is the 2nd largest. Code you wrote works, but it fetches from the same table 3 times which isn't optimal. For example (based on Scott's sample schema), fetching the 2nd largest salary:
SQL> select ename, sal from emp order by sal desc;
ENAME SAL
---------- ----------
KING 5000 --> largest
FORD 3000 --> Ford and Scott both share
SCOTT 3000 --> the 2nd largest salary
JONES 2975
BLAKE 2850
CLARK 2450
<snip>
Your query returns correct result:
SQL> select *
2 from emp
3 where sal = (select max(sal) from emp
4 where sal not in (select max(sal) from emp)
5 );
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ---------- --------- ---------- -------- ---------- ---------- ----------
7788 SCOTT ANALYST 7566 09.12.82 3000 20
7902 FORD ANALYST 7566 03.12.81 3000 20
Consider doing it differently:
SQL> select *
2 from (select e.*,
3 rank() over (order by sal desc) rnk
4 from emp e
5 )
6 where rnk = 2;
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO RNK
---------- ---------- --------- ---------- -------- ---------- ---------- ---------- ----------
7788 SCOTT ANALYST 7566 09.12.82 3000 20 2
7902 FORD ANALYST 7566 03.12.81 3000 20 2
SQL>
Now, back to your procedure. A simple option which doesn't require much effort is to use a cursor FOR loop. Procedure's IN parameter says which largest salary you want:
SQL> create or replace procedure nearthetop (par_n in number) as
2 begin
3 for cur_r in (select *
4 from (select e.*,
5 rank() over (order by sal desc) rnk
6 from emp e
7 )
8 where rnk = par_n
9 )
10 loop
11 dbms_output.put_line(cur_r.ename ||': '|| cur_r.sal);
12 end loop;
13 end;
14 /
Procedure created.
Testing:
SQL> set serveroutput on
SQL> begin
2 nearthetop(2); --> give me the 2nd largest salary
3 end;
4 /
SCOTT: 3000
FORD: 3000
PL/SQL procedure successfully completed.
SQL> begin
2 nearthetop(5); --> give me the 5th largest salary
3 end;
4 /
BLAKE: 2850
PL/SQL procedure successfully completed.
SQL>
I'm just displaying those values on the screen; you never said what you'd want to do with them.
If you'd like to return the result to the caller, a better option is to use a function instead of a procedure. In this case, it would be a ref cursor it returns.
SQL> create or replace function f_nearthetop (par_n in number)
2 return sys_refcursor
3 as
4 rc sys_refcursor;
5 begin
6 open rc for select *
7 from (select e.*,
8 rank() over (order by sal desc) rnk
9 from emp e
10 )
11 where rnk = par_n;
12 return rc;
13 end;
14 /
Function created.
Testing:
SQL> var l_rc refcursor
SQL>
SQL> exec :l_rc := f_nearthetop(2);
PL/SQL procedure successfully completed.
SQL> print :l_rc
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO RNK
---------- ---------- --------- ---------- -------- ---------- ---------- ---------- ----------
7788 SCOTT ANALYST 7566 09.12.82 3000 20 2
7902 FORD ANALYST 7566 03.12.81 3000 20 2
SQL>
So, yes - there are various options. Which one you'll actually use depends on what you want to do.
(As of Visual Studio: I can't help about it, I don't use it.)

Related

How to create an oracle plsql function that will return multiple rows for a specific parameter (ex: employee id)

I need to create a function that will be called on oracle screen.
I just don't know how to do it
A simple option is function that returns refcursor.
This is example based on Scott's sample emp table; function accepts department number as a parameter and returns data related to employees working in that department.
SQL> create or replace function f_test (par_deptno in emp.deptno%type)
2 return sys_refcursor
3 is
4 l_rc sys_refcursor;
5 begin
6 open l_rc for
7 select deptno, empno, ename, job, sal
8 from emp
9 where deptno = par_deptno;
10 return l_rc;
11 end;
12 /
Function created.
SQL> select f_test(10) from dual;
F_TEST(10)
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
DEPTNO EMPNO ENAME JOB SAL
---------- ---------- ---------- --------- ----------
10 7782 CLARK MANAGER 2572.5
10 7839 KING PRESIDENT 5250
10 7934 MILLER CLERK 1365
SQL>

Can I use "order by" with user input in oracle?

I use the same procedure for several tasks. Each function also needs to use ORDER BY every time.
I want to use ORDER BY with user inputs.
I tried this but it didn't work.
PROCEDURE GET_DEPARTMENT_LIST(ORDER_BY_PARAM IN VARCHAR2, DEPT_DATA OUT T_CURSOR) IS
V_CURSOR T_CURSOR;
BEGIN
OPEN V_CURSOR FOR
SELECT GET_LIST(ORDER_BY_PARAM) FROM DUAL;
DEPT_DATA := V_CURSOR;
END GET_DEPARTMENT_LIST;
FUNCTION GET_LIST (PAR_ORDER_BY IN VARCHAR2)
RETURN SYS_REFCURSOR
IS
L_CR SYS_REFCURSOR;
BEGIN
OPEN L_CR FOR
SELECT DEPARTMENT_ID, DEPARTMENT_CODE, DEPARTMENT_NAME
FROM DEPARTMENT ORDER BY PAR_ORDER_BY ASC;
RETURN L_CR;
END;
Procedure Executed Query :
VARIABLE RC REFCURSOR;
EXECUTE DEPARTMENT_PKG.GET_DEPARTMENT_LIST('DEPARTMENT_NAME', :RC);
PRINT RC;
Result :
Sure you can. For example:
SQL> create or replace function get_list (par_order_by in varchar2)
2 return sys_refcursor
3 is
4 l_rc sys_refcursor;
5 begin
6 open l_rc for
7 'select empno, ename, job, sal from emp where deptno = 10 order by ' || par_order_by;
8 return l_rc;
9 end;
10 /
Function created.
Testing:
SQL> select get_list('ename') result from dual;
RESULT
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
EMPNO ENAME JOB SAL
---------- ---------- --------- ----------
7782 CLARK MANAGER 2450
7839 KING PRESIDENT 5000
7934 MILLER CLERK 1300
SQL> select get_list('job, sal') result from dual;
RESULT
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
EMPNO ENAME JOB SAL
---------- ---------- --------- ----------
7934 MILLER CLERK 1300
7782 CLARK MANAGER 2450
7839 KING PRESIDENT 5000
SQL>
As you noticed, I created a function - for simplicity - instead of a procedure with an OUT parameter. If you have to use a procedure, no problem; it is the ORDER BY you had problem with, not the way you'll return the result to the caller.
[EDIT: procedure that calls a function]
SQL> CREATE OR REPLACE PROCEDURE get_dept_list (
2 order_by_param IN VARCHAR2,
3 dept_data OUT SYS_REFCURSOR)
4 IS
5 BEGIN
6 OPEN dept_data FOR SELECT get_list (order_by_param) FROM DUAL;
7 END get_dept_list;
8 /
Procedure created.
SQL> CREATE OR REPLACE FUNCTION get_list (par_order_by IN VARCHAR2)
2 RETURN SYS_REFCURSOR
3 IS
4 l_rc SYS_REFCURSOR;
5 BEGIN
6 OPEN l_rc FOR
7 'select empno, ename, job, sal from emp where deptno = 10 order by '
8 || par_order_by;
9
10
11 RETURN l_rc;
12 END;
13 /
Function created.
Sorted by ENAME:
SQL> var rc refcursor
SQL> exec get_dept_list('ename', :rc)
PL/SQL procedure successfully completed.
SQL> print rc
GET_LIST(:B1)
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
EMPNO ENAME JOB SAL
---------- ---------- --------- ----------
7782 CLARK MANAGER 2450
7839 KING PRESIDENT 5000
7934 MILLER CLERK 1300
Sorted by JOB:
SQL> exec get_dept_list('job', :rc)
PL/SQL procedure successfully completed.
SQL> print rc
GET_LIST(:B1)
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
EMPNO ENAME JOB SAL
---------- ---------- --------- ----------
7934 MILLER CLERK 1300
7782 CLARK MANAGER 2450
7839 KING PRESIDENT 5000
Sorted by SAL in descending order:
SQL> exec get_dept_list('sal desc', :rc)
PL/SQL procedure successfully completed.
SQL> print rc
GET_LIST(:B1)
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
EMPNO ENAME JOB SAL
---------- ---------- --------- ----------
7839 KING PRESIDENT 5000
7782 CLARK MANAGER 2450
7934 MILLER CLERK 1300
SQL>

PL/SQL Procedure/function with params to return a table from(of) a select query

how can a (Oracle)function/Procedure with parameters can be created to return a table, which was returned by a select statement, inside a block
tried with refcursor
getting data as below ,all in a single row
{<G_ID=748,P_G_ID=746,P_NO=null,M_DATE=06-OCT-20 >,
<G_ID=749,P_G_ID=746,P_NO=null,M_DATE=06-OCT-20>} instead of a table format
image link for the sample output
P.S. I am pretty new to PL/SQL
One option is to return refcursor. Here's an example:
SQL> create or replace function f_test (par_deptno in number)
2 return sys_refcursor
3 is
4 rc sys_refcursor;
5 begin
6 open rc for select * from emp where deptno = par_deptno;
7 return rc;
8 end;
9 /
Function created.
SQL> select f_test(10) from dual;
F_TEST(10)
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ---------- --------- ---------- -------- ---------- ---------- ----------
7782 CLARK MANAGER 7839 09.06.81 2450 10
7839 KING PRESIDENT 17.11.81 10000 10
7934 MILLER CLERK 7782 23.01.82 1300 10
SQL>

How to pass multiple parameters in oracle stored procedure

i am trying to execute like exec print_emp(1010,1111) but it will showing error.
create or replace
procedure print_emp(
P_empno NUMBER
)
IS
begin
for c in ( SELECT *
from emp
where empno in p_empno)
loop
dbms_output.put_line( c.empno||' '||c.ename||' '||c.job||' '||c.sal);
end loop;
END;
You'll need to create a type that is a collection of numbers, and then a procedure that accepts that collection.
Rather than use the IN operator, you should use MEMBER OF to test whether a scalar value is in a collection.
create or replace type tab_number is table of number
/
create or replace procedure print_nums
(p_nums in tab_number)
is
cursor c_main is
select column_value
from table(p_nums)
order by 1;
begin
for r_main in c_main loop
dbms_output.put_line(r_main.column_value);
end loop;
--
if 33 member of p_nums then
dbms_output.put_line('In the list');
end if;
end;
/
exec print_nums(tab_number(10,20,50));
exec print_nums(tab_number(10,20,33));
Obviously, if all parameters you'd like to pass represent the same column value, you can't just list them as if they were two different parameters. If that was the case, procedure should actually name them all, e.g.
create procedure print_emp(par_emp_1 in number, par_emp_2 in number)
but - what if there are 3 or 4 EMPNOs you'd like to pass? You can't modify the procedure every time (not just while declaring it, but also in SELECT statements, as you'd have to add new parameters there as well).
Therefore, one option might be this (as far as I understood the question):
one parameter, whose datatype is varchar2
it means that you'd have to enclose list of EMPNOs into single quotes and separate them with the same separator every time; let it be a comma , sign
cursor's query would contain a subquery (lines #6 - 9) which splits that comma-separated values list of EMPNO values into rows
the rest is simple
Here you go:
SQL> create or replace procedure print_emp(par_empno in varchar2)
2 is
3 begin
4 for c in (select *
5 from emp
6 where empno in (select to_number(regexp_substr(par_empno, '[^,]+', 1, level))
7 from dual
8 connect by level <= regexp_count(par_empno, ',') + 1
9 )
10 )
11 loop
12 dbms_output.put_line(c.empno||' '||c.ename||' '||c.job||' '||c.sal);
13 end loop;
14 end;
15 /
Procedure created.
Scott's EMP sample table:
SQL> select * from emp where deptno = 20;
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ---------- --------- ---------- ------------------- ---------- ---------- ----------
7369 SMITH CLERK 7902 17.12.1980 00:00:00 1000 20
7566 JONES MANAGER 7839 02.04.1981 00:00:00 2975 20
7788 SCOTT ANALYST 7566 09.12.1982 00:00:00 3000 20
7876 ADAMS CLERK 7788 12.01.1983 00:00:00 1100 20
7902 FORD ANALYST 7566 03.12.1981 00:00:00 3000 20
Testing:
SQL> set serveroutput on
SQL> exec print_emp('7369,7566,7788');
7566 JONES MANAGER 2975
7788 SCOTT ANALYST 3000
7369 SMITH CLERK 1000
PL/SQL procedure successfully completed.
SQL>

How to return a list from a function for a "select... IN.." statement [duplicate]

This question already has answers here:
Function or Procedure for an IN clause
(5 answers)
PL/SQL - Use "List" Variable in Where In Clause
(3 answers)
Closed 3 years ago.
I wish to write a function for an ERP system that returns a list that can be used in a "select...IN" statement. For example...
instead of...
"select * from A where b in ('c','d','e')"
I need something like...
"select * from A where b in (get_items('a'))"
I've tried a function that can create the 'c','d','e' bit, but that doesn't give me any results.
I've tried several methods, but I can't seem to hit upon the right idea. I'm okay with writing API's/functions, but what's the best method to get a list of items out of a function and into that select statement? would an array or a sys refcursor work?
One option is to create a function which returns a table. Here's an example based on Scott's sample schema. The function accepts department number and returns list of empnos in that deptno.
SQL> create or replace function get_emps (par_deptno in number)
2 return sys.odcinumberlist
3 is
4 l_list sys.odcinumberlist := sys.odcinumberlist ();
5 begin
6 select empno
7 bulk collect into l_list
8 from emp
9 where deptno = par_deptno;
10
11 return l_list;
12 end;
13 /
Function created.
Code that looks like yours (with IN):
SQL> select e.empno, e.ename, e.job, e.sal
2 from emp e
3 where e.empno in (select * from table(get_emps(10)));
EMPNO ENAME JOB SAL
---------- ---------- --------- ----------
7782 CLARK MANAGER 2450
7839 KING PRESIDENT 10000
7934 MILLER CLERK 1300
Code that uses JOIN (the result will be the same):
SQL> select e.empno, e.ename, e.job, e.sal
2 from emp e join table(get_emps(10)) t on e.empno = t.column_value;
EMPNO ENAME JOB SAL
---------- ---------- --------- ----------
7782 CLARK MANAGER 2450
7839 KING PRESIDENT 10000
7934 MILLER CLERK 1300
SQL>

Resources