Creating procedure with dynamic SQL - 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.

Related

How to execute table stored query using stored procedure

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>

What is the best way(in case of performance) to find out the user details with maximum salary?

We have user details with a column of salary also, how can we print the user details with the maximum salary, I don't want to use the Subquery, and yeah how subquery will reduce the performance.
I know this query is wrong but I want something like this:
select User_name, user_id
from dual where salary=Max(salary);
Analytic functions help.
Using a CTE (which is kind of a subquery; don't be afraid of it, it doesn't bite and won't affect performance), query might look like this (based on sample Scott's schema):
SQL> select ename, sal from emp order by sal desc;
ENAME SAL
---------- ----------
KING 5000 --> this is the highest salary
FORD 3000 --> FORD and SCOTT share the 2nd place
SCOTT 3000
JONES 2975
BLAKE 2850
CLARK 2450
ALLEN 1600
TURNER 1500
MILLER 1300
WARD 1250 --> WARD and MARTIN are then 9th
MARTIN 1250
ADAMS 1100
JAMES 950
SMITH 800
14 rows selected.
Query is then
SQL> with temp as
2 (select ename,
3 dense_rank() over (order by sal desc) rnk
4 from emp
5 )
6 select ename
7 from temp
8 where rnk = 1;
ENAME
----------
KING
SQL>
Why dense_rank? Because two (or more) employees can have the same salary so they "rank" the same. For example, if you want to know whose salary is ranked as 9th, you'd
SQL> l8
8* where rnk = 1
SQL> c/1/9
8* where rnk = 9
SQL> /
ENAME
----------
WARD
MARTIN
SQL>
Query you suggested (although wrong, but - I got the idea) looks like this:
SQL> select ename
2 from emp
3 where sal = (select max(sal) from emp);
ENAME
----------
KING
SQL>
And yes, it affects performance because you're fetching data from the same emp table twice: once to find the max salary (in a subquery), and then in the main query to find who it belongs to.

How to agregate queries results from dynamic table queries

