How to execute table stored query using stored procedure - sql

I have following table structure.
CREATE TABLE "DADMIN"."DATATEMPLATES"
(
"ID" VARCHAR2(100 BYTE),
"QUERY" CLOB,
"ACTIVESTATUS" VARCHAR2(2 BYTE),
)
I'm storing query data like this SELECT CD.CIF , CD.CREDIT_ACCOUNT FROM CUTOMERDATA CD WHERE ID = :PARA_ID
Currently I'm executing it through the C# raw sql execution
Instead of that, I need to execute the table query through the stored procedure. How can I execute table stored query using SP? and return its data using cursor output?
Update:
This is my sample SP,
PROCEDURE Get_customer_data (p_Query_id IN VARCHAR2,
p_cursor OUT OUTPUTCURSOR)
IS
BEGIN
DECLARE
l_query CLOB;
BEGIN
SELECT query
INTO l_query
FROM querytemplates
WHERE id = p_Query_id ;
OPEN p_cursor FOR l_query;
END;
END;
But this makes error
ORA-06512: at line 1
01008. 00000 - "not all variables bound"
And my other problem is, the table stored query also excepting parameter called PARA_ID how can I pass that.
sample table stored query as follows,
SELECT CD.CIF , CD.CREDIT_ACCOUNT FROM CUTOMERDATA CD WHERE ID = :PARA_ID

One option is to return refcursor. Here's an example.
Sample data (that stores queries):
SQL> SELECT * FROM datatemplates;
ID QUERY A
---------- ------------------------------------------------------------ -
1 select deptno, dname from dept A
2 select d.dname, e.ename, e.job, e.sal from emp e join dept d A
on d.deptno = e.deptno
SQL>
Procedure - based on passed ID parameter - selects appropriate query and opens a refcursor based on that select statement:
SQL> CREATE OR REPLACE FUNCTION f_test (par_id IN NUMBER)
2 RETURN SYS_REFCURSOR
3 IS
4 l_query CLOB;
5 l_rc SYS_REFCURSOR;
6 BEGIN
7 SELECT query
8 INTO l_query
9 FROM datatemplates
10 WHERE id = par_id;
11
12 OPEN l_rc FOR l_query;
13
14 RETURN l_rc;
15 END;
16 /
Function created.
Testing:
SQL> SELECT f_test (1) FROM DUAL;
F_TEST(1)
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
DEPTNO DNAME
---------- --------------
10 ACCOUNTING
20 RESEARCH
30 SALES
40 OPERATIONS
Some more testing:
SQL> SELECT f_test (2) FROM DUAL;
F_TEST(2)
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
DNAME ENAME JOB SAL
-------------- ---------- --------- ----------
ACCOUNTING CLARK MANAGER 2450
ACCOUNTING KING PRESIDENT 5000
ACCOUNTING MILLER CLERK 1300
RESEARCH JONES MANAGER 2975
RESEARCH FORD ANALYST 3000
RESEARCH ADAMS CLERK 1100
RESEARCH SMITH CLERK 800
RESEARCH SCOTT ANALYST 3000
SALES WARD SALESMAN 1250
SALES TURNER SALESMAN 1500
SALES ALLEN SALESMAN 1600
SALES JAMES CLERK 950
SALES BLAKE MANAGER 2850
SALES MARTIN SALESMAN 1250
14 rows selected.
SQL>

Related

Creating procedure with dynamic SQL

