Oracle Query: Loop through columns dynamically to create rows - oracle

I have the following Oracle database that can have any number of columns that may grow over time by user intervention in the web application.
ID | Name | Football | WhoCreated | When | Baseball | Cheerleading | Swimming
1 | Billy | (null) | sam, smith | (Timestamp)| 1 | (null) | 1
2 | Susie | 1 | sam, smith | (Timestamp)| (null) | 1 | 1
3 | Johnny | 1 | Homer | (Timestamp)| 1 | (null) | (null)
I am trying to generate an output that looks like
2, Susie, Football
3, Johnny, Football
1, Billy, Baseball
3, Johnny, Baseball
2, Susie, Cheerleading
1, Billy, Swimming
2, Susie, Swimming
I can do this with a UNION, but I will have to adjust each for the specific name field. I'm already up to about 50 columns (50 unions), and that can grow at any time by users in the system. To further complicate things, I have a few columns for auditing purposes tucked in the middle of the list. I really need some sort of dynamic way of looping through the columns, I have searched, but none seem to address the issue I have.

This should give you an idea. I couldn't test it, but fairly simple to understand.
create or replace package test_pkg AS
TYPE REP_CURS IS REF CURSOR;
TYPE output_REC IS RECORD(
id_ number,
name_ varchar2(50),
field_ varchar2(50));
TYPE output_TAB IS TABLE OF output_REC;
FUNCTION Get_Data RETURN output_TAB
PIPELINED;
END test_pkg;
CREATE OR REPLACE PACKAGE BODY test_pkg IS
FUNCTION Get_Data RETURN output_TAB
PIPELINED IS
output_REC_ output_REC;
rep_lines_ REP_CURS;
stmt_ VARCHAR2(5000);
table_rec_ yourtable%ROWTYPE;
begin
stmt_ := ' YOUR QUERY HERE ';
OPEN rep_lines_ FOR stmt_;
LOOP
FETCH rep_lines_
INTO table_rec_;
EXIT WHEN rep_lines_%NOTFOUND;
output_REC_.id_ := table_rec_.id;
output_REC_.name_ := table_rec_.name;
if table_rec_.football IS not null then
output_REC_.field_ := table_rec_.football;
PIPE ROW(output_REC_);
end if;
if table_rec_.Baseball IS not null then
output_REC_.field_ := table_rec_.Baseball;
PIPE ROW(output_REC_);
end if;
if table_rec_.Cheerleading IS not null then
output_REC_.field_ := table_rec_.Cheerleading;
PIPE ROW(output_REC_);
end if;
if table_rec_.Swimming IS not null then
output_REC_.field_ := table_rec_.Swimming;
PIPE ROW(output_REC_);
end if;
END LOOP;
CLOSE rep_lines_;
RETURN;
exception
when others then
DBMS_OUTPUT.put_line('Error:' || DBMS_UTILITY.FORMAT_ERROR_BACKTRACE ||
DBMS_UTILITY.FORMAT_ERROR_STACK ||
DBMS_UTILITY.FORMAT_CALL_STACK);
END Get_Data;
END test_pkg;

