Skip to Main Content
  • Questions
  • How to do bulk collect in Pro*C variables

Breadcrumb

Question and Answer

Tom Kyte

Thanks for the question, Kalpesh.

Asked: March 15, 2001 - 11:12 am UTC

Last updated: February 18, 2009 - 5:46 pm UTC

Version: 8.1.5

Viewed 10K+ times! This question is

You Asked

Hi Tom,
I am writing one Pro*C code.
In that code I want to get values from a table and want to write them into one file.

How can I do bulk collect using Pro*c variables so that in one single statement I will get all the values from a table into Pro*C variables and I can write them in a file.


My select statement is
SELECT /*+PARALLEL(tbtick,6)*/
NVL(symbol_id,0),
NVL(trade_price,0),
TO_CHAR(trade_date ,'YYYY/MM/DD'),
TO_CHAR(ROUND(trade_date ,'MI'),'HH24:MI:SS')
BULK COLLECT
INTO
p_arr_symbol_id,
p_arr_trade_price,
p_arr_trade_date,
p_arr_trade_time
FROM tkdaily.tbtick
where symbol_id in ( select symbol_id from tbsymbols where symbol_type_id=1);

I want to use my select statement only once , I don't want to make cursor or anything.

I want p_arr_symbol_id,
p_arr_trade_price,
p_arr_trade_date,
p_arr_trade_time into Pro*C .

How can I get them?

Thanks
Kalpesh



and Tom said...

In Oracle8i, release 8.1 and up, the easiest method would be to set the PREFETCH option on the precompiler command line or to place that option in your C code.

Oracle will then array fetch for you magically. You ask for a row and it'll prefetch however many you want.

Otherwise, you can just declare your host variables as arrays and array fetch them manually:

exec sql begin declare section;
/* array fetch upto 100 rows at a time */
varchar value[100][25];
exec sql end declare section;


EXEC SQL OPEN C;
for( rows_fetched = 0, done = 0;
!done;
rows_fetched=sqlca.sqlerrd[2] )
{
EXEC SQL FETCH C INTO :value;
done=(sqlca.sqlcode==1403);

for( i = 0; i < sqlca.sqlerrd[2] - rows_fetched; i++ )
{
printf( "row %d is '%.*s'\n",
rows_fetched+i+1,
value[i].len, value[i].arr );
}
}
EXEC SQL CLOSE C;


Rating

  (11 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

Bulk collect into Pro*C variables

Kalpesh Patel, March 16, 2001 - 1:36 pm UTC

This is really useful when someone doesn't want to use PL/SQL variables for bulk collect.
With this , we can do bulk collect into Pro*C variables and we can use them into our Pro*C code for some calculation or manipulation.
Thanks Tom, for such a useful information.

How about cursor with GROUP BY clause?

Julien Xu, February 05, 2004 - 3:09 am UTC

My cursor in Pro*C is:

SELECT BODY, ACCT, PRPRTY, SUM(NB_MEAS)
FROM TREAD
WHERE BATCH_NB = 75
GROUP BY BODY, ACCT, PRPRTY;

I define host arrays to receive the results. But after FETCHing, I find there is only one line in the arrays, all other cells are empty (0). Actually there should have >300 results.
If I remove that GROUP BY and that SUM(), the results are all FETCHed as expected. But I must need that SUM() and GROUP BY.
So why and how to fix this?

Tom Kyte
February 05, 2004 - 7:39 am UTC

if you run that query in sqlplus -- what are the results (how many rows) and show us the entire piece of PRO*C code, include things like "my version", "my platform"

Otherwise this question fits into the "my car won't start -- why?" category ;)

prefetch and host array

Pinguman, September 09, 2004 - 7:16 am UTC

Hi

Currently we are using Oracle 8.1.7.4 on Sun Solaris. We have some PRO*C codes which we would like to convert and use HOST ARRAYS. From your answer it seems that we dont need to modify the code and simply use PREFETCH option and the PRO*C will start using HOST ARRAYS. Am I correct?

For example if I have this

long int v_transation

cursor blah blah

EXEC SQL FETCH cur_cursor
into v_transation


This processes row by row, if I use PREFETCH 100 will this use HOST ARRAYS?