Here are the instructions:
A company wants to allow customers to do product search by selecting a product name or
description, and then typing a search term. Using native dynamic SQL, create a
procedure name SEARCH_SP that returns a product name, description, and price base
on users’ search criteria. The procedure needs handle multiple rows being returned.
Here is the code I have so far.
CREATE OR REPLACE PROCEDURE search_sp (product_name IN VARCHAR2,
description IN VARCHAR2,
price_based IN NUMBER
)
AS
BEGIN
SELECT customer.product.name, customer.description, customer.price
FROM dbo.customer
WHERE customer.description = #SEARCH.customer.product.name = #SEARCH
END;
/
EXECUTE IMMEDIATE plsql_block
USING IN OUT new_product_name, new_description, new_price_based;
END;
/
I'm getting compilation errors and more. Any help or suggestions will be greatly appreciated.
Dynamic SQL? What for? To make your life more miserable than it should be? What's wrong with a straight SQL?
I'd suggest a function. Why? You're supposed to return the result. If it is a procedure, it has to have an OUT parameter. If that's so, then it's a function.
Here's how I'd do it; see how it works, use it (and improve) if you want. I don't think I'll get involved into anything dynamic here, sorry.
As I don't have your tables, I'll use Scott's sample schema. Here's data I'm interested in:
SQL> select d.dname, e.ename, e.job, e.sal
2 from dept d join emp e on e.deptno = d.deptno
3 order by d.dname, e.job, e.sal;
DNAME ENAME JOB SAL
-------------- ---------- --------- ----------
ACCOUNTING MILLER CLERK 1300
ACCOUNTING CLARK MANAGER 2450
ACCOUNTING KING PRESIDENT 5000
RESEARCH SCOTT ANALYST 3000
RESEARCH FORD ANALYST 3000
RESEARCH SMITH CLERK 800
RESEARCH ADAMS CLERK 1100
RESEARCH JONES MANAGER 2975
SALES JAMES CLERK 950
SALES BLAKE MANAGER 2850
SALES MARTIN SALESMAN 1250 --> for testing, I'll fetch SALESMEN
SALES WARD SALESMAN 1250 --> who earn 1250 USD (it's probably USD)
SALES TURNER SALESMAN 1500
SALES ALLEN SALESMAN 1600
14 rows selected.
Code that searches through it looks like this:
SQL> create or replace function search_sp
2 (par_dname in varchar2,
3 par_job in varchar2,
4 par_sal in number
5 )
6 return sys_refcursor
7 is
8 rc sys_refcursor;
9 begin
10 open rc for
11 select d.dname, e.ename, e.job, e.sal
12 from dept d join emp e on e.deptno = d.deptno
13 where (d.dname = par_dname or par_dname is null)
14 and (e.job = par_job or par_job is null)
15 and (e.sal = par_sal or par_sal is null);
16 return rc;
17 end;
18 /
Function created.
Let's test it:
SQL> select search_sp(null, 'SALESMAN', 1250) from dual;
SEARCH_SP(NULL,'SALE
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
DNAME ENAME JOB SAL
-------------- ---------- --------- ----------
SALES WARD SALESMAN 1250
SALES MARTIN SALESMAN 1250
SQL>
Looks OK to me.

Oracle - best way to return from a procedure an OUT SYS_REFCURSOR with all the rows and columns deleted from a table

I want to do something like this:
create or replace procedure get_deleted_rows(result_set OUT SYS_REFCURSOR) is
begin
delete from table1
where start_date >= sysdate - 30 and start_date <= sysdate
returning * bulk collect into result_set;
end;
I saw that creating a type table of... and using bulk collect into a declared variable of this type was a way to go. But, this table I'm deleting has several columns and doing this would generate a lot of complexity for me to place it in production.
Is there a more simple way to return all the rows from a delete from a procedure?
... and doing this would generate a lot of complexity for me
I'm afraid that you'll just have to accept it.
Here's an example of how to do it.
Sample table; I'll delete rows for employees whose salaries are higher than 2000.
SQL> select * from test order by sal;
ENAME SAL STARS
---------- ---------- ----------
SMITH 800
JAMES 950
ADAMS 1100 *
WARD 1250 *
MARTIN 1250 *
TURNER 1500 *
ALLEN 1600 *
BLAKE 2850 ** --> delete Blake, Jones and Scott
JONES 2975 **
SCOTT 3000 ***
10 rows selected.
Let's start:
SQL> create or replace type t_row is object
2 (ename varchar2(10), sal number, stars varchar2(10));
3 /
Type created.
SQL> create or replace type t_tab as table of t_row;
2 /
Type created.
Procedure:
SQL> create or replace procedure p_test (par_rc out sys_refcursor)
2 is
3 l_tab t_tab;
4 begin
5 delete from test
6 where sal > 2000
7 returning t_row(ename, sal, stars) bulk collect into l_tab;
8
9 open par_Rc for select * from table (l_tab);
10 end;
11 /
Procedure created.
Testing:
SQL> var l_rc refcursor
SQL>
SQL> exec p_test(:l_rc);
PL/SQL procedure successfully completed.
SQL> print l_rc
ENAME SAL STARS
---------- ---------- ----------
JONES 2975 ** --> Deleted, as expected
BLAKE 2850 **
SCOTT 3000 ***
What's left?
SQL> select * from test order by sal;
ENAME SAL STARS
---------- ---------- ----------
SMITH 800
JAMES 950
ADAMS 1100 *
WARD 1250 *
MARTIN 1250 *
TURNER 1500 *
ALLEN 1600 *
7 rows selected.
SQL>

Update table inside a loop in a procedure pl/sql

So i want to hire a new manager for a department. This procedure has 2 parameters, the department name i want to change, and the new id for manager (taken from employee's id). So to change it, i need to update all employee that has the old manager id, and change their manager id into the new one. This is my code so far, the problem is that it updated all of the employees' manager, so the whole database is updated. I must use procedure, not function. Any idea? Thanks.
CREATE OR REPLACE PROCEDURE update_manager(v_deptname IN departments.department_name%TYPE,v_empid IN employees.employee_id%TYPE) IS
v_deptid departments.department_id%type;
BEGIN
SELECT department_id INTO v_deptid FROM departments WHERE department_name=v_deptname;
FOR i IN (SELECT * FROM employees)
LOOP
IF i.department_id=v_deptid THEN
UPDATE employees
SET manager_id=v_empid
WHERE i.department_id=v_deptid;
END IF;
END LOOP;
END;
/
BEGIN
update_manager('Marketing',100);
END;
/
If you must create a PL/SQL procedure then you can do
CREATE OR REPLACE PROCEDURE update_manager(v_deptname IN departments.department_name%TYPE,v_empid IN employees.employee_id%TYPE) IS
v_deptid departments.department_id%type;
BEGIN
SELECT department_id INTO v_deptid FROM departments WHERE department_name=v_deptname;
UPDATE employees
SET manager_id=v_empid
WHERE department_id=v_deptid;
END;
/
The problem with your code that's causing "it updated all of the employees' manager" is that your update statement:
UPDATE employees
SET manager_id=v_empid
WHERE i.department_id=v_deptid;
Your filter here is comparing i.department_id, this is the variable that's coming from your FOR i IN (SELECT * FROM employees), NOT from the update statement. You've already confirmed that i.department_id=v_deptid because you are calling this in a loop with an if statement checking it.
It is not efficient at all to get all rows in employees, loop the results, checking each row if it matches a condition and then firing off an update statement (even if your update statement is filtering against the correct row.
I don't have your tables, but I do have Scott's so - here's an example.
Departments and employees in department 10:
SQL> select * From dept order by deptno;
DEPTNO DNAME LOC
---------- -------------- -------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
SQL> select deptno, empno, ename, mgr
2 from emp
3 where deptno = 10;
DEPTNO EMPNO ENAME MGR
---------- ---------- ---------- ----------
10 7782 CLARK 7839
10 7839 KING
10 7934 MILLER 7782
Procedure that looks like yours (as you're studying PL/SQL), using a variable to fetch department number into, as well as a loop:
SQL> create or replace procedure update_manager
2 (v_deptname in dept.dname%type,
3 v_empno in emp.empno%type
4 )
5 is
6 v_deptno dept.deptno%type;
7 begin
8 select d.deptno
9 into v_deptno
10 from dept d
11 where d.dname = v_deptname; --> this is what you are missing
12
13 for cur_r in (select e.empno
14 from emp e
15 where e.deptno = v_deptno
16 )
17 loop
18 update emp a set
19 a.mgr = v_empno
20 where a.empno = cur_r.empno;
21 end loop;
22 end;
23 /
Procedure created.
Testing: let's modify manager of all employees in department 10:
SQL> exec update_manager ('ACCOUNTING', 7839);
PL/SQL procedure successfully completed.
SQL> select deptno, empno, ename, mgr
2 from emp
3 where deptno = 10;
DEPTNO EMPNO ENAME MGR
---------- ---------- ---------- ----------
10 7782 CLARK 7839
10 7839 KING 7839
10 7934 MILLER 7839
SQL>
Seems to work. Though, that's inefficient; doing it row-by-row (in a loop) is usually slow-by-slow. Work on the whole sets of data, rather. Something like this:
SQL> rollback;
Rollback complete.
SQL> create or replace procedure update_manager
2 (v_deptname in dept.dname%type,
3 v_empno in emp.empno%type
4 )
5 is
6 begin
7 update emp e set
8 e.mgr = v_empno
9 where e.deptno = (select d.deptno
10 from dept d
11 where d.dname = v_deptname
12 );
13 end;
14 /
Procedure created.
SQL> exec update_manager ('ACCOUNTING', 7782);
PL/SQL procedure successfully completed.
SQL> select deptno, empno, ename, mgr
2 from emp
3 where deptno = 10;
DEPTNO EMPNO ENAME MGR
---------- ---------- ---------- ----------
10 7782 CLARK 7782
10 7839 KING 7782
10 7934 MILLER 7782
SQL>
Why not a simple select for manager id and an update on all concerned employee ?
DECLARE #department_id uniqueidentifier
SELECT #department_id = departement_id
FROM departments
WHERE department_name = #v_deptname
UPDATE employees
SET manager_id = #v_empid
WHERE department_id = #department_id

Oracle - remove Count query as the same query is already used in returning ref cursor

I am returning a ref cursor(P_CUR_SUMMARY) and then taking count of the same query to return a string (WARN# / ERROR). I want to eliminate the Count query.
This proc is using an existing framework so I cant return the string with the ref cursor.
I dont want to create a temp table and store data. This will remove the Count query but increase IO.
Any other option to eliminate the Count query? I just want to know if the ref cursor has ANY records. Not interested in actual count.
Procedure:
V_MAIN_TITLE and P_CUR_SUMMARY - OUT parameters
...
OPEN P_CUR_SUMMARY FOR
select * from emp...
...
select count(1) into V_CNT from emp...
...
IF V_CNT > 0 then
SELECT 'WARN#' INTO V_MAIN_TITLE FROM dual;
ELSE
SELECT 'ERROR' INTO V_MAIN_TITLE FROM dual;
END IF;
As you were told, you'll have to fetch from a cursor in order to find out what it contains (use %NOTFOUND or %FOUND cursor attributes for such a purpose). If there's something to be returned, re-open the cursor (otherwise you'd lose the first row, the one you used to check what's in there).
Here's an example:
SQL> create or replace procedure p_test
2 (par_deptno in number, par_main_title out varchar2, par_cur_summary out sys_refcursor)
3 is
4 r_emp emp%rowtype;
5 begin
6 open par_cur_summary for
7 select *
8 from emp
9 where deptno = par_deptno;
10
11 -- fetch in order to find out whether there's something returned by the cursor
12 fetch par_cur_summary into r_emp;
13 if par_cur_summary%notfound then
14 par_main_title := 'ERROR';
15 else
16 par_main_title := 'WARN#';
17
18 -- now, re-open the cursor; otherwise, you'd lose the first row, the one
19 -- that has been fetched for "testing" purpose
20 open par_cur_summary for
21 select *
22 from emp
23 where deptno = par_deptno;
24 end if;
25 end;
26 /
Procedure created.
Let's test it:
SQL> var l_cur_sum refcursor
SQL> var l_mai_tit varchar2(30)
SQL> exec p_test(37, :l_mai_tit, :l_cur_sum);
PL/SQL procedure successfully completed.
SQL> print l_mai_tit
L_MAI_TIT
---------------------------------------------------------------------------------------
ERROR
SQL> print l_cur_sum
ERROR:
ORA-01002: fetch out of sequence
no rows selected
SQL> exec p_test(10, :l_mai_tit, :l_cur_sum);
PL/SQL procedure successfully completed.
SQL> print l_mai_tit
L_MAI_TIT
---------------------------------------------------------------------------------------
WARN#
SQL> print l_cur_sum
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ---------- --------- ---------- ---------- ---------- ---------- ----------
7782 CLARK MANAGER 7839 09.06.1981 2450 10
7839 KING PRESIDENT 17.11.1981 5000 10
7934 MILLER CLERK 7782 23.01.1982 1300 10
SQL>
Look what happens if you don't re-open the cursor (by commenting those lines out):
if par_cur_summary%notfound then
par_main_title := 'ERROR';
else
par_main_title := 'WARN#';
/*
open par_cur_summary for
select *
from emp
where deptno = par_deptno;
*/
end if;
SQL> exec p_test(10, :l_mai_tit, :l_cur_sum);
PL/SQL procedure successfully completed.
SQL> print l_mai_tit
L_MAI_TIT
----------------------------------------------------------------------------------------
WARN#
SQL> print l_cur_sum
EMPNO ENAME JOB MGR HIREDATE SAL COMM DEPTNO
---------- ---------- --------- ---------- ---------- ---------- ---------- ----------
7839 KING PRESIDENT 17.11.1981 5000 10
7934 MILLER CLERK 7782 23.01.1982 1300 10
SQL>
See? CLARK is lost.
The number of rows that will be returned is not known until the query is executed (i.e. when rows are fetched). When the cursor has merely been opened that is still in the future.
Perhaps it makes more sense for the client to handle the condition where no rows are returned and raise its own errors.

Combining two select statements with differing returns into a single view

I have a T-SQL stored procedure I'm trying to convert to PL-SQL.
Currently, this stored procedure executes a very long select query, immediately followed by a much shorter select query. It looks like so:
CREATE PROCEDURE proc_name
#userid int,
AS
select (about twenty fields)
from (about five tables)
where something = #userid
select (about five fields)
from (about three tables)
where something = #userid
My first thought when I saw this was to convert it to a view, since it's just doing a select statement. However, is this actually possible, given that two selects are being done? Could a stored procedure be used? I've experimented with that idea, but to my understanding I'd need to create two sys_refcursors, meaning the calling code would need to handle both query returns differently, which it's not having to do currently.
So question is: How can I convert that type of procedure, from T-SQL, to PL-SQL?
You cannot simply have a select query in PL/SQL. It will throw PLS-00428: an INTO clause is expected in this SELECT statement error.
To return multiple rows, you could use CURSOR. In your case, with multiple statements, you can have two REFCURSOR.
For example,
SQL> variable v_ref1 refcursor
SQL> variable v_ref2 refcursor
SQL>
SQL> DECLARE
2 v_ref1 sys_refcursor;
3 v_ref2 sys_refcursor;
4 BEGIN
5 OPEN :v_ref1 FOR SELECT empno, ename
6 FROM emp ORDER BY empno
7 FETCH FIRST 5 ROWS ONLY;
8 OPEN :v_ref2 FOR SELECT empno, ename
9 FROM emp ORDER BY empno DESC
10 FETCH FIRST 5 ROWS ONLY;
11 END;
12 /
PL/SQL procedure successfully completed.
SQL> print v_ref1
EMPNO ENAME
---------- ----------
7369 SMITH
7499 ALLEN
7521 WARD
7566 JONES
7654 MARTIN
SQL> print v_ref2
EMPNO ENAME
---------- ----------
7934 MILLER
7902 FORD
7900 JAMES
7876 ADAMS
7844 TURNER
SQL>
If you want to combine the resultset of your multiple SELECT statements, you could use UNION operator and have it in single REFCURSOR. Given that the column data types match and are in proper order. It is just an example,
SQL> variable v_ref refcursor
SQL>
SQL> DECLARE
2 v_ref sys_refcursor;
3 BEGIN
4 OPEN :v_ref FOR
5 SELECT empno, DEPTNO FROM emp WHERE ROWNUM <=5
6 UNION ALL
7 SELECT empno, DEPTNO FROM EMP WHERE ROWNUM <=5;
8 END;
9 /
PL/SQL procedure successfully completed.
SQL> print v_ref
EMPNO DEPTNO
---------- ----------
7369 20
7499 30
7521 30
7566 20
7654 30
7369 20
7499 30
7521 30
7566 20
7654 30
10 rows selected.
SQL>
You could make second select query have 20 columns as well by making the fields which are not there in the first select query NULL. But that depends if you are okay with that.