'Member of' in Oracle - oracle

I am trying to use member of in Oracle.
I am able to use this when table type is of number or any other data type. Below is the code for this:
declare
type t is table of number;
lt t;
begin
select channel_key
bulk collect into lt
from dim_channels;
if 22 member of lt then
dbms_output.put_line('ss');
end if;
end;
How do I use member of when the table is based on a record as in the code below.
declare
type rt is record
(
channel_key number(10),
channel_code varchar2(100)
);
type t is table of rt;
lt t;
lrt rt;
begin
select channel_key, channel_code
bulk collect into lt
from dim_channels;
end;

This won't work with plain local PL/SQL record types. To include more attributes you will need an object type with a MAP or ORDER function:
create or replace type demo_ot as object
( channel_key integer
, channel_code varchar2(30)
, map member function demo_map return varchar2 )
/
create or replace type body demo_ot as
map member function demo_map return varchar2
is
begin
return self.channel_key || '<#>' || self.channel_code;
end demo_map;
end;
/
declare
type demo_t is table of demo_ot; -- You would normally create this globally in SQL
my_set demo_t;
my_object demo_ot;
begin
select demo_ot(ckey, ccode)
bulk collect into my_set
from ( select 1 as ckey, 'One' as ccode from dual
union all
select 2 as ckey, 'Two' as ccode from dual );
my_object := demo_ot(2, 'Two');
if my_object member of my_set then
dbms_output.put_line('Member found');
else
dbms_output.put_line('Member not found');
end if;
end;
/

I created below code to test it
create or replace PROCEDURE P_MEMBER_OF_TEST(p_fname IN VARCHAR2,
p_lname in varchar2)
AS
type type_rec is record
(
first_name employees.first_name%type,
last_name employees.last_name%type
);
TYPE T_TAB_TYPE IS TABLE OF type_rec;
T_TAB T_TAB_TYPE;
t_rec type_rec;
i int;
BEGIN
t_rec.first_name := p_fname;
t_rec.last_name := p_lname;
SELECT FIRST_NAME,last_name bulk collect INTO T_TAB FROM
EMPLOYEES;
dbms_output.put_line(t_rec.first_name || ',' || t_rec.last_name);
IF t_rec MEMBER OF T_TAB THEN
DBMS_OUTPUT.PUT_LINE ('YES');
ELSE
DBMS_OUTPUT.PUT_LINE('NO');
END IF;
END;
It compiled with no issues however when i execute it i get error that my connection has been reset , when i comment the if-else-end if block . it gets executed. Can you also suggest what is the problem in code #William Robertson

Related

Oracle PL/SQL - procedure with array parameter