Tom Kyte
September 09, 2004 - 8:33 am UTC

correct -- it will do it "automagically"

this subroutine:


void process()
{
exec sql begin declare section;
int x;
exec sql end declare section;

EXEC SQL WHENEVER SQLERROR DO sqlerror_hard();
exec sql declare c cursor for select rownum from all_objects where rownum <= 1000;
exec sql open c; for( ;; )
{
exec sql whenever notfound do break; exec sql fetch c into :x;
}
exec sql whenever notfound do continue;
exec sql close c;
}


when executed without setting prefetch:


select rownum from
all_objects where rownum<=1000

call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.03 0.03 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 501 0.23 0.33 8 4560 4 1000
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 503 0.26 0.36 8 4560 4 1000


shows the default is 2 -- with prefetch=100

select rownum from
all_objects where rownum<=1000

call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 11 0.17 0.17 0 4075 4 1000
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 13 0.17 0.17 0 4075 4 1000

showing the reduced fetch calls...


are we talking about host arrays?

A reader, September 10, 2004 - 5:40 am UTC

Hi

If I understand correctly host arrays is not about fetching data is it? (from doc, With arrays, you manipulate an entire array with a single SQL statement)

For example, if I have to run 10 insert statements using host arrays I probably need one only.

Or host arrays is about both, fetching and number of stateents excuted?

Tom Kyte
September 10, 2004 - 9:17 am UTC

you can array fetch -- so to retrieve 1000 rows you could either

