PLSQL Iterate over object attributes - sql

I'm new in PLSQL (about a week) and I have an object with hundreds attributes and I need to check which ones are filled and null to add to a clob.
It is possible to iterate in the object attributes like
for i in object loop
object.i
end loop;

Using standard PL/SQL construct you cannot. You can iterate through a collection using
for i in mycol.FIRST .. mycol.LAST loop
Variable i is not index but one element of the iterated collection.
If your object is an Oracle TYPE you can query a system view ALL_TYPE_ATTRS. This view is part of a database catalog. You find its description in Oracle Reference (not Oracle SQL Reference!). You get a list of attribute names, I'm not sure, if you can access it in dynamic way.
The simplest way is to write a set of helper procedures which will check an attribute according its type and manage its value and then simply call this procedure.
declare
procedure Manage_Char_Attr(value IN VARCHAR2, pclob CLOB) IS
BEGIN
if value is null then
...
END;
procedure Manage_Number_Attr(value IN NUMBER, pclob CLOB) IS
...
procedure Manage_Date_Attr(value IN DATE, pclob CLOB) IS
...
CLOB myclob
begin
-- initialize your CLOB
...
-- manage attributes
Manage_Char_Attr(attr1, myclob); -- let attr1 is a string
Manage_Char_Attr(attr2, myclob); -- let attr2 is a string
Manage_Numvber_Attr(attr3, myclob); -- let attr3 is a number
Manage_Date_Attr(attr4, myclob); -- let attr4 is a date
...
end;

Related

Why do I get this Error: ORA-00932: inconsistent datatypes: expected - got -

I am trying to cast a number to a varchar with the CAST function in order to be able to concatenate inside dbms_output.put_line().
Please note that you need to be able to understand collection types in Oracle PL-SQL to be able to understand my code.
Please see below code:
CREATE TYPE items_va AS VARRAY(5) OF orders_nt;
/
CREATE TYPE items_nt AS TABLE OF VARCHAR(60);
/
CREATE TYPE orders_ot AS OBJECT (order_id NUMBER, items items_nt);
/
CREATE OR REPLACE TYPE orders_nt IS TABLE OF orders_ot;
/
CREATE OR REPLACE TYPE orders_va IS VARRAY(5) OF orders_ot;
/
CREATE TABLE monthly_orders
(act_id NUMBER,
act_month VARCHAR2(8),
order_info orders_nt)
NESTED TABLE order_info STORE AS order_store
(NESTED TABLE items STORE AS item_store);
INSERT INTO monthly_orders
(act_id,
act_month,
order_info)
Values
(1,
'JANUARY',
orders_nt(
orders_ot(1, items_nt('Bike', 'Treadmill')),
orders_ot(2, items_nt('Weights'))
)
);
DECLARE
CURSOR cur_emp IS
select cast( collect(order_info) as orders_va)
from monthly_orders;
empt_t orders_va;
BEGIN
OPEN cur_emp;
FETCH cur_emp INTO empt_t;
CLOSE cur_emp;
FOR i IN empt_t.FIRST .. empt_t.LAST LOOP
dbms_output.put_line('Index counter: '|| CAST(empt_t(i).order_id as VARCHAR));
END LOOP;
END;
If my logic is correct I must see a string like this one "Index counter: 1" print out for me.
Guessing at your problem specification, there seem to be two problems in your code.
First, you define an object type and a nested table type - as nested table of objects. Your table column data type is not the object type you defined, but the collection of such objects (the nested table type). Finally, you define orders_va as a variable array of objects, and you attempt to collect values from the table column into this array. This is the first problem: the column values are not individual objects, they are collections of objects. If you want to collect them into the orders_va array, then the array must be an array of nested tables of objects, not an array of individual objects.
After you make this change, in the anonymous block you will not be able to reference the order_id member of an element of the array - because the array elements are nested tables of objects, they are not individual objects. To access the object members, you will need to loop over nested table elements (within the outer loop over array elements).
The two changes are in the definition of orders_va and the loop in the anonymous block.
........
CREATE OR REPLACE TYPE orders_va IS VARRAY(5) OF orders_nt; -- not orders_ot!
/
........
DECLARE
CURSOR cur_emp IS
select cast( collect(order_info) as orders_va)
from monthly_orders;
empt_t orders_va;
BEGIN
OPEN cur_emp;
FETCH cur_emp INTO empt_t;
CLOSE cur_emp;
FOR i IN empt_t.FIRST .. empt_t.LAST LOOP
for j in empt_t(i).first .. empt_t(i).last loop -- add this nested loop!
dbms_output.put_line('Index counter: '||
CAST(empt_t(i)(j).order_id as VARCHAR));
end loop;
END LOOP;
END;
/
Index counter: 1
Index counter: 2
PL/SQL procedure successfully completed.
Note that empt_t is an array of nested tables of objects; empt_t(i) is one of those nested tables, and empt_t(i)(j) is the j'th object in the nested table. i corresponds to rows in your table; j corresponds to individual objects in the nested table which is an atomic value in your table column (in row i).