You should create a function and then use it, please consider the code below:
CREATE OR REPLACE FUNCTION commalist (par_id NUMBER) RETURN VARCHAR2 IS
TYPE curs IS REF CURSOR;
v_emp_cursor curs;
fieldvalue VARCHAR2(4000);
var_out VARCHAR2(4000);
CURSOR col IS
SELECT column_name
FROM user_tab_columns
WHERE table_name = 'TABLE';
BEGIN
FOR reccol IN col LOOP
OPEN v_emp_cursor FOR ('SELECT '||reccol.column_name||' val
FROM TABLE
WHERE id = '||par_id);
LOOP
FETCH v_emp_cursor INTO fieldvalue;
IF fieldvalue IS NOT NULL THEN
var_out := var_out||fieldvalue||', ';
END IF;
EXIT WHEN v_emp_cursor%NOTFOUND;
END LOOP;
CLOSE v_emp_cursor;
END LOOP;
RETURN SubStr(var_out, 0, Length(var_out) - 2);
END;

Try this
with src as (select 1 ID, 'Billy' name, 0 Football, 'sam smith' WhoCreated, sysTimestamp when,
1 Baseball, cast(null as number) Cheerleading, 1 Swimming
from dual
union all
select 2, 'Susie', 1, 'sam smith', sysTimestamp, null, 1, 1
from dual
union all
select 3, 'Johnny', 1, 'Homer', sysTimestamp, 1, null, null
from dual),
unpivottbl as (select *
from src
UNPIVOT
(
VAL
FOR descript
IN (Football, Baseball, Cheerleading, Swimming)
))
select ID, name, descript
from unpivottbl
where VAL = 1
Though it is not dynamic solution, you will need to add the columns for which you are going to unpivot the data. This is reference for unpivot.

Related

How to get distinct items and number of occurrences on a nested table?

I have a nested table that includes a list of emails, which could be inside several times.
The problem is I need to get the distinct list of items, and the number of times it appears on the list.
| emails |
| ------------ |
| a#mail.com |
| b#mail.com |
| c#mail.com |
| d#mail.com |
| c#mail.com |
| c#mail.com |
| a#mail.com |
| a#mail.com |
| b#mail.com |
| b#mail.com |
| c#mail.com |
Ideally, my result would be a table or an output that tells me the following:
| Email | Number |
| ---------- | - |
| a#mail.com | 3 |
| b#mail.com | 3 |
| c#mail.com | 4 |
| d#mail.com | 1 |
To select from a table I would use a select statement, but if I try this in my code I get an error "ORA- 00942: table or view does not exist" same with even a simple select from emails table so I'm just guessing you can't use select on nested tables that way.
The nested table was created like this:
type t_email_type is table of varchar2(100);
t_emails t_email_type := t_email_type();
and then populated under a loop that adds an email for each iteration of the loop:
t_emails.extend;
t_emails(t_emails.LAST) := user_r.email;
I tried to do what you described so far; here you go:
Table that contains e-mail addresses (some of them are duplicates):
SQL> select * from test_mails;
EMAIL
----------
a#mail.com
b#mail.com
c#mail.com
a#mail.com
b#mail.com
Type you created; I think you used type within your PL/SQL procedure. That won't work if it is a function which is supposed to return result of that type because it then must be created at SQL level, so - that's what I'm doing:
SQL> create or replace type t_email_type is table of varchar2(100);
2 /
Type created.
Function: FOR loop selects e-mail addresses from the table and puts them into t_emails. What you're interested in is what follows in lines #12-14 as it shows how to return the result:
SQL> create or replace function f_test
2 return t_email_type
3 is
4 t_emails t_email_type := t_email_type();
5 retval t_email_type;
6 begin
7 for user_r in (select email from test_mails) loop
8 t_emails.extend;
9 t_emails(t_emails.last) := user_r.email;
10 end loop;
11
12 select distinct column_value
13 bulk collect into retval
14 from table(t_emails);
15 return retval;
16 end;
17 /
Function created.
OK, let's test it:
SQL> select * from table(f_test);
COLUMN_VALUE
--------------------------------------------------------------------------------
a#mail.com
b#mail.com
c#mail.com
SQL>
Distinct addresses; that's what you asked for.
Try it like this:
SET SERVEROUTPUT ON
Create or replace type t_email_type is table of varchar2(100);
/
Declare -- Outer block - just to populate variable emails of type t_email_type
Cursor c_emails IS
Select 'a#mail.com' "EMAIL" From Dual Union All
Select 'b#mail.com' "EMAIL" From Dual Union All
Select 'c#mail.com' "EMAIL" From Dual Union All
Select 'd#mail.com' "EMAIL" From Dual Union All
Select 'c#mail.com' "EMAIL" From Dual Union All
Select 'c#mail.com' "EMAIL" From Dual Union All
Select 'a#mail.com' "EMAIL" From Dual Union All
Select 'a#mail.com' "EMAIL" From Dual Union All
Select 'b#mail.com' "EMAIL" From Dual Union All
Select 'b#mail.com' "EMAIL" From Dual Union All
Select 'c#mail.com' "EMAIL" From Dual;
email VarChar2(100);
emails t_email_type := t_email_type();
i Number(3) := 0;
Begin
OPEN c_emails;
LOOP
FETCH c_emails Into email;
EXIT WHEN c_emails%NOTFOUND;
i := i + 1;
emails.extend;
emails(i) := email;
END LOOP;
CLOSE c_emails;
-- Inner block - get distinct emails and number of appearances from variable emails of type t_email_type
Declare
Cursor c Is
Select COLUMN_VALUE "EMAIL", Count(*) "NUM_OF_APPEARANCES"
From TABLE(emails)
Group By COLUMN_VALUE
Order By COLUMN_VALUE;
cSet c%ROWTYPE;
i Number(3) := 0;
Begin
OPEN c;
LOOP
FETCH c Into cSet;
EXIT WHEN c%NOTFOUND;
i := i + 1;
If i = 1 Then
DBMS_OUTPUT.PUT_LINE(RPAD('EMAIL', 20, ' ') || ' ' || LPAD('NUM_OF_APPEARANCES', 20, ' '));
DBMS_OUTPUT.PUT_LINE(RPAD('-', 20, '-') || ' ' || LPAD('-', 20, '-'));
End If;
DBMS_OUTPUT.PUT_LINE(RPAD(cSet.EMAIL, 20, ' ') || ' ' || LPAD(cSet.NUM_OF_APPEARANCES, 20, ' '));
END LOOP;
CLOSE c;
End;
End;
/* R e s u l t :
anonymous block completed
EMAIL NUM_OF_APPEARANCES
-------------------- --------------------
a#mail.com 3
b#mail.com 3
c#mail.com 4
d#mail.com 1
*/
Outer block of this code is here just to generate data and to insert it into emails variable that is of type you have defined (t_email_type). You, probably, need just the inner block to get you list of emails with number of appearances within the table type.
SQL that gives you expected result is, actualy, the cursor in the inner block:
Select COLUMN_VALUE "EMAIL", Count(*) "NUM_OF_APPEARANCES"
From TABLE(emails)
Group By COLUMN_VALUE
Order By COLUMN_VALUE;
Result:
EMAIL
NUM_OF_APPEARANCES
a#mail.com
3
b#mail.com
3
c#mail.com
4
d#mail.com
1
Regards...

Is this possible to apply a function to every fields of table (groupy by type)

I would like to replace every string are null by 'n' and every number by 0.
Is there a way to do that?
With a polymorphic table function I can select all the columns of a certain type but I can't modify the value of the columns.
Yes, it is possible with PTF also. You may modify columns in case you've set pass_through to false for the column in the describe method (to drop it) and copy it into new_columns parameter of the describe.
Below is the code:
create package pkg_nvl as
/*Package to implement PTF*/
function describe(
tab in out dbms_tf.table_t
) return dbms_tf.describe_t
;
procedure fetch_rows;
end pkg_nvl;
/
create package body pkg_nvl as
function describe(
tab in out dbms_tf.table_t
) return dbms_tf.describe_t
as
modif_cols dbms_tf.columns_new_t;
new_col_cnt pls_integer := 0;
begin
/*Mark input columns as used and as modifiable for subsequent row processing*/
for i in 1..tab.column.count loop
if tab.column(i).description.type in (
dbms_tf.type_number,
dbms_tf.type_varchar2
) then
/*Modifiable*/
tab.column(i).pass_through := FALSE;
/*Used in the PTF context*/
tab.column(i).for_read := TRUE;
/* Propagate column to the modified*/
modif_cols(new_col_cnt) := tab.column(i).description;
new_col_cnt := new_col_cnt + 1;
end if;
end loop;
/*Return the list of modified cols*/
return dbms_tf.describe_t(
new_columns => modif_cols
);
end;
procedure fetch_rows
/*Process rowset and replace nulls*/
as
rowset dbms_tf.row_set_t;
num_rows pls_integer;
in_col_vc2 dbms_tf.tab_varchar2_t;
in_col_num dbms_tf.tab_number_t;
new_col_vc2 dbms_tf.tab_varchar2_t;
new_col_num dbms_tf.tab_number_t;
begin
/*Get rows*/
dbms_tf.get_row_set(
rowset => rowset,
row_count => num_rows
);
for col_num in 1..rowset.count() loop
/*Loop through the columns*/
for rn in 1..num_rows loop
/*Calculate new values in the same row*/
/*Get column by index and nvl the value for return column*/
if rowset(col_num).description.type = dbms_tf.type_number then
dbms_tf.get_col(
columnid => col_num,
collection => in_col_num
);
new_col_num(rn) := nvl(in_col_num(rn), 0);
elsif rowset(col_num).description.type = dbms_tf.type_varchar2 then
dbms_tf.get_col(
columnid => col_num,
collection => in_col_vc2
);
new_col_vc2(rn) := nvl(in_col_vc2(rn), 'n');
end if;
end loop;
/*Put the modified column to the result*/
if rowset(col_num).description.type = dbms_tf.type_number then
dbms_tf.put_col(
columnid => col_num,
collection => new_col_num
);
elsif rowset(col_num).description.type = dbms_tf.type_varchar2 then
dbms_tf.put_col(
columnid => col_num,
collection => new_col_vc2
);
end if;
end loop;
end;
end pkg_nvl;
/
create function f_replace_nulls(tab in table)
/*Function to replace nulls using PTF*/
return table pipelined
row polymorphic using pkg_nvl;
/
with a as (
select
1 as id, 'q' as val_vc2, 1 as val_num
from dual
union all
select
2 as id, '' as val_vc2, null as val_num
from dual
union all
select
3 as id, ' ' as val_vc2, 0 as val_num
from dual
)
select
id
, a.val_num
, a.val_vc2
, n.val_num as val_num_repl
, n.val_vc2 as val_vc2_repl
from a
join f_replace_nulls(a) n
using(id)
ID | VAL_NUM | VAL_VC2 | VAL_NUM_REPL | VAL_VC2_REPL
-: | ------: | :------ | -----------: | :-----------
3 | 0 | | 0 |
2 | null | null | 0 | n
1 | 1 | q | 1 | q
db<>fiddle here
Use COALESCE or NVL and list the columns you want to apply them to:
SELECT COALESCE(col1, 'n') AS col1,
COALESCE(col2, 0) AS col2,
COALESCE(col3, 'n') AS col3
FROM table_name;
The way I understood the question, you actually want to modify table's contents. If that's so, you'll need dynamic SQL.
Here's an example; sample data first, with some numeric and character columns having NULL values:
SQL> desc test
Name Null? Type
----------------------------------------- -------- ----------------------------
ID NUMBER
NAME VARCHAR2(20)
SALARY NUMBER
SQL> select * from test order by id;
ID NAME SALARY
---------- ---------- ----------
1 Little 100
2 200
3 Foot 0
4 0
Procedure reads USER_TAB_COLUMNS, checking desired data types (you can add some more, if you want), composes the update statement and executes it:
SQL> declare
2 l_str varchar2(1000);
3 begin
4 for cur_r in (select column_name, data_type
5 from user_tab_columns
6 where table_name = 'TEST'
7 and data_type in ('CHAR', 'VARCHAR2')
8 )
9 loop
10 l_str := 'update test set ' ||
11 cur_r.column_name || ' = nvl(' || cur_r.column_name ||', ''n'')';
12 execute immediate l_str;
13 end loop;
14
15 --
16
17 for cur_r in (select column_name, data_type
18 from user_tab_columns
19 where table_name = 'TEST'
20 and data_type in ('NUMBER')
21 )
22 loop
23 l_str := 'update test set ' ||
24 cur_r.column_name || ' = nvl(' || cur_r.column_name ||', 0)';
25 execute immediate l_str;
26 end loop;
27 end;
28 /
PL/SQL procedure successfully completed.
Result:
SQL> select * from test order by id;
ID NAME SALARY
---------- ---------- ----------
1 Little 100
2 n 200
3 Foot 0
4 n 0
SQL>
You can create a table macro to intercept the columns and replace them with nvl if they're a number or varchar2:
create or replace function replace_nulls (
tab dbms_tf.table_t
)
return clob sql_macro as
stmt clob := 'select ';
begin
for i in 1..tab.column.count loop
if tab.column(i).description.type = dbms_tf.type_number
then
stmt := stmt || ' nvl ( ' ||
tab.column(i).description.name || ', 0 ) ' ||
tab.column(i).description.name || ',';
elsif tab.column(i).description.type = dbms_tf.type_varchar2
then
stmt := stmt || ' nvl ( ' ||
tab.column(i).description.name || ', ''n'' ) ' ||
tab.column(i).description.name || ',';
else
stmt := stmt || tab.column(i).description.name || ',';
end if;
end loop;
stmt := rtrim ( stmt, ',' ) || ' from tab';
return stmt;
end;
/
with a (
aa1,aa2,aa3
) as (
select 1, '2hhhh', sysdate from dual
union all
select null, null, null from dual
)
select * from replace_nulls(a);
AA1 AA2 AA3
---------- ----- -----------------
1 2hhhh 27-JUN-2022 13:17
0 n <null>

Can procedures, cursors and triggers be used within user-defined types with object-relational databases?

I am new to object-relational databases, currently teaching myself from a textbook.
When creating user-defined types in Oracle, with tables of UDT objects, is it possible to use procedures, cursors, triggers etc. or are these functions only usable with relational databases?
I have spent time searching online to find this answer myself but I'm at a point where I have read so much on the topic it has me confused, so I thought it could be answered simply here (hopefully).
When creating user-defined types in Oracle, with tables of UDT objects, is it possible to use procedures, cursors, triggers etc.
Yes.
or are these functions only usable with relational databases?
No.
CREATE TYPE person IS OBJECT(
id NUMBER,
first_name VARCHAR2(50),
last_name VARCHAR2(50),
mother REF PERSON,
MEMBER FUNCTION full_name( self IN PERSON )
RETURN VARCHAR2,
MEMBER FUNCTION children (self IN PERSON )
RETURN SYS_REFCURSOR
);
CREATE TABLE people OF person (
ID PRIMARY KEY
);
CREATE TYPE BODY person IS
MEMBER FUNCTION full_name( self IN PERSON )
RETURN VARCHAR2
IS
BEGIN
RETURN self.first_name || ' ' || self.last_name;
END;
MEMBER FUNCTION children( self IN PERSON )
RETURN SYS_REFCURSOR
IS
p_cursor SYS_REFCURSOR;
BEGIN
OPEN p_cursor FOR
SELECT VALUE(p) AS child
FROM people p
WHERE p.mother.id = self.id;
RETURN p_cursor;
END;
END;
/
CREATE SEQUENCE people__id__seq;
CREATE TRIGGER people__new__trg
BEFORE INSERT ON people
FOR EACH ROW
WHEN (new.ID IS NULL)
BEGIN
:new.ID := PEOPLE__ID__SEQ.NEXTVAL;
END;
/
INSERT INTO people ( first_name, last_name )
SELECT 'Alice', 'Abbot' FROM DUAL UNION ALL
SELECT 'Belle', 'Burns' FROM DUAL UNION ALL
SELECT 'Carol', 'Charles' FROM DUAL;
UPDATE people
SET mother = ( SELECT REF(p) FROM people p WHERE id=2 )
WHERE id = 3;
Then:
SELECT id,
first_name,
last_name,
p.mother.id
FROM people p;
Outputs:
ID | FIRST_NAME | LAST_NAME | MOTHER.ID
-: | :--------- | :-------- | --------:
1 | Alice | Abbot | null
2 | Belle | Burns | null
3 | Carol | Charles | 2
Showing that the trigger has worked, generating ID values for the rows.
and
DECLARE
p_person PERSON;
p_child PERSON;
p_cursor SYS_REFCURSOR;
BEGIN
SELECT VALUE( p )
INTO p_person
FROM people p
WHERE id = 2;
p_cursor := p_person.children();
DBMS_OUTPUT.PUT_LINE( 'Children of ' || p_person.full_name );
LOOP
FETCH p_cursor INTO p_child;
EXIT WHEN p_cursor%NOTFOUND;
DBMS_OUTPUT.PUT_LINE( p_child.full_name );
END LOOP;
CLOSE p_cursor;
END;
/
Outputs:
Children of Belle Burns
Carol Charles
Showing an example of a cursor from a function. Procedures are similarly possible and are left as an example for the OP.
db<>fiddle here

How to write store procedure with object type as out parameter in Pl/SQL with multiple joins

I am trying to write a store procedure with below sample use case and tables,
Employee
Emp_id Emp_Name
1 Jhon
2 Mark
3 Marry
Department
Emp_Id Dept_Id
1 A
2 B
3 C
1 B
2 D
Assets
Emp_Id Asset_Name
1 AA
1 BB
2 CC
2 DD
4 EE
4 FF
Relationship
One employee can be added to more than one department.
e.g Emp 1 added to A and B Department.
One Employee Can have more than one Assets
e.g Emp 1 owing Assets AA and BB.
No Foreign key constraint between Employee And Assets.
So Assets can have EmpId which is not available in Employee Table.
e.g Emp 4
Desired output
EmployeeInfo
Emd_Id
Emp_Name
Array of Dept_Id[]
Array of Assets[]
Desired Result for employee Id 1
Emd_Id :1
Emp_Name :Jhon
Array of Dept_Id[] :[A,B]
Array of Assets[] :[AA,BB]
Desired Result for employee Id 4
Emd_Id :4
Emp_Name :null -- As no entry in Employee table.
Array of Dept_Id[] :null
Array of Assets[] :[EE,FF]
So want write a store procedure for this.Please suggest solution for this.
Either this can be achieved with multiple cursor or object type out variable?
Stored Procedure I tried as below,
CREATE OR REPLACE PROCEDURE PRC_TEST(
employeeInfo OUT SYS_REFCURSOR)
IS
BEGIN
OPEN employeeInfo FOR
SELECT e.EMP_ID ,e.EMP_NAME,
d.DEPT_ID,
a.ASSET_NAME
FROM EMPLOYEE e, DEPARTMENT d, ASSETS a
WHERE
e.EMP_ID = d.EMP_ID
AND e.EMP_ID = a.EMP_ID;
END;
Thanks in advance
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE Employee ( Emp_id, Emp_Name ) AS
SELECT 1, 'Jhon' FROM DUAL UNION ALL
SELECT 2, 'Mark' FROM DUAL UNION ALL
SELECT 3, 'Marry' FROM DUAL
/
CREATE TABLE Department ( Emp_Id, Dept_Id ) AS
SELECT 1, 'A' FROM DUAL UNION ALL
SELECT 2, 'B' FROM DUAL UNION ALL
SELECT 3, 'C' FROM DUAL UNION ALL
SELECT 1, 'B' FROM DUAL UNION ALL
SELECT 2, 'D' FROM DUAL
/
CREATE TABLE Assets ( Emp_Id, Asset_Name ) AS
SELECT 1, 'AA' FROM DUAL UNION ALL
SELECT 1, 'BB' FROM DUAL UNION ALL
SELECT 2, 'CC' FROM DUAL UNION ALL
SELECT 2, 'DD' FROM DUAL UNION ALL
SELECT 4, 'EE' FROM DUAL UNION ALL
SELECT 4, 'FF' FROM DUAL
/
CREATE TYPE StringLIst IS TABLE OF VARCHAR2(20)
/
CREATE TYPE Emp_Dept_Assets_Obj AS OBJECT(
Emp_id INTEGER,
Emp_Name VARCHAR2(50),
Depts StringList,
Assets StringList
)
/
CREATE FUNCTION get_Details(
i_emp_id IN Employee.EMP_ID%TYPE
) RETURN Emp_Dept_Assets_Obj
IS
o_details Emp_Dept_Assets_Obj;
BEGIN
o_details := Emp_Dept_Assets_Obj( i_emp_id, NULL, NULL, NULL );
BEGIN
SELECT Emp_Name
INTO o_details.Emp_Name
FROM Employee
WHERE emp_id = i_emp_id;
EXCEPTION
WHEN NO_DATA_FOUND THEN
NULL;
END;
SELECT dept_id
BULK COLLECT INTO o_details.Depts
FROM Department
WHERE emp_id = i_emp_id;
SELECT asset_name
BULK COLLECT INTO o_details.Assets
FROM Assets
WHERE emp_id = i_emp_id;
RETURN o_details;
END;
/
Query 1:
SELECT d.details.emp_id,
d.details.emp_name,
d.details.depts,
d.details.assets
FROM (
SELECT get_Details( LEVEL ) AS details
FROM DUAL
CONNECT BY LEVEL <= 4
) d
Results:
| DETAILS.EMP_ID | DETAILS.EMP_NAME | DETAILS.DEPTS | DETAILS.ASSETS |
|----------------|------------------|---------------|----------------|
| 1 | Jhon | A,B | AA,BB |
| 2 | Mark | B,D | CC,DD |
| 3 | Marry | C | |
| 4 | (null) | | EE,FF |
It is best practice to create a package definition and body. Your package definition will be something like below:
CREATE OR REPLACE PACKAGE EmployeeInfo AS
TYPE departament_type IS TABLE OF VARCHAR2(100);
TYPE assets_type IS TABLE OF VARCHAR2(100);
PROCEDURE get_employee_info ( Emp_Id_col IN OUT NUMBER
,Emp_Name_col OUT VARCHAR2
,departament_tbl OUT DEPARTAMENT_TYPE
,assets_tbl OUT ASSETS_TYPE);
END EmployeeInfo;
Then your package body will match your definition and it is where the procedure is going to be implemented:
CREATE OR REPLACE PACKAGE BODY EmployeeInfo AS
PROCEDURE get_employee_info ( Emp_Id_col IN OUT NUMBER
,Emp_Name_col OUT VARCHAR2
,departament_tbl OUT DEPARTAMENT_TYPE
,assets_tbl OUT ASSETS_TYPE)
IS
BEGIN
BEGIN
SELECT Emp_Name
INTO Emp_Name_col
FROM Employee
WHERE Emp_Id = Emp_Id_col;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RETURN;
END;
departament_tbl := DEPARTAMENT_TYPE();
FOR dep_rec IN (SELECT *
FROM Department
WHERE Emp_id = Emp_Id_col) LOOP
departament_tbl.extend;
departament_tbl(departament_tbl.COUNT) := dep_rec.Dep_Id;
END LOOP;
assets_tbl := ASSETS_TYPE();
FOR asset_rec IN (SELECT *
FROM Assets
WHERE Emp_Id = Emp_Id_col) LOOP
assets_tbl.extend;
assets_tbl(assets_tbl.COUNT) := asset_rec.Asset_Name;
END LOOP;
END get_employee_info;
END EmployeeInfo;
Now here there is a very simple test script for your stored procedure:
DECLARE
Emp_Id_col NUMBER(10);
Emp_Name_col Employee.Emp_Name%TYPE;
departament_tbl EMPLOYEEINFO.DEPARTAMENT_TYPE;
assets_tbl EMPLOYEEINFO.ASSETS_TYPE;
BEGIN
Emp_Id_col := 1;
EMPLOYEEINFO.GET_EMPLOYEE_INFO(Emp_Id_col
,Emp_Name_col
,departament_tbl
,assets_tbl);
DBMS_OUTPUT.PUT_LINE(Emp_Name_col || ' - ' ||
departament_tbl.COUNT || ' - ' ||
assets_tbl.COUNT);
END;

Re-write table update as set instead of iterative method

I have a list of user email addresses, in a test Oracle database, which are currently all set to the same value. I want to replace these with unique entries, with a number of invalid addresses and null values mixed in. My table currently looks like this and is around 250k rows in total.(I've excluded the null and invalid entries to save some space)
+-------------+--------------------+
| employee_id | email |
+-------------+--------------------+
| 1 | test#testemail.com |
| 2 | test#testemail.com |
| 3 | test#testemail.com |
|... |... |
+-------------+--------------------+
And I'd like it to look like this
+-------------+---------------------+
| employee_id | email |
+-------------+---------------------+
| 1 | test1#testemail.com |
| 2 | test2#testemail.com |
| 3 | test3#testemail.com |
|... |... |
+-------------+---------------------+
I've wrote the following PL/SQL to do the change, it works, but it's seems very inefficient. Can I use another method to take advantage of set processing?
Thanks for any help with this.
DECLARE
i number(20);
l_employee_id hr.employees.employee_id%TYPE;
output_query varchar2(1000);
CURSOR c_cursor IS
SELECT employee_id
FROM hr.employees;
PROCEDURE update_sql(id_num IN hr.employees.employee_id%TYPE,
email IN VARCHAR2) IS
BEGIN
output_query := 'UPDATE hr.employees
SET email = '''|| email ||'''
WHERE employee_id = '|| id_num;
dbms_output.put_line(output_query); --for debug
EXECUTE IMMEDIATE output_query;
END;
BEGIN
OPEN c_cursor;
i := 1;
<<outer_loop>>
LOOP
For j IN 1..5 LOOP
FETCH c_cursor INTO l_employee_id;
EXIT outer_loop WHEN c_cursor%NOTFOUND;
IF j <= 3 THEN
update_sql(l_employee_id, ('test' || i || '#testemail.com'));
ELSIF j = 4 THEN
update_sql(l_employee_id, ('test' || i || 'testemail.com'));
ELSIF j = 5 THEN
update_sql(l_employee_id, ' ');
END IF;
i := i + 1;
END LOOP;
END LOOP outer_loop;
CLOSE c_cursor;
END;
/
EDIT - 26/09/2016 - to clarify size of table.
create table emp (emp_id number, email varchar2(32));
insert into emp select level as emp_id, 'test#testemail.com' as email
from dual connect by level<=2500000;
update emp set email = regexp_replace(email, '(\w+)(#\w+\.\w+)', '\1' || emp_id || '\2');
--250,000 rows updated ~16 sec.
EMP_ID, EMAIL
1 test1#testemail.com
2 test2#testemail.com
3 test3#testemail.com
...
drop table emp;
First off, even if you were going to code this iteratively, please don't use dynamic SQL where it is not necessary. And it is only necessary if you don't know the table or columns you are going to be querying at compile time.
That said, it sounds like you just want
UPDATE employees
SET email = (case when employee_id <= 3
then 'test' || employee_id || '#testemail.com'
when employee_id = 4
then 'test' || employee_id || 'testemail.com'
when employee_id = 5
then ' '
else null
end)
Oracle Setup:
create table employees (
id NUMBER,
email VARCHAR2(100)
);
INSERT INTO employees
SELECT 1, 'test#testemail.com' FROM DUAL UNION ALL
SELECT 2, 'test#testemail.com' FROM DUAL UNION ALL
SELECT 3, 'test#testemail.com' FROM DUAL UNION ALL
SELECT 4, 'test#testemail.com' FROM DUAL UNION ALL
SELECT 5, 'test#testemail.com' FROM DUAL UNION ALL
SELECT 6, 'test#testemail.com' FROM DUAL;
Query:
update employees
set email = CASE MOD( ROWNUM, 5 )
WHEN 4 THEN 'test' || ROWNUM || 'testemail.com'
WHEN 0 THEN ''
ELSE 'test' || ROWNUM || '#testemail.com'
END;
Output:
SELECT * FROM employees;
ID EMAIL
---------- ------------------------
1 test1#testemail.com
2 test2#testemail.com
3 test3#testemail.com
4 test4testemail.com
5
6 test6#testemail.com

Resources