I need to write an oracle procedure which will have an array of ID's as parameter.
Then I will return a cursor which contains result of select(1).
(1) - select * from table where id in(ID's)
As an option we can pass a string param and then convert string to array.
DECLARE
info sys_refcursor ;
error varchar(255);
BEGIN
package.test_function('1,2,3',info ,error);// info will contain a result cursor for select(1)
END;
Do you have other ideas?
You can create a user-defined collection type:
CREATE TYPE int8_list IS TABLE OF NUMBER(8,0);
Then your package:
CREATE PACKAGE pkg_name AS
PROCEDURE proc_name (
i_ids IN int8_list,
o_cursor OUT SYS_REFCURSOR
);
END;
/
CREATE PACKAGE BODY pkg_name AS
PROCEDURE proc_name (
i_ids IN int8_list,
o_cursor OUT SYS_REFCURSOR
)
IS
BEGIN
OPEN o_cursor FOR
SELECT * FROM table_name WHERE id MEMBER OF i_ids;
END;
END;
/
Then you can call the procedure:
DECLARE
v_info sys_refcursor ;
v_id TABLE_NAME.ID%TYPE;
v_value TABLE_NAME.VALUE%TYPE;
BEGIN
pkg_name.proc_name(int8_list(1,2,3), v_info);
LOOP
FETCH v_info INTO v_id, v_value;
EXIT WHEN v_info%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(v_id || ' ' || v_value);
END LOOP;
END;
/
Which, for the sample data:
CREATE TABLE table_name (id, value) AS
SELECT LEVEL, CHR(64+LEVEL) FROM DUAL CONNECT BY LEVEL <= 5;
Outputs:
1 A
2 B
3 C
db<>fiddle here

How it insert result of select statement into table of records (associative array)

I am working on HR scheme of oracle. I have table of records
type emp_record is RECORD(emp_first_name employees.first_name%type,
emp_last_name employees.last_name%type,
);
type emp_record_table is table of emp_record
index by pls_integer;
I want to insert into emp_record_table results of the next select statement
select first_name, last_name
from employees
where department_id=30;
can you explain me how to solve this problem? thank you.
Simplest approach is to use bulk collect:
declare
type emp_record is RECORD(emp_first_name employees.first_name%type,
emp_last_name employees.last_name%type
);
type emp_record_table is table of emp_record
index by pls_integer;
l_recs emp_record_table;
begin
select first_name, last_name
bulk collect into l_recs
from employees
where department_id=30;
for idx in l_recs.first()..l_recs.last() loop
dbms_output.put_line(l_recs(idx).emp_first_name ||' '|| l_recs(idx).emp_last_name);
end loop;
end;
/
Note that you don't really need an associative array to process records like this. You can ditch the index by pls_integer and things will still work just fine. The value of associative arrays is when we need to maintain an access path to specific rows. For instance, we might want to use the primary key of the employees table to index the array. This would create a sparse array, because the selected employee IDs are not guaranteed to form a contiguous sequence. Consequently the logic for wrangling the array is more verbose:
declare
type emp_record is RECORD(emp_first_name employees.first_name%type,
emp_last_name employees.last_name%type
);
type emp_record_table is table of emp_record
index by pls_integer;
l_recs emp_record_table;
idx pls_integer;
begin
for r in (select emp_id, first_name, last_name
from employees
where department_id=30 )
loop
l_recs(r.emp_id).emp_first_name := r.first_name;
l_recs(r.emp_id).emp_last_name := r.last_name;
end loop;
idx := l_recs.first();
while idx is not null loop
dbms_output.put_line(l_recs(idx).emp_first_name ||' '|| l_recs(idx).emp_last_name);
idx := l_recs.next(idx);
end loop;
end;
/
Here is a demo on db<>fiddle.
Oracle Setup:
CREATE TABLE employees (
id NUMBER(8,0) PRIMARY KEY,
first_name VARCHAR2(50),
last_name VARCHAR2(80)
);
CREATE PACKAGE test_pkg IS
TYPE emp_record IS RECORD(
emp_first_name employees.first_name%type,
emp_last_name employees.last_name%type
);
TYPE emp_record_table IS TABLE OF emp_record INDEX BY pls_integer;
END;
/
INSERT INTO employees( id, first_name, last_name )
SELECT -1, 'a', 'aaa' FROM DUAL UNION ALL
SELECT +3, 'b', 'bbb' FROM DUAL;
PL/SQL Block:
DECLARE
x PLS_INTEGER;
emps test_pkg.emp_record_table;
BEGIN
-- Populate the associative array
FOR row IN ( SELECT * FROM employees ) LOOP
emps(row.id).emp_first_name := row.first_name;
emps(row.id).emp_last_name := row.last_name;
END LOOP;
-- Read the associative array
x := emps.FIRST;
WHILE x IS NOT NULL LOOP
DBMS_OUTPUT.PUT_LINE( x || ': ' || emps(x).emp_first_name || ' ' || emps(x).emp_last_name );
x := emps.NEXT(x);
END LOOP;
END;
/
Output:
-1: a aaa
3: b bbb
db<>fiddle here
You don't need associative array for that operation. Nested table will be perfect for that. Just declare your table type as following:
type emp_record_table is table of emp_record;
Firstly you have to declare table of your created type. Now you have only declared type or record and type of table of these records. You don't have yet your table. You can daclare it following way in the DECLARE section:
l_emp_table emp_record_table;
Then initialize it:
l_emp_table := emp_record_table();
Then you create cursor that will get data from your SELECT query. If you don't know how to do it, please read about cursor declaration, and fetching.
Next step will be: for each cursor row insert its data into table. You write simple loop that will do following steps:
Extend your declared table l_emp_table.extend()
Save data into table l_emp_table(i).emp_first_name := FETCHED_ROW.first_name and so on...

Using count(*) to fetch more than one row in a SQL Procedure

I'm trying to return the number of rows per invoice_id using a function and procedure. Some invoice_id's have more than one row and I'm not sure how to fetch the count when I execute my procedure. As an example invoice_id(7) has just one row, but invoice_id(100) has four rows of information.
Create or replace function return_num_rows_function(invoice_id_text in varchar2)
Return varchar2
Is inv_id varchar2(20);
Begin
Select count(*)invoice_id into inv_id from invoice_line_items where invoice_id=invoice_id_text;
Return inv_id;
End;
Create or replace procedure return_num_rows (invoice_id_text in varchar2)
Is inv_id varchar(20);
line_item_desc invoice_line_items.line_item_description%type;
Begin
inv_id := return_num_rows_function(invoice_id_text);
If inv_id is not null then
Select count(*)invoice_id, line_item_description into inv_id,line_item_desc
From invoice_line_items where invoice_id = inv_id;
dbms_output.put_line('The number of rows returned:'|| inv_id);
dbms_output.put_line('Item description(s):'|| line_item_desc);
End if;
End;
set serveroutput on;
execute return_num_rows(7);
First of all do not use a string type variable for a numeric one
(invoice_id_text).
For your case it's better to use a procedure instead of called
function ( return_num_rows_function ), since you need two out
arguments returned.
A SQL Select statement cannot be used without Group By with aggegated and non-aggregated columns together ( i.e. don't use this one :
Select count(*) invoice_id, line_item_description
into inv_id,line_item_desc
From invoice_line_items
Where invoice_id = inv_id;
)
So, Try to create below procedures :
SQL> CREATE OR REPLACE Procedure
return_num_rows_proc(
i_invoice_id invoice_line_items.invoice_id%type,
inv_id out pls_integer,
line_item_desc out invoice_line_items.line_item_description%type
) Is
Begin
for c in
(
Select line_item_description
into line_item_desc
From invoice_line_items
Where invoice_id = i_invoice_id
)
loop
line_item_desc := line_item_desc||' '||c.line_item_description;
inv_id := nvl(inv_id,0) + 1;
end loop;
End;
/
SQL> CREATE OR REPLACE Procedure
return_num_rows(
i_invoice_id pls_integer
) Is
inv_id pls_integer;
line_item_desc invoice_line_items.line_item_description%type;
Begin
return_num_rows_proc(i_invoice_id,inv_id,line_item_desc);
If inv_id is not null then
dbms_output.put_line('The number of rows returned:' || inv_id);
dbms_output.put_line('Item description(s):' || line_item_desc);
End if;
End;
/
and call as in your case :
SQL> set serveroutput on;
SQL> execute return_num_rows(7);
Replace inv_id varchar2(20) with inv_id number;
and also if you want to get two outputs from procedure better to use refcursor.

ORA-06504: PL/SQL: Return types of Result Set variables while execution

I created an Object and procedure as below and while execution i get the below error.
ORA-06504: PL/SQL: Return types of Result Set variables or query do
not match ORA-06512: at line 8
CREATE OR REPLACE TYPE OBJ_TST AS OBJECT
(
COl_ID NUMBER (30, 0),
Col_DATE TIMESTAMP (6)
);
/
create or replace TYPE OBJ_TBL AS TABLE OF OBJ_TST;
/
CREATE OR REPLACE PROCEDURE TST_OBJ (input_date IN DATE,
out_cur OUT SYS_REFCURSOR )
AS
l_tab OBJ_TBL := OBJ_TBL ();
BEGIN
SELECT OBJ_TST (ti.col_id, ti.col_date)
BULK COLLECT INTO l_tab
FROM MY_TBL ti
WHERE ti.create_date BETWEEN input_date AND input_date + 1;
Open o_cur for select col_id,col_date from table(l_tab);
END TST_OBJ;
/
Execution brings me the above mentioned error. MY_TBL has column data type of (col_id and col_date) same as of my object.
DECLARE
a SYS_REFCURSOR;
var1 OBJ_TBL;
BEGIN
TST_OBJ (input_date => '21-Aug-2017', out_cur => a);
FETCH a bulk collect INTO var1;
For rec in 1..var1.count
LOOP
DBMS_OUTPUT.put_line (var1(rec).col_id ||' '|| var1(rec).Col_DATE);
END LOOP;
END;
/
ORA-06504: PL/SQL: Return types of Result Set variables or query do
not match ORA-06512: at line 8
However when i excute like this it works fine:
DECLARE
a SYS_REFCURSOR;
var1 NUMBER;
var2 TIMESTAMP (6);
BEGIN
TST_OBJ (i_date => '21-Aug-2017', out_cur => a);
LOOP
FETCH a INTO var1, var2;
EXIT WHEN a%NOTFOUND;
DBMS_OUTPUT.put_line (var1 ||' '|| var2);
END LOOP;
END;
Can anyone please suggest whats wrong here ?
You're using a table collection expression to unnest your table collection:
Open out_cur for select col_id,col_date from table(l_tab);
The query is returning two relational columns, not a single object, so your cursor has two columns too. Trying to bulk collect two relational columns into a single object in your anonymous block is throwing the exception.
You could, I suppose, recombine them as objects:
Open out_cur for select OBJ_TST(col_id,col_date) from table(l_tab);
or if you don't want to explicitly list the column/field names:
Open out_cur for select cast(multiset(select * from table(l_tab)) as obj_tbl) from dual;
But then in your example having the table type is a bit pointless, and you can just do:
CREATE OR REPLACE PROCEDURE TST_OBJ (input_date IN DATE,
out_cur OUT SYS_REFCURSOR )
AS
BEGIN
Open out_cur for
SELECT OBJ_TST (ti.col_id, ti.col_date)
FROM MY_TBL ti
WHERE ti.create_date BETWEEN input_date AND input_date + 1;
END TST_OBJ;
/
But I image you have some other use for the collection inside the function - modifying it before querying and returning it. Or you could make the second argument of OBJ_TBL type instead of a ref cursor, so the caller doesn't have to bulk collect that into its own local collection itself.
DECLARE
a SYS_REFCURSOR;
var1 OBJ_TBL;
BEGIN
TST_OBJ (input_date => '21-Aug-2017', out_cur => a);
FETCH a bulk collect INTO var1;
For rec in 1..var1.count
LOOP
DBMS_OUTPUT.put_line (var1(rec).col_id ||' '|| var1(rec).Col_DATE);
END LOOP;
END;
/
The cursor a has two columns and you are trying to bulk collect them into a single variable. Oracle will not wrap them in a OBJ_TST object and it can't match them.
Why use cursors at all:
CREATE OR REPLACE PROCEDURE TST_OBJ (
input_date IN DATE,
out_objs OUT OBJ_TBL
)
AS
BEGIN
SELECT OBJ_TST( col_id, col_date)
BULK COLLECT INTO out_objs
FROM MY_TBL
WHERE create_date BETWEEN input_date AND input_date + 1;
END TST_OBJ;
/
Then you can just do:
DECLARE
var1 OBJ_TBL;
BEGIN
TST_OBJ (
input_date => DATE '2017-08-21',
out_objs => var1
);
For rec in 1..var1.count LOOP
DBMS_OUTPUT.put_line (var1(rec).col_id ||' '|| var1(rec).Col_DATE);
END LOOP;
END;
/

Can't retrieve data using dynamic SQL from the function that returns the table

My types:
TYPE T_rowBalanceListForClient IS RECORD
(
RowCode Asset.RowCode%TYPE,
RowName Asset.RowName%TYPE
);
TYPE T_tableBalanceListForClient IS TABLE OF T_rowBalanceListForClient;
My function:
FUNCTION F_BalanceListForClient
(
p_ClientId Client.ClientId%TYPE
)
RETURN T_tableBalanceListForClient PIPELINED
AS
CURSOR CUR_TABLE IS
SELECT
RowCode,
RowName
FROM Asset;
BEGIN
FOR CUR_REC IN CUR_TABLE LOOP
PIPE ROW(CUR_REC);
END LOOP;
END;
Part of my stored procedure:
sql_statement := ' SELECT * FROM TABLE(:1)';
OPEN c_Result FOR sql_statement USING F_BalanceListForClient(11);
While building the package I reseive the Oracle error:
PLS-00457: expressions have to be of SQL types
In the common stored procedures calls like this are built and operate well (not dynamics):
PROCEDURE GET_BALANCE_STANDARD_LIST
(
c_Result OUT SYS_REFCURSOR,
p_ClientId Client.ClientId%TYPE DEFAULT NULL
)
AS
BEGIN
OPEN c_Result FOR
SELECT RowName FROM TABLE(F_BalanceListForClient(p_ClientId));
END;
Appreciate any help.
Thanks.
CREATE OR REPLACE TYPE T_rowBalanceListForClient IS OBJECT
(
RowCode NUMBER,
RowName VARCHAR2(200)
);
CREATE OR REPLACE TYPE T_tableBalanceListForClient AS TABLE OF T_rowBalanceListForClient;
/
CREATE OR REPLACE FUNCTION F_BalanceListForClient
(
p_ClientId NUMBER
)
RETURN T_tableBalanceListForClient PIPELINED
AS
CURSOR CUR_TABLE IS
SELECT
RowCode,
RowName
FROM Assets
; --put a filter of the p_clientId
BEGIN
FOR CUR_REC IN CUR_TABLE
LOOP
pipe row (T_rowBalanceListForClient (CUR_REC.RowCode, CUR_REC.RowName));
END LOOP;
RETURN;
END;
/
CREATE OR REPLACE PROCEDURE GET_BALANCE_STANDARD_LIST
(
c_Result OUT SYS_REFCURSOR,
p_ClientId NUMBER DEFAULT NULL
)
AS
sql_statement varchar2(200);
BEGIN
sql_statement := ' SELECT * FROM TABLE(F_BalanceListForClient(:1))';
OPEN c_Result FOR sql_statement USING p_ClientId;
END;
/
BEGIN
GET_BALANCE_STANDARD_LIST(:cur ,11);
END;
/

Resources