Save and return multiple rows within function pl/sql oracle

declare
type t_trayIds is table of number(38,0) index by binary_integer;
v_trayIdsTable t_trayIds;
create or replace function F_getTrayIdByDiameter(v_diameterInCm tray.diameterincm%TYPE)
return t_trayIds
as
v_trayIdsTable t_trayIds := null;
begin
select t.trayid into v_trayIds from tray t
where t.diameterincm = v_diameterincm;
return v_trayIdsTable;
end;
So what I want is, to ask for all Tray IDs with a specific Diameter and store them in an Array or Table. In Java I used ArrayList. I want to return the Table in the end to pass the result onto another function. The above code doesn't seem to work. SQL Developer gives me a syntax error at the word create.
Can someone help?
Your code fails because you are mixing a declare section that must be followed by a begin section, with a "create or replace function" that is a standalone statement to create objects;
If you want to declare a PL/SQL table type and make it public,
you must put it in a package specification, so it can be visible by any function (I also declare here the function F_getTrayIdByDiameter, to make it visible):
CREATE OR REPLACE package utils is
type t_trayIds is table of number(38,0) index by binary_integer;
function F_getTrayIdByDiameter(v_diameterInCm tray.diameterincm%TYPE) return t_trayIds;
end utils;
/
besides, you can't use SELECT INTO syntax, because
select col into var
can be used only for single row, not for lists;
in PL/SQL, if you want to manage multiple rows, you have to use a cursor;
so, if you want to create your PL/SQL table, you can fetch your cursor and build your list (PL/SQL table);
so, your package body can be,
CREATE OR REPLACE package body utils is
function F_getTrayIdByDiameter(v_diameterInCm tray.diameterincm%TYPE) return t_trayIds is
v_trayIdsTable t_trayIds;
i number := 0;
cursor c is
select t.trayid from tray t
where t.diameterincm = v_diameterincm;
begin
for my_rec in c loop
v_trayIdsTable(i) := my_rec.trayid;
i := i + 1;
end loop;
return v_trayIdsTable;
end;
end utils;
/
Then, you can use your list in another function, or in an anonymous block, just for example:
declare
my_result utils.t_trayIds;
begin
my_result := utils.F_GETTRAYIDBYDIAMETER(20);
dbms_output.put_line(my_result(0));
end;
By starting with declare you are creating an anonymous block, which do not have return values. They just do stuff and quit. It sounds like you want to create a function instead.
First, to return a collection of trayids, you need to create a type to return. This has to be done at the schema level; it is an object in its own right. There are three kinds of collections in Oracle: nested tables, associative arrays ("index by" tables), and varrays. I pretty much never use varrays and I don't think you can use associative arrays like this (but I forget, this may have changed in recent versions of Oracle). So create your type:
create or replace type t_trayids as table of number;
Now create your function. The key here is you must use bulk collect to populate the array. This is vastly faster than creating a result set and looping over it.
create or replace function F_getTrayIdByDiameter(v_diameterInCm tray.diameterincm%TYPE)
return t_trayIds
as
v_trayIdsTable t_trayIds;
begin
select t.trayid bulk collect into v_trayIdsTable from tray t
where t.diameterincm = v_diameterincm;
return v_trayIdsTable;
end;
As Nicola points out, you can also create a package and declare the type inside the package specification. You can use associative arrays this way. Which approach depends on what you're trying to do.

