Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Programming PL SQL.doc
Скачиваний:
3
Добавлен:
01.07.2025
Размер:
5.06 Mб
Скачать

16.8.2.2 Creating a pipelined function

A pipelined function is one that returns a result set as a collection, but does so iteratively. In other words, Oracle no longer waits for the function to run to completion, storing all the rows it computes in the PL/SQL collection, before it delivers the first rows. Instead, as each row is ready to be assigned into the collection, it is "piped out" of the function.

Here is a rewrite of the pet_family function as a pipelined function:

1 CREATE FUNCTION pet_family (dad_in IN pet_t, mom_in IN pet_t)

2 RETURN pet_nt PIPELINED

3 IS

4 l_count PLS_INTEGER;

5 retval pet_nt := pet_nt ( );

6

7 BEGIN

8 PIPE ROW (dad_in);

9 PIPE ROW (mom_in);

10

11 IF mom_in.breed = 'RABBIT' THEN l_count := 12;

12 ELSIF mom_in.breed = 'DOG' THEN l_count := 4;

13 ELSIF mom_in.breed = 'KANGAROO' THEN l_count := 1;

14 END IF;

15

16 FOR indx IN 1 .. l_count

17 LOOP

18 PIPE ROW (pet_t ('BABY' || indx, mom_in.breed, SYSDATE));

19 END LOOP;

20

21 RETURN;

22 END;

The following table notes several changes to our original functionality:

Line(s)

Description

2

The PIPELINED keyword is necessary to tell Oracle that rows are to be returned iteratively.

8, 9, 18

Rather than assign rows of data to the collection, you simply prepare the row of information (it must have the same structure as a row in the collection type specified in the RETURN clause of the function) and then use PIPE ROW to pipe it back out of the function.

21

An unqualified RETURN (formerly allowed only in procedures) is used in pipelined functions. Nothing is actually returned at this point, except control to the calling block.

I can invoke a pipelined function within a SQL statement (as you already saw) or I can use a simpler syntax:

SELECT *

FROM TABLE (pet_family (

pet_t ('Bob', 'KANGAROO', SYSDATE),

pet_t ('Sally', 'KANGAROO', SYSDATE)));

In other words, I no longer need to employ the CAST AS syntax to explicitly cast into a recognized database collection type.

So what is the advantage of a pipelined function? If your application can take advantage of or needs to start working with rows of data before the entire set has been identified, then a pipelined function can make an enormous difference. At the end of the next section, we'll present a script that performs a comparison of performance.

16.8.2.3 Building a transformative function

A transformative function is a pipelined function that accepts as a parameter a result set (via a CURSOR expression) and returns a result set. This functionality is also new to Oracle9i and can have a very positive effect on application performance.

You can define a table function with an IN argument of type REF CURSOR and call it with a CURSOR expression as the actual parameter—all inside a SQL statement. This technique allows you to "pipe" data from one function to the next, or from one SQL operation to the next, without needing to rely on any intermediate storage of data. Here is an example of how you can use this functionality:

All the code shown here may be found in the tabfunc.sql script on the O'Reilly site.

Consider the following scenario. I have a table of stock ticker information that contains a single row for the openand close prices of stock:

CREATE TABLE StockTable (

ticker VARCHAR2(10),

open_price NUMBER,

close_price NUMBER);

I need to transform (or pivot) that information into another table:

CREATE TABLE TickerTable (

ticker VARCHAR2(10),

PriceType VARCHAR2(1),

price NUMBER);

In other words, a single row in StockTable becomes two rows in TickerTable.

There are many ways to achieve this goal. For example, when using traditional methods in pre-Oracle9i versions of the database, I could write code like this:

FOR rec IN (SELECT * FROM stocktable)

LOOP

INSERT INTO tickertable

(ticker, pricetype, price)

VALUES (rec.ticker, 'O', rec.open_price);

INSERT INTO tickertable

(ticker, pricetype, price)

VALUES (rec.ticker, 'C', rec.close_price);

END LOOP;

It works, but for very large volumes of data, perhaps it is not as efficient as it could be. Let's see if I can use a transformative function to do the job more quickly.

I create a collection type to use in my function:

CREATE TYPE TickerType AS OBJECT (

ticker VARCHAR2(10),

PriceType VARCHAR2(1),

price NUMBER);

CREATE TYPE TickerTypeSet AS TABLE OF TickerType;

I then create a package defining a REF CURSOR type based on this collection type. I do this in a package specification so that it can be referenced by my function:

CREATE OR REPLACE PACKAGE refcur_pkg

IS

TYPE refcur_t IS REF CURSOR

RETURN StockTable%ROWTYPE;

END refcur_pkg;

And here is my stock pivot function:

CREATE OR REPLACE FUNCTION StockPivot (

p refcur_pkg.refcur_t) RETURN TickerTypeSet

PIPELINED

IS

out_rec TickerType := TickerType(NULL,NULL,NULL);

