Section 3.5. Table Function Examples


3.5. Table Function Examples

This section contains a number of additional examples that demonstrate some handy applications of using table functions to handle such tasks as performing extra tracing, establishing time limits, and using timed data refreshes. Each example takes advantage of the fact that table functions allow code to be written with a SELECT statement.

3.5.1. Tracing

Most PL/SQL tracing tools (SQL Trace , DBMS_TRACE, etc.) require you to run an operation and then look elsewhere for the trace output. Even Oracle's DBMS_OUTPUT package (the simplest debug tool of all) requires separate output when using a development tool like Toad or PL/SQL Developer .

Table functions allow debug information to be included within query results. When combined with autonomous transactions, they can even provide tracing for DML operations. Consider the following function.

     /* File on web: tracer.sql */     CREATE OR REPLACE FUNCTION tracer                       RETURN debug_t AS       PRAGMA AUTONOMOUS_TRANSACTION;       v_debug debug_t := debug_t(  );     BEGIN       v_trace.EXTEND;       v_trace(v_debug.LAST) := 'Started Insert At ' ||                                 TO_CHAR(SYSDATE,'HH24:MI:SS');       INSERT INTO a_table VALUES(1);       COMMIT;       v_trace.EXTEND;       v_trace(v_debug.LAST) := 'Completed Insert At ' ||                                 TO_CHAR(SYSDATE,'HH24:MI:SS');       RETURN(v_trace);     END;

Without the AUTONOMOUS TRANSACTION clause , I would get the error ORA-14551: cannot perform a DML operation inside a query when executing the query. With this clause in place, I can run the function using a SELECT statement.

     SQL> SELECT *       2    FROM a_table;     no rows selected     SQL> SELECT *       2    FROM TABLE(debug);     COLUMN_VALUE     ----------------------------     Started Insert At 22:04:28     Completed Insert At 22:04:28     SQL> SELECT *       2    FROM a_table;           COL1     ----------              1

3.5.2. Establishing Time Limits

One very useful application of table functions is to use them to establish time limits for returning records from queries. This is great if you want to test an application using a subset of queried records without having to wait for the whole list. The following function pipes records back from a query for the number of seconds passed in. Once the number of seconds is reached, a value of negative 1 is piped out and the function is exited.

     /* File on web: time_limit.sql */     CREATE OR REPLACE FUNCTION get_a_table               ( p_limit NUMBER )               RETURN rowset_t               PIPELINED AS       CURSOR curs_get_a IS       SELECT *         FROM a_table;       v_start DATE;     BEGIN       v_start := SYSDATE;       FOR v_a_rec IN curs_get_a LOOP         PIPE ROW(rowset_o(v_a_rec.col1));         IF SYSDATE - v_start >= ( p_limit * 0.000011574 ) THEN           PIPE ROW(rowset_o(-1));           EXIT;         END IF;       END LOOP;     END;

Here's an example of selecting from a table with 1,000 records in it.

     SQL> SELECT *       2    FROM TABLE(get_a_table(1));           COL1     ----------            661            662            663            664             -1     5 rows selected.

If the query takes more than p_limit seconds to return, the function will exceed its time limit.

3.5.3. Enabling Nested Cursors

Table functions can also be used to help queries perform better by adding application knowledge to a query. The classic example is the multiple "OR EXISTS" type of query shown in this example.

     SELECT *       FROM main_table mt       WHERE col1 = 1         AND ( EXISTS ( SELECT 1                          FROM or_table_one                         WHERE col11 = mt.col1 )            OR EXISTS ( SELECT 1                          FROM or_table_two                         WHERE col21 = mt.col1 )            OR EXISTS ( SELECT 1                          FROM or_table_three                         WHERE col31 = mt.col1 ) );