PL/SQL Statement Ignored. ORA-22905: cannot access rows from a non-nested table item

I tried to select values from the nested table and bulk collecting into an associative array collection. When I try to bulk collect oracle throwing the above exception(PL/SQL: SQL Statement ignored
PL/SQL: ORA-22905: cannot access rows from a non-nested table
item) though I fetch the data from the nested table.
It is not happening in all the cases. When the same package compiled in the different client database, Some case it is not throwing an error and in some environment, it is throwing an error. Can you please help what was the exact issue.
I have not attached the entire package. Instead provided the case where the issue occurs.
DECLARE
TYPE rc_rec_multiset IS record (
rc_id NUMBER,
doc_num VARCHAR2(100)
);
TYPE rc_type IS TABLE OF rc_rec_multiset;
TYPE tab_rec_type_multiset IS
TABLE OF rc_rec_multiset INDEX BY pls_integer;
rc_tab_type rc_type := rc_type() ;
rc_tab_type_dist_rc tab_rec_type_multiset;
BEGIN
SELECT DISTINCT rc_id,
doc_num BULK COLLECT
INTO rc_tab_type_dist_rc
FROM TABLE(rc_tab_type);
END;
You cannot do this using SQL; an associative array is a PL/SQL data type and cannot be used in the SQL scope. In a similar vein, collections defined in the PL/SQL scope cannot be used in SQL scope (in 11g and earlier) - you either need to define collections in the SQL scope (Note - you cannot do this for associative arrays as they are purely PL/SQL) or just use PL/SQL.
Assuming the rc_tab_type collection is not sparse then you can use pure PL/SQL like this:
DECLARE
TYPE rc_rec_multiset IS record (
rc_id NUMBER,
doc_num VARCHAR2(100)
);
TYPE rc_type IS TABLE OF rc_rec_multiset;
TYPE tab_rec_type_multiset IS
TABLE OF rc_rec_multiset INDEX BY pls_integer;
rc_tab_type rc_type := rc_type();
rc_tab_type_dist_rc tab_rec_type_multiset;
BEGIN
/*
* Populate rc_tab_type here.
*/
FOR i IN 1 .. rc_tab_type.COUNT LOOP
rc_tab_type_dist_rc( rc_tab_type(i).rc_id ) := rc_tab_type(i).doc_num;
END LOOP;
END;
If it is sparse then, instead of the FOR loop, you will have to use:
i := rc_tab_type.FIRST;
WHILE i IS NOT NULL LOOP
rc_tab_type_dist_rc( rc_tab_type(i).rc_id ) := rc_tab_type(i).doc_num;
i := rc_tab_type.NEXT(i);
END LOOP;

How does a %ROWTYPE return type wok for a cursor?

I have found the following pl/sql block in oracle official doc
DECLARE
CURSOR c1 RETURN departments%ROWTYPE IS
SELECT * FROM departments;
BEGIN
NULL;
END;
I don't understand why do they use %ROWTYPE as the return type. Since a cursor is used to hold bulk amount of data(in this scenario) and %ROWTYPE is used to fecth single record or row how does a cursor return a %rowtype. Please provide a complete pl/sql block as above but with some statement that returning the above cursor.
Rowtype used to represent the typeof the row, eg. structure of the returned row.
As Justin is correct, cursors do not contain data. They are pointers to SQL statements.
From the docs:The %ROWTYPE attribute provides a record type that represents a row in a database table. The record can store an entire row of data selected from the table or fetched from a cursor or cursor variable. Variables declared using %ROWTYPE are treated like those declared using a datatype name. You can use the %ROWTYPE attribute in variable declarations as a datatype specifier.