in_rec p%ROWTYPE;

BEGIN

LOOP

FETCH p INTO in_rec;

EXIT WHEN p%NOTFOUND;

-- first row

out_rec.ticker := in_rec.Ticker;

out_rec.PriceType := 'O';

out_rec.price := in_rec.Open_Price;

PIPE ROW(out_rec);

-- second row

out_rec.PriceType := 'C';

out_rec.Price := in_rec.Close_Price;

PIPE ROW(out_rec);

END LOOP;

CLOSE p;

RETURN ;

END;

And here is that same function defined in a nonpipelined way:

CREATE OR REPLACE FUNCTION StockPivot_nopl (

p refcur_pkg.refcur_t)

RETURN TickerTypeSet

IS

out_rec TickerType := TickerType(NULL,NULL,NULL);

in_rec p%ROWTYPE;

retval TickerTypeSet := TickerTypeSet( );

BEGIN

retval.DELETE;

LOOP

FETCH p INTO in_rec;

EXIT WHEN p%NOTFOUND;

out_rec.ticker := in_rec.Ticker;

out_rec.PriceType := 'O';

out_rec.price := in_rec.Open_Price;

retval.EXTEND;

retval(retval.LAST) := out_rec;

out_rec.PriceType := 'C';

out_rec.Price := in_rec.Close_Price;

retval.EXTEND;

retval(retval.LAST) := out_rec;

END LOOP;

CLOSE p;

RETURN retval;

END;

So many different approaches to solving the same problem! How does one choose among them? Well, if you have lots of data to process, you will certainly want to choose the most efficient implementation. You will find in the tabfunc.sql file[1] an anonymous block that compares the performance of the following four approaches:

[1] The tabfunc.sql file relies on the PL/Vision timer mechanism, PLVtmr, to calculate elapsed time. You can install this package by running the :plvtmr.pkg script.

  • A direct insert from SELECT using the pipelined function:

  • INSERT INTO tickertable

  • SELECT *

FROM TABLE (StockPivot (CURSOR(SELECT * FROM StockTable)));

  • A direct insert from SELECT using the nonpipelined function:

  • INSERT INTO tickertable

  • SELECT *

FROM TABLE (StockPivot_nopl (CURSOR(SELECT * FROM StockTable)));

  • A deposit of pivoted data into a local collection. You can then use a simple loop to transfer the collection's contents to the table:

  • OPEN curvar FOR

  • SELECT * FROM stocktable;

  • mystock := stockpivot_nopl (curvar);

  • indx := mystock.FIRST;

  • LOOP

  • EXIT WHEN indx IS NULL;

  • INSERT INTO tickertable

  • (ticker, pricetype, price)

  • VALUES (mystock (indx).ticker, mystock (indx).pricetype,

  • mystock (indx).price);

END LOOP;

  • The "old-fashioned" method. Use a cursor FOR loop to expand each single row from stocktable into the two rows of the tickertable:

  • FOR rec IN (SELECT * FROM stocktable)

  • LOOP

  • INSERT INTO tickertable

  • (ticker, pricetype, price)

  • VALUES (rec.ticker, 'O', rec.open_price);

  • INSERT INTO tickertable

  • (ticker, pricetype, price)

  • VALUES (rec.ticker, 'C', rec.close_price);

END LOOP;

When I execute the block comparing these four aproaches in my SQL*Plus session, I see these results:

All SQL with Pipelining function Elapsed: 2.47 seconds.

All SQL with non-pipelining function Elapsed: 1.78 seconds.

Intermediate collection Elapsed: 6.71 seconds.

Cursor FOR Loop and two inserts Elapsed: 6.9 seconds.

I draw two conclusions from this output:

  • The ability of the table function (whether pipelined or regular) to transform data "in-line" (i.e., within a single SQL statement) noticeably improves performance.

  • Pipelining doesn't help us in this scenario; it actually seems to slow things down a bit. In fact, I am not really taking advantage of pipelining in this code. In all cases, I am waiting until the logic has executed to completion before I do anything with my data (or compute elapsed time).

I would expect (or hope, at least) to see some improvement in elapsed time when executing this logic in parallel or, more generally, when we get the first N number of rows and start processing them before all of the data has been retrieved. The file tabfunc.sql offers a simulation of such a scenario.

I compare the time it takes to execute each of these statements:

-- With pipelining

INSERT INTO tickertable

SELECT *

FROM TABLE (StockPivot (CURSOR(SELECT * FROM StockTable)))

WHERE ROWNUM < 10;

-- Without pipelining

INSERT INTO tickertable

SELECT *

FROM TABLE (StockPivot_nopl (CURSOR(SELECT * FROM StockTable)))

WHERE ROWNUM < 10;

And the contrasting timings are very interesting:

Pipelining first 10 rows Elapsed: .08 seconds.

No pipelining first 10 rows Elapsed: 1.77 seconds.

Clearly, piping rows back does work and does make a difference!

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]