Need help in output for Oracle PLSQL function - oracle

Am currently working on oracle PLSQL function to list the project numbers, titles and the names of employees who work on each project.
For this function, I am required to obtain an output as such:
[Fragmented example]
1001 Computation: Alvin, Peter
1002 Study methods: Bob, Robert
1003 Racing car: Robert
Here is the output I am currently having.
SQL> execute PROJECTGROUPS;
1001 Computation: Alvin
1001 Computation: Ami
1001 Computation: Michael
1001 Computation: Peter
1001 Computation: Wendy
1002 Study methods: Bob
1002 Study methods: Robert
1003 Racing car: Bob
1003 Racing car: Robert
1004 Football: Douglass
1004 Football: Eadger
1005 Swimming: Douglass
1005 Swimming: Eadger
1006 Training: Aban
1006 Training: Carl
[Current Code]
SQL> set echo on
SQL> set feedback on
SQL> set linesize 100
SQL> set pagesize 200
SQL> set serveroutput on
SQL> --
SQL> -- Task 01
SQL> --
SQL> CREATE OR REPLACE PROCEDURE PROJECTGROUPS
2 IS
3 Previous_pnum PROJECT.P#%type := -1;
4 --
5 begin
6 for currentRow IN(select p.P#, p.PTITLE, e.NAME
7 from PROJECT p LEFT OUTER JOIN EMPLOYEE e
8 on p.d# = e.d#
9 WHERE p.P# IN(1001,1002,1003,1004,1005,1006)
10 ORDER BY p.P#, p.PTITLE, e.NAME)
11 --
12 --
13 loop
14 if currentRow.P# is not null then
15 dbms_output.put_line(currentRow.P# || ' ' || currentRow.PTITLE || ': ' || currentRow.NAME);
16 end if;
17 Previous_pnum := currentRow.P#;
18 end loop;
19 dbms_output.put_line(NULL);
20 END;
21 /
Procedure created.

What you need is listagg function.
Here is the SQL -
SELECT p.P#
,p.PTITLE
,listagg(e.NAME, ',') within group (order by e.name) employee_names
FROM PROJECT p
,EMPLOYEE e
WHERE p.P# IN(1001,1002,1003,1004,1005,1006)
AND p.d# = e.d#(+)
GROUP
BY p.P#
,p.PTITLE
ORDER
BY p.P#
,p.PTITLE

LISTAGG() function to return the string in order to print directly with alias (str), and print such as DBMS_OUTPUT.PUT_LINE( str ) :
SELECT p.p#||' '||p.ptitle||': '||LISTAGG( e.name, ',' ) WITHIN GROUP ( ORDER BY e.name ) AS str
FROM project p
LEFT JOIN employee e
ON p.d# = e.d#
WHERE p.p# BETWEEN 1001 AND 1006
GROUP BY p.p#, p.ptitle
ORDER BY p.p#, p.ptitle;
we don't know your sample data set. Perhaps you'd need INNER JOIN depending on the data. Btw, it seems need to fix the data structure of project table. The column d# should be moved to another(the third) table I think.

Related

PL/SQL looping 'back' through the records

I have a problem i hope you can help to resolve.
I have a table named PRODUCT:
Product_ID NOT NULL NUMBER(10)
TARGET_PRODUCT VARCHAR2(10)
SOURCE_PRODUCT VARCHAR2(10)
So each target_product made out of source_product(except the first one - first one just has target_product and source_product is null)
I need to find the fist source_product for given target_product.
I need to go 'back' in a loop until source product is null.
Is there a solution for this scenario?
Thanks in advance
It is not a loop you need, but hierarchical query. Have a look at the following example based on Scott's EMP table.
This is its contents; employees are displayed hierarchically, showing who's whose boss:
SQL> select level,
2 lpad(' ', level * 2, ' ') || e.ename name
3 from emp e
4 start with e.mgr is null
5 connect by prior e.empno= e.mgr;
LEVEL NAME
---------- ---------------
1 KING
2 JONES
3 SCOTT
4 ADAMS
3 FORD
4 SMITH
2 BLAKE
3 ALLEN
3 WARD
3 MARTIN
3 TURNER
3 JAMES
2 CLARK
3 MILLER
14 rows selected.
SQL>
As you want to start from the middle of the table (for example, starting from SMITH), you'd "reverse" it:
SQL> select level lvl,
2 lpad(' ', level * 2, ' ') || e.ename name
3 from emp e
4 start with e.ename = 'SMITH'
5 connect by prior e.mgr= e.empno;
LVL NAME
---------- ---------------
1 SMITH
2 FORD
3 JONES
4 KING
SQL>
Finally, using that query as a CTE (or a subquery, if your Forms version doesn't support CTEs), fetch the one whose name has the highest level:
SQL> with temp as
2 (select level lvl,
3 lpad(' ', level * 2, ' ') || e.ename name
4 from emp e
5 start with e.ename = 'SMITH'
6 connect by prior e.mgr= e.empno
7 )
8 select trim(t.name) name
9 from temp t
10 where t.lvl = (select max(t1.lvl) from temp t1);
NAME
---------------
KING
SQL>
Or, even better, using connect_by_isleaf:
SQL> select e.ename name
2 from emp e
3 where connect_by_isleaf = 1
4 start with e.ename = 'SMITH'
5 connect by prior e.mgr= e.empno;
NAME
---------------
KING
SQL>
You may try this, it will return the base target_product for a given target_product:
select * from (
select PRODUCT_ID, TARGET_PRODUCT, SOURCE_PRODUCT
from PRODUCT
start with target_product = '<your target product>'
connect by prior SOURCE_PRODUCT = TARGET_PRODUCT
)
where SOURCE_PRODUCT is null;

Generate Random String in PL/SQL (oracle 12c)

I'm trying to generate a Random String using PL/SQL with only 2 fixed words. It's this possible?
Is this what you're looking for?
SQL> with
2 -- two fixed words
3 test as
4 (select 'fixed words' col from dual),
5 -- split them to rows
6 inter as
7 (select level lvl, regexp_substr(col, '.', 1, level) let
8 from test
9 connect by level <= length(col)
10 )
11 -- aggregate them back, randomly
12 select listagg(let, '') within group (order by dbms_random.value(1, max_lvl)) result
13 from inter
14 join (select max(lvl) max_lvl from inter) on 1 = 1;
RESULT
--------------------------------------------------------------------------------
reiosdwxf d
SQL> /
RESULT
--------------------------------------------------------------------------------
fe ixoddrws
SQL> /
RESULT
--------------------------------------------------------------------------------
wdxeorsdfi
SQL>

Using case update in cursor

I have below 2 tables. One is dept a
DEPT_NO DEPT_NAME DEPT_VALUE
---------- ------------------------------ ----------
10 Chemistry 100
40 Physics 600
20 Mathematics 200
30 Biology 300
50 Cosmos 550
other one is updated_dept b
DEPT DEPT_NAME DEPT_UPDATED_VALUE
---------- ------------------------------ ------------------
10 Chemistry
20 Mathematics
30 Biology
90 Astrology
40 Numerology
50 Cosmos
With the help of the cursor, I want to fetch a.dept_value from 1st table dept a and on basis of (a.dept_no=b.dept and a.dept_name=b.dept_name) update column dept_updated_value in 2nd table updated_dept b using case.
If combination of dept_no and dept_name is not found, I want to update dept_updated_value to 0
I have written below code but it's not giving correct result. Please help
declare
v_dept dept.dept_no%type;
v_dept_name dept.dept_name%type;
v_dept_value dept.dept_value%type;
cursor c_dept_update
is
select dept_no, dept_name, dept_value from dept;
begin
open c_dept_update;
loop
fetch c_dept_update into v_dept,v_dept_name, v_dept_value;
exit when c_dept_update%notfound;
update dept_updated
set dept_updated_value=
case
when dept=v_dept and dept_name=v_dept_name
then v_dept_value
else 0
end;
commit;
end loop;
close c1;
end;
result is like this
DEPT DEPT_NAME DEPT_UPDATED_VALUE
---------- ---------------------------------- ------------------
10 Chemistry 0
20 Mathematics 0
30 Biology 0
90 Astrology 0
40 Numerology 0
50 Cosmos 550
To paraphrase Steven Feuerstein, don't do in a loop what you can do in SQL. So I'd join the tables in an updatable inline view. I haven't tested this so it may need tweaking:
UPDATE ( SELECT b.dept, b.dept_name, b.dept_updated_value, a.dept AS dept2
FROM updated_dept b
LEFT JOIN dept a ON a.dept = b.dept AND a.dept_name = b.dept_name )
SET dept_updated_value = NVL( dept2, 0 )

Combining two stored procedures into a single one using some joins

I am very new to Oracle; I have written two stored procedures where they have both different parameters. I would like to combine the queries in that two stored procedures into single query in a single stored procedure, and make sure it supports any criteria.
These are the stored procedures:
procedure usp_testsp1(RC1 OUT RCT1,
in_dep_Id IN number,
in_Org_id IN number,
in_emp_no IN number)
is
begin
select emp.Name as name,select emp.phone as phone,select emp.Race as
race
from employee emp
JOIN
Project prj ON
emp.ProjectId=prj.ProjectId
JOIN Project_Org Prorg
ON prj.ProjectId=Prorg.ProjectId
JOIN Organization Org1
ON Prorg.OrgId=Org1.OrgId
JOIN Organization Org2
ON emp.OrgId=Org2.OrgId
AND Org2.OrgType=0
WHERE (upper(emp.emp_No) in (in_emp_No ))
AND Prorg.OrgId=in_Org_id
AND Org2.OrgId=in_dep_Id
end;
procedure usp_testsp2(RC1 OUT RCT1,
in_dep_Id IN number,
in_Org_id IN number,
in_vendor_id IN raw,
in_vendor_startdate IN date,
in_vendor_enddate IN date)
is
begin
OPEN RC1 FOR
select emp.Name as name,select emp.phone as phone,select emp.Race as
race
from
from employee emp
JOIN
Project prj ON
emp.ProjectId=prj.ProjectId
JOIN Project_Org Prorg
ON prj.ProjectId=Prorg.ProjectId
JOIN Organization Org1
ON Prorg.OrgId=Org1.OrgId
JOIN Organization Org2
ON emp.OrgId=Org2.OrgId
AND Org2.OrgType=0
INNER JOIN vendor_Emp
ON emp.employeeid=vendor_emp.employeeid
INNER JOIN Vendor
ON Vendor.VendorId=Vendor_emp.VendorId
WHERE Prorg.OrgId=in_Org_id
AND Org2.OrgId=in_dep_Id
AND Vendor.VendorId=in_vendor_id
AND vendor.StartDate=in_vendor_startdate AND Vendor.EndDate=in_vendor_enddate
end;
My objective is to combine the queries within these two separate procedures into a single query such that if I combine all the parameters, I have in_emp_no, in_vendor_id, in_vendor_startdate, in_vendor_enddate apart from the common parameters any of the parameters can be null and I want to make sure that those things won't affect the rest of the query even though its not null
For ex i have only in_emp_no ='xxxx' and common inputs rest of the params like
in_vendor_id etc is null.
I would like to make my query to work even with single paramter to filter the result set
NOTEWithout using Dynamic SQL
Thanks
BJ
You can make columns in the query behave as optional, as far as your join's go, you may need to play with various combinations to make sure everything works.
Sample Table
create table sqltest (
empid number,
name varchar2(10),
orgid number,
depid number,
vendorid number);
insert into sqltest values(1,'BoB',1,1,21);
insert into sqltest values(2,'Chuck',1,1,21);
insert into sqltest values(3,'Mary',1,2,21);
insert into sqltest values(4,'Jane',1,2,22);
insert into sqltest values(5,'Rick',2,1,22);
insert into sqltest values(6,'Samir',2,6,23);
insert into sqltest values(7,'Kirk',3,6,23);
insert into sqltest values(8,'Alex',3,9,23);
commit;
Sample Proc
create or replace procedure calltest (
rc1 out SYS_REFCURSOR,
pempid number,
porgid number,
pdepid number,
pvendorid number)
is
begin
open rc1 for
select * from sqltest
where
(pempid IS NULL OR empid = pempid) and
(porgid IS NULL OR orgid = porgid) and
(pdepid IS NULL OR depid = pdepid) and
(pvendorid IS NULL OR vendorid = pvendorid);
end;
Sample Call
set autoprint on;
var vcur refcursor;
declare
begin
calltest(rc1=>:vcur,pempid=>NULL,porgid=>1,pdepid=>NULL,pvendorid=>NULL);
--calltest(rc1=>:vcur,pempid=>NULL,porgid=>1,pdepid=>2,pvendorid=>NULL);
--calltest(rc1=>:vcur,pempid=>NULL,porgid=>1,pdepid=>2,pvendorid=>22);
--calltest(rc1=>:vcur,pempid=>NULL,porgid=>NULL,pdepid=>NULL,pvendorid=>23);
end;
Sample Output 1
VCUR
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
EMPID NAME ORGID DEPID VENDORID
--------------------------------------- ---------- --------------------------------------- --------------------------------------- ---------------------------------------
1 BoB 1 1 21
2 Chuck 1 1 21
3 Mary 1 2 21
4 Jane 1 2 22
Sample Output 2
VCUR
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
EMPID NAME ORGID DEPID VENDORID
--------------------------------------- ---------- --------------------------------------- --------------------------------------- ---------------------------------------
3 Mary 1 2 21
4 Jane 1 2 22
Sample Output 3
VCUR
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
EMPID NAME ORGID DEPID VENDORID
--------------------------------------- ---------- --------------------------------------- --------------------------------------- ---------------------------------------
4 Jane 1 2 22
Sample Output 4
VCUR
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
EMPID NAME ORGID DEPID VENDORID
--------------------------------------- ---------- --------------------------------------- --------------------------------------- ---------------------------------------
6 Samir 2 6 23
7 Kirk 3 6 23
8 Alex 3 9 23

Pivoting row dynamically in oracle database

below is the scenario:
(* denotes primary key)
**CompanyUnit**(*Unit_id, unit_loc,Year);
**Employees**(Unit_id,Dept_id,no_of_emp); (unit_id and dept_id are foreign keys)
**ref_department**(*dept_id,dept_name,dept_desc);
sample data:
unit_id unit_loc year
------------------------------------
1 Delhi 2003
2 Mumbai 2004
------------------------------------
dept_id dept_name dept_desc
----------------------------------------
101 ABC ABC-AI
102 ABC ABC-BI
103 ABC ABC-CS
104 XYZ XYZ-Testing
105 XYZ XYZ-Development
----------------------------------------------
unit_id dept_id no_of_emp
----------------------------------------------
1 101 5000
2 102 3000
1 103 4000
1 104 2000
2 105 1000
2 101 3000
----------------------------------------------
Required output: A dynamic view or select:
---------------------------------------------------------------------------
unit_id unit_loc ABC-AI ABC-BI ABC-CS XYZ-Testing XYZ-Development
---------------------------------------------------------------------------
1 Delhi 5000 4000 2000
2 Mumbai 3000 3000 1000
---------------------------------------------------------------------------
The problem is that each new department in ref_department corresponds to a new column in view/select query.
I've written below query:
variable DEPARTMENTS VARCHAR2(100)
BEGIN
:DEPARTMENTS :=NULL;
FOR cur_rec IN (SELECT DISTINCT DEPT_DESC FROM REF_DEPARTMENT) LOOP
:DEPARTMENTS := :DEPARTMENTS || ''''|| cur_rec.DEPT_DESC|| '''' || ',' ;
END LOOP;
dbms_output.put_line(rtrim(:DEPARTMENTS,','));
select * from
(select uid,uloc,uyear, depdesc, emp from
(select C.unit_id as uid, C.unit_loc as uloc,C.year as uyear, E.DEPT_ID as depid,R.DEPT_NAME as depname,R.DEPT_DESC as depdesc,S.NO_OF_EMP as emp
FROM Unit U INNER JOIN Employees E ON U.Unit_id=E.unit_id INNER JOIN REF_DEPARTMENT R ON E.DEPT_ID=R.DEPT_ID))
pivot
(min(emp) for depdesc in (:departments));
END;
error: PL/SQL: ORA-56901: non-constant expression is not allowed for pivot|unpivot
I've referred below links:
Pivoting rows into columns dynamically in Oracle;
http://www.orafaq.com/forum/t/187172/
Long ago (before Oracle's PIVOT queries existed) I wrote a blog post on "Pivot" Queries that gives you the code for a package to help construct such queries.
For your requirement you would do something like this:
declare
rc sys_refcursor;
begin
rc := pivot.pivot_cursor
( group_cols => 'u.unit_id, u.unit_loc'
, pivot_col => 'rd.dept_desc'
, tables => 'CompanyUnit x, Employees e, ref_departmnent rd'
, value_cols => 'e.no_of_emp'
, agg_types => 'sum'
, where_clause => 'e.dept_id = rd.dept_id and e.unit_id = c.unit_id'
);
end;
/
Since the ref cursor can have a variable number of columns you would need to use the DBMS_SQL package to parse it, find out the column names, and run it. The starting point would be to convert the ref cursor to a DBMS_SQL cursor:
n := dbms_sql.to_cursor_number(rc);
You'd need to refer to the DBMS_SQL package documentation example to take this further.

Resources