Postgresql trigger function with parameters

I want to create a trigger on a table called takes in postgresql to update a value in another table called student
I'm trying to do it in the following way. But I'm getting an error that there is syntax error near "OLD". I don't understand whats wrong with this. This is my code:
CREATE OR REPLACE FUNCTION upd8_cred_func
(id1 VARCHAR, gr1 VARCHAR,id2 VARCHAR, gr2 VARCHAR)
RETURNS void AS $$
BEGIN
IF (id1=id2 and gr1 is null and gr2 is not null) THEN
update student set tot_cred = tot_cred + 6 where id = id1;
END IF;
RETURN;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER upd8_cred
AFTER UPDATE ON takes
FOR EACH ROW
EXECUTE PROCEDURE upd8_cred_func(OLD.id,OLD.grade,NEW.id,NEW.grade);
You do not need to pass the NEW and OLD as parameters to the trigger function. They are automagically available there:
http://www.postgresql.org/docs/9.1/interactive/trigger-definition.html :
The trigger function must be declared as a function taking no arguments and returning type trigger. (The trigger function receives its input through a specially-passed TriggerData structure, not in the form of ordinary function arguments.)
About the records passed to the trigger procedure, please see http://www.postgresql.org/docs/9.1/interactive/plpgsql-trigger.html :
When a PL/pgSQL function is called as a trigger, several special variables are created automatically in the top-level block. They are: [...] NEW, [...] OLD [...]
As SeldomNeedy pointed in the comment below, you can still pass and use parameters to the trigger function. You declare the function as taking no parameters, but when defining the trigger (by CREATE TRIGGER), you may add some.
They will be available for the trigger as TG_NARG (the number of such parameters), and TG_ARGV[] (an array of text values).
As Greg stated, trigger functions can take arguments, but the functions themselves cannot have declared parameters. Here's a simple example in plpgsql:
CREATE TABLE my_table ( ID SERIAL PRIMARY KEY ); -- onelined for compactness
CREATE OR REPLACE FUNCTION raise_a_notice() RETURNS TRIGGER AS
$$
DECLARE
arg TEXT;
BEGIN
FOREACH arg IN ARRAY TG_ARGV LOOP
RAISE NOTICE 'Why would you pass in ''%''?',arg;
END LOOP;
RETURN NEW; -- in plpgsql you must return OLD, NEW, or another record of table's type
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER no_inserts_without_notices BEFORE INSERT ON my_table
FOR EACH ROW EXECUTE PROCEDURE raise_a_notice('spoiled fish','stunned parrots');
INSERT INTO my_table DEFAULT VALUES;
-- the above kicks out the following:
--
-- NOTICE: Why would you pass in 'spoiled fish'?
-- NOTICE: Why would you pass in 'stunned parrots'?
--
There are a few other goodies such as TG_NARGS (to know how many args you got without looping through them) discussed in the docs. There's also information there about how to get the name of the triggering table in case you have mostly-but-not-quite-shared logic for one trigger-function that spans a number of tables.
The trigger function can have parameters, but, you can't have those parameters passed like a normal function (e.g. arguments in the function definition). You can get the same result... In python you get access to the OLD and NEW data as the answer above describes. For example, I can use TD['new']['column_name'] in python to reference the new data for column_name. You also have access to the special variable TD['args']. So, if you like:
create function te() returns trigger language plpython2u as $function$
plpy.log("argument passed 1:%s 2:%s" %(TD['args'][0], TD['args'][1], ))
$function$
create constraint trigger ta after update of ttable
for each for execute procedure te('myarg1','myarg2');
Granted, these arguments are static, but, they are useful when calling a common trigger function from multiple trigger declarations. I am pretty sure that the same variables are available for other stored procedure languages. (sorry if the code doesn't work verbatim, but, I do practice this technique, so I know you can pass arguments!).