To begin I am far from being an expert and I would like to have some guidance on the following subject :
I am trying to get a query result from several table without having the table names on the query but coming as a result of another table.
I have all the table names from 1 table :
Tables names from U5WOMA
What I would like to get is something as this as a result :
Union results from Tables stored in U5WOMA
select description, actif, code from U5WOCO
union
select description, actif, code from u5wowo
union
select description, actif, code from U5woeq;
Except that I would like this result without having to put the table names in the query but rather having them dynamically from the U5WOMA table since I will not know how many tables that I would have neither their names.
I did some research and I get the feeling that the response is in dynamic SQL but I am unable to find something even close to works.
I am using Oracle database.
If you have any suggestion, tutorial or advice that could help me to move to the right direction, it would be very appreciated.
Thank you,
Here's one option - create a function which composes a "long" select statement based on contents of the u5woma table and uses it as a source for the refcursor.
Sample data:
SQL> select * from u5woco;
DESCRIPTI ACTIF CODE
--------- ---------- ----------
MANAGER CLARK 7782
PRESIDENT KING 7839
CLERK MILLER 7934
SQL> select * from u5wowo;
DESCRIPTI ACTIF CODE
--------- ---------- ----------
CLERK SMITH 7369
MANAGER JONES 7566
ANALYST SCOTT 7788
CLERK ADAMS 7876
ANALYST FORD 7902
SQL> select * from u5woeq;
DESCRIPTI ACTIF CODE
--------- ---------- ----------
SALESMAN ALLEN 7499
SALESMAN WARD 7521
SALESMAN MARTIN 7654
MANAGER BLAKE 7698
SALESMAN TURNER 7844
CLERK JAMES 7900
6 rows selected.
The u5woma table that contains list of all tables involved in query:
SQL> select * from u5woma;
TABLE_
------
U5WOCO
U5WOWO
U5WOEQ
Function: in order for union to work, all select statements must return the same number of columns and they must match in datatype. Your example shows that all tables share the same column list (which simplifies the task):
SQL> create or replace function f_u5
2 return sys_refcursor
3 is
4 l_str varchar2(2000);
5 l_rc sys_refcursor;
6 begin
7 for cur_r in (select table_name from u5woma) loop
8 l_str := l_str || ' select description, actif, code from ' || cur_r.table_name
9 || ' union all';
10 end loop;
11
12 l_str := rtrim(l_str, ' union all');
13 open l_rc for l_str;
14 return l_rc;
15 end;
16 /
Function created.
SQL>
Testing:
SQL> select f_u5 from dual;
F_U5
--------------------
CURSOR STATEMENT : 1
CURSOR STATEMENT : 1
DESCRIPTI ACTIF CODE
--------- ---------- ----------
MANAGER CLARK 7782
PRESIDENT KING 7839
CLERK MILLER 7934
CLERK SMITH 7369
MANAGER JONES 7566
ANALYST SCOTT 7788
CLERK ADAMS 7876
ANALYST FORD 7902
SALESMAN ALLEN 7499
SALESMAN WARD 7521
SALESMAN MARTIN 7654
MANAGER BLAKE 7698
SALESMAN TURNER 7844
CLERK JAMES 7900
14 rows selected.
SQL>
There are other possibilities as well (for example, you could return a collection; or use a pipelined function; or ...), but the main principle remains the same.
thank you for your help, it is a very interesting way of managing it and it is very helpfull.
If I understand correctly all the results are stored in one variable. So as long as there is less than 2000 characters I will be fine but if there are more, it will be trunqued ?
When I call the funtion directly, I have the following result :
SQL>
{<DESCRIPTION=Commande 2,ACTIF=-,CODE=1001>,<DESCRIPTION=Commande 1,ACTIF=+,CODE=1000>,<DESCRIPTION=OT 1,ACTIF=+,CODE=1000>,<DESCRIPTION=OT 2,ACTIF=-,CODE=1001>,<DESCRIPTION=Actif 1,ACTIF=+,CODE=1000>,<DESCRIPTION=Actif 2,ACTIF=-,CODE=1001>,<DESCRIPTION=Actif 3,ACTIF=+,CODE=1003>,}
SQL>
Since the results are all in one line I have to split them in several lines to be able to use them.
I should be able to do it when I call the function f_u5 but is it possible to store the date directly on the function in order to get as many lines as I have results ?
Thank you,
I finally did it a little differently :
I first created a new table :
SQL>
create table SG_u5woma(description varchar2(80), actif varchar2(1), code varchar2(30));
SQL>
Then a stored procedure :
SQL>
create or replace procedure P_u5
as
begin
delete from SG_u5woma;
for cur_r in (select table_name from u5woma) loop
execute immediate 'insert into SG_u5woma (description, actif,code) select description, actif, code from ' || cur_r.table_name ;
end loop;
end;
/
SQL>
The I execute the procedure :
SQL>
exec P_u5();
SQL>
And the result is the data in the table :
SQL>
select * from SG_u5woma;
Commande 2 - 1001
Commande 2 + 1003
Commande 1 + 1000
OT 1 + 1000
OT 2 - 1001
Actif 1 + 1000
Actif 2 - 1001
Actif 3 + 1003
SQL>
Anyway, thank you for the help, it was very helpfull.

PL/SQL: master detail loop using cursors

I currently have a master detail using a single cursor
DECLARE
CURSOR emps_cur (department_id_in IN INTEGER)
IS
select ENAME,DNAME,d.DEPTNO
from scott.EMP e, scott.DEPT d
where d.DEPTNO = e.DEPTNO;
BEGIN
FOR rec IN emps_cur (1700)
LOOP
DBMS_OUTPUT.put_line (rec.DEPTNO||' '||rec.DNAME);
DBMS_OUTPUT.put_line (rec.ENAME);
END LOOP;
END;
/
Which results in:
Statement processed.
10 ACCOUNTING
CLARK
10 ACCOUNTING
MILLER
10 ACCOUNTING
KING
20 RESEARCH
FORD
20 RESEARCH
SCOTT
20 RESEARCH
JONES
20 RESEARCH
SMITH
20 RESEARCH
ADAMS
30 SALES
WARD
30 SALES
MARTIN
30 SALES
TURNER
30 SALES
JAMES
30 SALES
ALLEN
30 SALES
BLAKE
I've been trying to figure out how to loop it so the results look like this:
10 ACCOUNTING
CLARK
MILLER
KING
20 RESEARCH
FORD
SCOTT
JONES
SMITH
ADAMS
30 SALES
WARD
MARTIN
TURNER
JAMES
ALLEN
BLAKE
Essentially, print deptno and dname and the assigned employees right below (with no duplicates). Sorry if this seems elementary. Just started trying to learn PL/SQL and I can't find an answer from search engines.
Create a variable v_dname valorised at the end of the loop with ref.dname. At the beginning of the loop, compare rec.dname with v_dname. If the value is the same, you are in the same department, and you don't dbms it.
DECLARE
vCurrentDeptName dept.DNAME%type := 'impossible name';
CURSOR emps_cur (department_id_in IN INTEGER)
IS
select ENAME,DNAME,JOB
from scott.EMP e, scott.DEPT d
where d.DEPTNO = e.DEPTNO;
BEGIN
FOR rec IN emps_cur (1700)
LOOP
if vCurrentDeptName != rec.DNAME then
DBMS_OUTPUT.put_line (rec.DNAME);
vCurrentDeptName := rec.DNAME;
end if;
DBMS_OUTPUT.put_line (rec.ENAME);
END LOOP;
END;
/

