oracle plsql chunking the rows - oracle

I have a select statement which returns 0 or more rows.
I'm trying to come up with a plsql proc with a cursor to produce xml output fro, all rows returned into 100 rows at a time. i'm doing this to chunk loo rows at a time based on requirement.
So basically my program should follow below logic
cursor c1 is select id,code_id,code_desc from table order by id; --returns some rows
if c1%notfound
then return;` -- exit from procedure
else
loop
grab first 100 rows from select and append to a variable
and assign it to a variable;
update this variable into a clob field in a table.
grab next 100 rows and append into a variable
update this variable into a clob field in a table in another row;see below
table data
and so on
and grab remaining rows and append into a variable
print the variable;
until no data found;
exit
I'm trying to do convert the output from select statement into xml text.
The output should look something like below:
TABLE: STG_XML_DATA
LOOP_NO(NUMBER), XML_TEXT(CLOB), ROWS_PROCESSED
1 <XML><id>1</ID><id>2</ID>..<ID>100</ID></XML> 100
2 <XML><id>101</ID><id>102</ID>..<ID>200</ID></XML> 200
3 <XML><id>301</ID><id>102</ID>..<ID>320</ID></XML> 20
Can someone please help

First of all, can you do this with a single INSERT ... SELECT statement that does what you want with reasonable performance? If you're doing a million rows, yes, breaking them up into chunks may be a good idea. But if it's 100, that might be your best bet.
For your actual question, you want to use BULK COLLECT into a collection variable and possibly FORALL. So your function is going to look something like this:
DECLARE
TYPE id_tt IS TABLE OF NUMBER;
TYPE desc_tt IS TABLE OF VARCHAR2(100);
l_ids id_tt;
l_code_ids id_tt;
l_code_descs desc_tt;
cursor c1 is select id,code_id,code_desc from table
BEGIN
OPEN c1;
LOOP
FETCH c1 BULK COLLECT INTO l_ids, l_code_ids, l_code_descs
LIMIT 100;
EXIT WHEN l_ids.COUNT = 0;
FORALL idx IN 1..l_ids.COUNT
INSERT [... some insert statement here ...]
[... maybe some other processing here...]
END LOOP;
CLOSE c1;
END;
What you absolutely do not want to do is fetch a row, process it, fetch another row, etc. SQL is a set-oriented language, so try to operate on sets. Every time you switch context from SQL to PL/SQL there is a cost and it can kill your performance.
See: http://www.oracle.com/technetwork/issue-archive/2012/12-sep/o52plsql-1709862.html

Related

How to duplicate rows with a column change for a list of tables?

Given the following example:
BEGIN
FOR r IN (
SELECT * FROM table_one WHERE change_id = 0
) LOOP
r.change_id := -1;
INSERT INTO table_one VALUES r;
END LOOP;
END;
This inserts new rows to table_one with the exact same content, except the intended change on column change_id to the value -1. I don't have to specify the columns inside of the script as I have to in an INSERT INTO table_one (change_id, ...) SELECT -1, ... FROM table_one WHERE change_id=0;
It works perfectly fine. But how to modify this script to work with a list of tables? The internal structure of those tables are different, but all of them have the necessary column change_id.
Of course the easiest solution would be to copy and paste this snippet x-times and replace the fix table name inside. But is there an option to work with a list of tables in an array?
My approach was like this:
DECLARE
TYPE tablenamearray IS VARRAY(30) OF VARCHAR2(30);
tablenames tablenamearray;
BEGIN
tablenames := tablenamearray('TABLE_ONE', 'TABLE_TWO', 'TABLE_THREE'); -- up to table 30...
FOR i IN tablenames.first..tablenames.last LOOP
/* Found no option to use tablenames(i) here with dynamic SQL */
END LOOP;
END;
Note: There is no technical primary key like an id with a sequence behind. The primary key is build by three columns incl. the change_id column.
You cannot create a SQL statement where the statement is not known at parse time. So, you cannot have a variable as a table name. What you're looking for is Dynamic SQL, which is a fairly complicated topic, but basically you're going to wind up building a SQL statement with DBMS_SQL or running a statement as a string with EXECUTE IMMEDIATE.

Postgres 9.2 select star into an array

I want to do something like this inside a stored procedure so I can see the result of an insert statement for debugging:
thing := array_to_string(ARRAY(select * from some_table limit 1 ));
raise info 'insert result: %',thing;
Where all the columns of some_table get concatenated into an array
Is there a way to do this?
Arrays are of uniform type. You can't have an array where different entries have different data types.
What you appear to want is an anonymous row (record).
DECLARE
debug_row record;
BEGIN
SELECT * FROM some_table LIMIT 1 INTO debug_row;
RAISE INFO 'insert result: %',debug_row;
Note that this only works for a single row result. For multiple rows you can call the query as the input for a loop and iterate over the result. There are examples in the PL/PgSQL documentation.

trying to copy data from a prepopulated collection to an associated array in a function

I am getting below error while trying to copy data from one collection to other.
Error(17,8): PL/SQL: ORA-00904: "COLUMN_VALUE": invalid identifier
Please help me providing a better way.
create or replace type type_record as object(employee_id NUMBER(6),
first_name VARCHAR2(20));
create or replace type type_tbl as table of type_record;
create or replace function scrub_final_2 return sys_refcursor IS
x type_tbl;
test1 type_tbl;
y sys_refcursor;
z sys_refcursor;
begin
x:=type_tbl();
z:=scrub_final_1; /*This is a function which returns a refcursor*/
loop
fetch z bulk collect into test1;
exit when z%NOTFOUND;
select column_value bulk collect into x from table(test1);
end loop;
open y for select employee_id,first_name from employees a where not exists
(select employee_id from table(x) where a.employee_id=employee_id);
return y;
end;
First, using x, y, z, and test1 as variable names makes it relatively hard to understand your code since it is not obvious at any point which variables represent a cursor and which represent a collection. Calling an object type_record is also confusing since it is not, in fact, a record which is a PL/SQL structure very similar to a SQL object. Additionally, your title is rather confusing since neither of your collections are actually associative arrays.
Second, your loop as currently constructed doesn't make any sense. If you're going to have a loop, you'd want to do a bulk collect with a limit. If you're not going to use a limit in your bulk collect, there is no point in looping since you'll only ever have one iteration of the loop.
Third, there appears to be no reason to copy the data from one collection to another. You can use the data in test1 to open y rather than using x in the query. Using a second collection just means that you're wasting valuable space in the PGA.
Fourth, if you really do want to copy the data from one collection to another, you can do a simple assignment
x := test1;
Fifth, if you're going to write a select against a collection defined on an object type, the columns in the result will be the names of the object type's attributes. column_value is only a column name for collections of built-in types. If you really, really wanted to so the assignment the hard way with a select statement, you'd do something like
SELECT type_record( employee_id, first_name )
BULK COLLECT INTO x
FROM TABLE( test1 );

Can a table variable be used in a select statement where clause?

I have a stored procedure that is doing a two-step query. The first step is to gather a list of VARCHAR2 type characters from a table and collect them into a table variable, defined like this:
TYPE t_cids IS TABLE OF VARCHAR2(50) INDEX BY PLS_INTEGER;
v_cids t_cids;
So basically I have:
SELECT item BULK COLLECT INTO v_cids FROM table_one;
This works fine up until the next bit.
Now I want to use that collection in the where clause of another query within the same procedure, like so:
SELECT * FROM table_two WHERE cid IN v_cids;
Is there a way to do this? I am able to select an individual element, but I would like to use the table variable like a would use a regular table. I've tried variations using nested selects, but that doesn't seem to work either.
Thanks a lot,
Zach
You have several choices as to how you achieve this.
If you want to use a collection, then you can use the TABLE function to select from it but the type of collection you use becomes important.
for a brief example, this creates a database type that is a table of numbers:
CREATE TYPE number_tab AS TABLE OF NUMBER
/
Type created.
The next block then populates the collection and performs a rudimentary select from it using it as a table and joining it to the EMP table (with some output so you can see what's happening):
DECLARE
-- Create a variable and initialise it
v_num_tab number_tab := number_tab();
--
-- This is a collection for showing the output
TYPE v_emp_tabtype IS TABLE OF emp%ROWTYPE
INDEX BY PLS_INTEGER;
v_emp_tab v_emp_tabtype;
BEGIN
-- Populate the number_tab collection
v_num_tab.extend(2);
v_num_tab(1) := 7788;
v_num_tab(2) := 7902;
--
-- Show output to prove it is populated
FOR i IN 1 .. v_num_tab.COUNT
LOOP
dbms_output.put_line(v_num_tab(i));
END LOOP;
--
-- Perform a select using the collection as a table
SELECT e.*
BULK COLLECT INTO v_emp_tab
FROM emp e
INNER JOIN TABLE(v_num_tab) nt
ON (e.empno = nt.column_value);
--
-- Display the select output
FOR i IN 1 .. v_emp_tab.COUNT
LOOP
dbms_output.put_line(v_emp_tab(i).empno||' is a '||v_emp_tab(i).job);
END LOOP;
END;
You can see from this that the database TYPE collection (number_tab) was treated as a table and could be used as such.
Another option would be to simply join your two tables you are selecting from in your example:
SELECT tt.*
FROM table_two tt
INNER JOIN table_one to
ON (to.item = tt.cid);
There are other ways of doing this but the first might suit your needs best.
Hope this helps.
--Doesn't work.
--SELECT item BULK COLLECT AS 'mySelectedItems' INTO v_cids FROM table_one;
SELECT table_two.*
FROM table_two INNER JOIN v_cids
ON table_two.paramname = v_cids.mySelectedItems;
Unless I'm misunderstanding the question, this should only return results that are in the table variable.
Note: I've never used Oracle, but I imagine this case would be the same.

plsql cursor via straight sql

Im looking at the following two ways of retrieving a value and storing it later via an insert statement. i.e. via Pl/SQL cursors or via direct SQL.
Is there any advantage to either approach? Or is there a more efficient approach?
Approach 1
Cursor system_date
Is
select sysdate from dual;
system_date_rec system_date%type;
Open system_Date;
Fetch system_date into system_date_rec;
Insert into table(dateValue)
values(system_date_rec.date);
Approach 2
dateString varchar(20);
Select sysdate into dateString from dual;
Insert into table(dateValue)
values(dateString);
How about approach 3:
Insert into table(dateValue)
values(sysdate);
or assuming you did actually need to do a select to get the data:
Insert into table(dateValue)
select dateValue from other_table where ...;
Regarding whether an explicit cursor or a SELECT INTO is preferable when one or the other is needed, I would go for the SELECT INTO because it is neater and safer if you expect the query to return exactly one row:
select some_value
into l_var
from other_table
where ...;
if l_var = 'A' then
do_something;
end if;
Now you will get an exception (NO_DATA_FOUND or TOO_MANY_ROWS) if the number of rows returned is not as expected. With the cursor you will just end up with l_var unchanged, or set to the value from the first matching row - which probably means yu've got a bug but don't know it.
Each approach has it's merit but if it's one and only one value you are getting then I'd go with select ... into ... as this is much simpler and will check that you have one and only one value.
Although Tony's approach is possibly preferable to both in the right circumstances.
If you also want to get the value back there is always the RETURNING clause of the insert statement.
my_date_value date;
...
INSERT into table(datevalue)
values (sysdate)
returning sysdate into my_date_value;
I'd agree with #Tony and #MikeyByCrikey that select ... into is generally preferable, not least - in my personal, subjective opinion - because it keeps the select and into together instead of having the select out of sight up in the declare section. Not really an issue if it's simple but you've suggested you're doing several big queries and manipulations, which implies a longish procedure.
Slightly off-topic, but if all the manipulations are to gather data for a single insert at the end, then rather than having lots of separate variables I'd consider declaring a single variable as a row type and updating the columns as appropriate:
declare
l_row my_table%ROWTYPE;
begin
select ... into l_row.column1;
select ... into l_row.column2;
if l_row.column2 = 'A' then
/* do something */
end if;
l_row.column3 := 'somevalue';
fetch ... into l_row.column4;
/* etc */
insert into my_table values l_row;
end;

Resources