It will return a record if a corresponding record is found in any of three other tables. The AUTOTRACE output for the query shows that the Oracle optimizer looked at each table involved in order to get the single resultant record.

     Execution Plan     ----------------------------------------------------------          0      SELECT STATEMENT Optimizer=ALL_ROWS (Cost=1 Card=1 Bytes=4)        1    0   FILTER        2    1     TABLE ACCESS (BY INDEX ROWID) OF 'MAIN_TABLE' (TABLE) (Cost=1 Card=1                     Bytes=4)        3    2       INDEX (UNIQUE SCAN) OF 'SYS_C003477' (INDEX (UNIQUE)) (Cost=0                     Card=1)        4    1     INDEX (UNIQUE SCAN) OF 'SYS_C003479' (INDEX (UNIQUE)) (Cost=0 Card=1                     Bytes=2)        5    1     INDEX (UNIQUE SCAN) OF 'SYS_C003481' (INDEX (UNIQUE)) (Cost=1 Card=1                     Bytes=3)        6    1     INDEX (UNIQUE SCAN) OF 'SYS_C003483' (INDEX (UNIQUE)) (Cost=1 Card=1                     Bytes=3)     Statistics     ----------------------------------------------------------               0  recursive calls               0  db block gets              13  consistent gets               0  physical reads               0  redo size             446  bytes sent via SQL*Net to client             511  bytes received via SQL*Net from client               2  SQL*Net roundtrips to/from client               0  sorts (memory)               0  sorts (disk)               1  rows processed

The Statistics section shows the amount of work Oracle had to do to provide the result set, including 13 consistent gets.

But what if I know that there is a 90% chance of a match in the OR_TABLE_ONE table and only a 10% chance of a match in the other tables? I'd want to look in OR_TABLE_ONE and only go to the others if I didn't find a record there. One solution is to use nested cursors within a table function so the whole operation can still be done as a query.

     CREATE OR REPLACE FUNCTION nested                       RETURN number_t AS       -- get a main table record       CURSOR curs_get_mt IS       SELECT mt.col1,              CURSOR ( SELECT 1                         FROM or_table_one                        WHERE col11 = mt.col1 ),              CURSOR ( SELECT 1                         FROM or_table_two                        WHERE col21 = mt.col1 ),              CURSOR ( SELECT 1                         FROM or_table_three                        WHERE col31 = mt.col1 )         FROM main_table mt        WHERE col1 = 1;       v_col1       NUMBER;       cursor_one   SYS_REFCURSOR;       cursor_two   SYS_REFCURSOR;       cursor_three SYS_REFCURSOR;       v_dummy      NUMBER;       v_ret_val number_t := number_t(  );     BEGIN       OPEN curs_get_mt;       FETCH curs_get_mt INTO v_col1,                              cursor_one,                              cursor_two,                              cursor_three;       IF curs_get_mt%FOUND THEN         -- look in the first OR table         FETCH cursor_one INTO v_dummy;         IF cursor_one%FOUND THEN           v_ret_val.EXTEND;           v_ret_val(v_ret_val.LAST) := v_col1;         ELSE           -- look in the seconds OR table           FETCH cursor_two INTO v_dummy;           IF cursor_two%FOUND THEN             v_ret_val.EXTEND;             v_ret_val(v_ret_val.LAST) := v_col1;           ELSE             - look in the third OR table             FETCH cursor_three INTO v_dummy;             IF cursor_two%FOUND THEN               v_ret_val.EXTEND;               v_ret_val(v_ret_val.LAST) := v_col1;             END IF;           END IF;         END IF;       END IF;       IF cursor_one%ISOPEN THEN         CLOSE cursor_one;       END IF;       IF cursor_two%ISOPEN THEN         CLOSE cursor_two;       END IF;       IF cursor_three%ISOPEN THEN         CLOSE cursor_three;       END IF;       CLOSE curs_get_mt;       RETURN(v_ret_val);     END;

The AUTOTRACE output for the function looks like this.

     SQL> SELECT *       2    FROM TABLE(nested);     COLUMN_VALUE     ------------                1     Execution Plan     ----------------------------------------------------------        0      SELECT STATEMENT Optimizer=ALL_ROWS (Cost=25 Card=8168 Bytes=16336)        1    0   COLLECTION ITERATOR (PICKLER FETCH) OF 'NESTED'     Statistics     ----------------------------------------------------------              13  recursive calls               0  db block gets               3  consistent gets               0  physical reads               0  redo size             397  bytes sent via SQL*Net to client             511  bytes received via SQL*Net from client               2  SQL*Net roundtrips to/from client               0  sorts (memory)               0  sorts (disk)               1  rows processed

The optimizer cost of the function evaluated much higher than the query, 25 to 1. But the database had to do a little less work, 3 consistent gets versus 13.

A word of caution: switch to this implementation only when you have identified a bottleneck and only after thorough testing. The performance gains may not be worth the extra lines of code you have to write, debug, and then maintain.





Oracle PL(s)SQL For DBAs
Oracle PL(s)SQL For DBAs
ISBN: N/A
EAN: N/A
Year: 2005
Pages: 122

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net