Oracle: Overwrite values in a column with the longest string

I’m running into a problem.
Say, I have columns called “C1, C2, C3....” in a table. I’d like to use the longest string in C1 to replace every other cells in C1 column without disturbing other columns.
I tired several ways but I cannot get my Oracle code run. Could someone please show me a sample code to do this problem? I typed my question using a cellphone so I apologize for not showing you my code. But I think my description is fine... Thank you!
I would use window functions. Oracle has a very convenient functionality with keep:
select max(col1) keep (dense_rank first order by len(col1) desc) over () as col1,
col2, col3, . . .
from t;
You can incorporate this into an update:
update t
set col1 = (select select max(col1) keep (dense_rank first order by len(col1) desc) over () as col1
from t
);
For example:
SQL> select * from test;
DEPTNO DNAME LOC
---------- -------------- -------------
10 ACCOUNTING NEW YORK
20 RESEARCH DALLAS
30 SALES CHICAGO
40 OPERATIONS BOSTON
SQL> update test set
2 dname = (select max(dname) --> MAX fixes TOO-MANY-ROWS because ACCOUNTING
3 from test -- and OPERATIONS have same length
4 where length(dname) = (select max(length(dname))
5 from test
6 )
7 );
4 rows updated.
SQL> select * from test;
DEPTNO DNAME LOC
---------- -------------- -------------
10 OPERATIONS NEW YORK
20 OPERATIONS DALLAS
30 OPERATIONS CHICAGO
40 OPERATIONS BOSTON
SQL>
[EDIT, GROUP BY]
Another example is based on a different table, which reflects what you described in a comment. The DEPTNO (department number) is used to "group" employees, and I'm going to update the JOB column value to the longest job name within that department.
Query is similar to the previous one; it just joins appropriate columns (DEPTNO) throughout code.
Sample data:
SQL> select * from test order by deptno, ename;
DEPTNO ENAME JOB
---------- ---------- ---------
10 CLARK MANAGER
KING PRESIDENT --> the longest in DEPTNO = 10
MILLER CLERK
20 ADAMS CLERK
FORD ANALYST
JONES MANAGER --> as long as ANALYST, but MAX(JOB) will return this value
SCOTT ANALYST
SMITH CLERK
30 ALLEN SALESMAN --> the longest in DEPTNO = 30
BLAKE MANAGER
JAMES CLERK
MARTIN SALESMAN
TURNER SALESMAN
WARD SALESMAN
Update and the result:
SQL> update test t set
2 t.job = (select max(t1.job)
3 from test t1
4 where t1.deptno = t.deptno
5 and length(t1.job) = (select max(length(t2.job))
6 from test t2
7 where t2.deptno = t1.deptno
8 )
9 );
14 rows updated.
SQL> select * from test order by deptno, ename;
DEPTNO ENAME JOB
---------- ---------- ---------
10 CLARK PRESIDENT
KING PRESIDENT
MILLER PRESIDENT
20 ADAMS MANAGER
FORD MANAGER
JONES MANAGER
SCOTT MANAGER
SMITH MANAGER
30 ALLEN SALESMAN
BLAKE SALESMAN
JAMES SALESMAN
MARTIN SALESMAN
TURNER SALESMAN
WARD SALESMAN