o fetch a row at a time, 1000 fetches OR
o fetch N rows at a time, 1000/N fetches (eg: n = 100, you'd fetch 10 times instead of 1000 times)


you can array insert/update/delete as well.

say you have 1000 rows to insert.
you batch them up into an array of 100 elements at a time.

You'll EXECUTE your insert 10 times, inserting 1000 rows.


so, array processing is a method to "batch things up"

drawbacks of prefetching?

Andrey, September 21, 2004 - 4:14 pm UTC

Tom,

What are the drawbacks of setting the array size too high?
Just preallocating too much memory on a client machine?
Any additional network traffic because of it?

Is your method any different performance-wise, with the following line?

EXEC SQL FOR :batch_limit FETCH db_batch_cursor_1 USING DESCRIPTOR select_des;

Thanks!

Tom Kyte
September 21, 2004 - 4:24 pm UTC

memory, choke points (hey, lets flood the network with 500megabytes right now), latency (why does it take 30 seconds to get that first row, I only care about 10 of them initially), etc.


Yes, you'll find diminishing marginal returns for large array sizes -- I have an analogy in Effective Oracle by Design with regards to a wheelbarrow, a big pile of bricks and two people wanting to pave a driveway (regards of Connor McDonald whom I believe developed the analogy in the first place as I recall...)

bringing over a brick at a time (row by row) is slow. lots of wait times, not processing anything.

bringing over the entire stack of bricks is fraught with danger (tipping over), and because the thing weighs so much, takes a long time. So, for a long time, wheelbarrow person works, paver sits. then paver works and wheelbarrow guy does nothing.

brining over a nice pile to get started and going back and forth and back and forth keeps things flowing nicely. no one gets stressed out, no one gets hurt.

But the parses...

Shawn, January 13, 2005 - 8:53 am UTC

I've been reviewing a database for sql effeciency. One of the items that I found was I had a large amount of sql (greater than 50%) parsing everytime it executes. At first I was alarmed at what was causing this, but now I'm not sure if I should be concerned or if it can be eliminated. We have a number of batch processes that call subroutines to perform specific processing. This is all written in Pro*C with this basic pattern...

void process(int v_someid)
{
exec sql begin declare section;
int x;
exec sql end declare section;


EXEC SQL WHENEVER SQLERROR DO sqlerror_hard();
exec sql declare c cursor for select some_columns from some_table where id = :id;
exec sql open c; for( ;; )
{
exec sql whenever notfound do break;
exec sql fetch c into :x;
}
exec sql whenever notfound do continue;
exec sql close c;
}

They are opening a cursor, processing all rows for a specific id fetching 20 at a time, and then closing the cursor. However, the batch process calls this routine once for every id and we process millions of id's a day. It appears that every time the subroutine is called, the statement is parsed. Should I be concerned about this from a performance stand point? Is there a different/better way to accomplish this task? Or, do I just have to live with the fact that it parses everytime and go on with life because it's not that big of a deal?

This is 7 terabyte instance of ORACLE 9.2 on HP-UX server (32 processor with 96GB of RAM)

Tom Kyte
January 13, 2005 - 9:14 am UTC

hold_cursor=yes release_cursor=no prefetch=20


with hold cursor no, release cursor yes, prefetch defaulted:


select user_id
from
t where user_id>:b0


call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 10000 0.19 0.17 0 0 0 0
Execute 10000 0.53 0.35 0 0 0 0
Fetch 110000 2.50 2.41 1 120000 0 190000
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 130000 3.22 2.94 1 120000 0 190000



with hold_cursor=yes, release cursor = no, prefect=20

select user_id
from
t where user_id>:b0


call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 10000 0.39 0.39 0 0 0 0
Fetch 20000 1.18 1.00 1 40000 0 190000
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 30001 1.57 1.40 1 40000 0 190000



Ask the coders to take a peek at the pro*c precompile options and see if any of them might be "interesting"


You have a big honking machine there, you probably try to be very scalable. Parsing is death to scaling.


The more you parse, the less you parse.....

(sounds counterintuitive but it is true -- the more you try to parse, the less you can parse, the lines get longer and longer and longer and longer....)

Thanks.

shawn, January 13, 2005 - 9:22 am UTC

"You have a big honking machine there, you probably try to be very scalable.
Parsing is death to scaling." That was my thought exactly, just didn't know how to get around it. I'll get back with the developers and see what we can get accomplished.



Giridhar, June 01, 2005 - 11:06 pm UTC

Hi Tom,
Would you please explain more on how the PREFETCH happens.

When we fetch data in a loop (without PREFETCH), we fetch one row for every iteration in the loop. If we use PREFETCH, oracle prefetches more than 1 record for us depending on the PREFETCH value.
As we use the LOOP, will oracle PREFETCH data once and ignore fetching till it iteration counter is equal to PREFETCH value.

Thanks in advance Tom.
Regards,
Giridhar

Tom Kyte
June 02, 2005 - 4:25 pm UTC

you explained it very well? what more need be said?

Thanks

Giridhar, June 05, 2005 - 12:47 am UTC

Thanks tom for your reply. I just wanted to confirm if my understanding of PREFETCH was correct.
Regards,
Giridhar

A reader, February 18, 2009 - 2:23 pm UTC

We have a job scheduler written in Pro*C, which runs in the background in the UNIX machine.
It's job is to pick up requests from a job_queue table and process the requests sequentially.
The requests can be to send to email, generate oracle report, run plsql code, run shell scripts, convert text files to pdf etc.
We need to make this job multithreaded to improve the performance.

What is the best approach to do this?

I was looking into the below approaches.

1) Multhreaded Applications in Pro*C
2) Host Arrays


Please let me know if there are any other approaches available?

Tom Kyte
February 18, 2009 - 4:06 pm UTC

why not erase the code and use dbms_scheduler.

it can run external things
has a history/audit trail built in
is already very multi-threaded

A reader, February 18, 2009 - 4:40 pm UTC

Thanks Tom, but we are in Oracle 9i !


Tom Kyte
February 18, 2009 - 5:46 pm UTC

use dbms_job and a java stored procedure to run host commands.

dbms_job can do upto 1,000 things at a time.

You'd don't get the features of the scheduler (better intervals/schedules and audit), but you can more easily do that (build your own audit) than attempt to make a multi-threaded pro*c app.



That would be my first choice in 9i - dbms_job.


I would not go multi-threaded in the C program, if I was to stick with the C code (and I would not), I would have it use AQ (advanced queues) and instead of you sticking just into a do it yourself queue (you'd have to figure out how to handle concurrency on that one - AQ does that for you) have the requestor put it into a real queue.

And then just start N of your C programs, each will attempt to dequeue a message - if you start 5 and there are 10 messages, each will get one of the first five, process it and then get the rest...



That would be 2nd choice.




A cheap way to do it would be to simply grab a row, fork/exec the program to deal with it.