Pipelined functions have been available in Oracle for several versions (and years) since 9i Release 1 and there are several related articles on oracle-developer.net. This latest article summarises some of the issues we might encounter when using pipelined functions in our applications.
issue 1: parallel pipelined functions and cursor variables
One of the major benefits of pipelined functions is that they can be parallelised. This means that Oracle's parallel query mechanism can be exploited to execute PL/SQL code in parallel, providing excellent performance gains. For a pipelined function to be parallel-enabled, however, it must take its source dataset from a ref cursor parameter (rather than have a static cursor defined inside the PL/SQL). Such a function would be used as follows (in pseudo-code):
SELECT *
FROM TABLE(
pipelined_function(
CURSOR(SELECT * FROM staging_table))) --<-- input rowsource
The cursor parameter in a parallel-enabled pipelined function can be defined as either a weak or strong refcursor. The first issue we will see is that when we use a cursor variable to pass to the pipelined function (instead of the direct CURSOR function call as above), Oracle's parallel DML fails.
It should be stated at the outset that this problem only occurs when we are using parallel DML to load a table from the resultset of a pipelined function (i.e. not when simply selecting from the function). This is important, however, because parallel DML from pipelined functions is how Oracle most commonly demonstrates the technology in its articles and documentation.
We will setup a small example of a parallel insert from a parallel pipelined function. We will begin by creating a source and target table of the same structure (for simplicity) as follows.
SQL> CREATE TABLE source_table ( x PRIMARY KEY, y )
2 PARALLEL
3 NOLOGGING
4 AS
5 SELECT ROWNUM, CAST(NULL AS INTEGER)
6 FROM dual
7 CONNECT BY ROWNUM <= 10000;
Table created.
SQL> CREATE TABLE target_table
2 PARALLEL
3 NOLOGGING
4 AS
5 SELECT *
6 FROM source_table;
Table created.
We will now create the types required for our pipelined function (an object type to define a record and a collection type for buffering arrays of this record), as follows.
SQL> CREATE TYPE target_table_row AS OBJECT
2 ( x INT, y INT );
3 /
Type created.
SQL> CREATE TYPE target_table_rows
2 AS TABLE OF target_table_row;
3 /
Type created.
To complete our setup, we will create a parallel-enabled pipelined function (in a package) as follows. The cursor parameter in this example is defined as a SYS_REFCURSOR (built-in weak refcursor type).
SQL> CREATE OR REPLACE PACKAGE etl_pkg AS
2
3 FUNCTION pipelined_fx (p_cursor IN SYS_REFCURSOR)
4 RETURN target_table_rows PIPELINED
5 PARALLEL_ENABLE (PARTITION p_cursor BY ANY);
6
7 END etl_pkg;
8 /
Package created.
The function, implemented in the package body below, simply pipes out the input dataset. Obviously, this is not a true representation of what pipelined functions are needed for, but it keeps the example short and simple.
SQL>CREATE OR REPLACE PACKAGE BODY etl_pkg AS
FUNCTION pipelined_fx (p_cursor IN SYS_REFCURSOR)
RETURN target_table_rows PIPELINED
PARALLEL_ENABLE (PARTITION p_cursor BY ANY) IS
TYPE cursor_ntt IS TABLE OF source_table%ROWTYPE;
nt_src_data cursor_ntt;
BEGIN
LOOP
FETCH p_cursor BULK COLLECT INTO nt_src_data LIMIT 100;
FOR i IN 1 .. nt_src_data.COUNT LOOP
PIPE ROW (target_table_row(
nt_src_data(i).x, nt_src_data(i).y
));
END LOOP;
EXIT WHEN p_cursor%NOTFOUND;
END LOOP;
CLOSE p_cursor;
RETURN;
END pipelined_fx;
END etl_pkg;
Package body created.
We will now test the function by inserting its output into the target table we created earlier. We enable both parallel query and parallel DML to avoid having to use any hints (we already parallel-enabled both the source and target tables). Note how we use the CURSOR function to supply the refcursor parameter to our pipelined function.
SQL> ALTER SESSION ENABLE PARALLEL QUERY;
Session altered.
SQL> ALTER SESSION ENABLE PARALLEL DML;
Session altered.
SQL> BEGIN
2
3 INSERT INTO target_table
4 SELECT *
5 FROM TABLE(
6 etl_pkg.pipelined_fx(
7 CURSOR(SELECT * FROM source_table) ));
8
9 DBMS_OUTPUT.PUT_LINE( SQL%ROWCOUNT || ' rows inserted.' );
10
11 ROLLBACK;
12
13 END;
14 /
10000 rows inserted.
PL/SQL procedure successfully completed.
As we are using a refcursor parameter in our pipelined function, we might wish to use a cursor variable instead of a direct CURSOR function call. For example, our source data cursor might be large and complex. Therefore, we might wish to avoid having to embed a large SQL statement in a packaged function call. The following example demonstrates, however, that this will not work with parallel DML statements. This is true of all versions up to and including 11.1.0.6 (this is the latest version tested).
SQL> DECLARE
2 rc SYS_REFCURSOR;
3 BEGIN
4 OPEN RC FOR SELECT * FROM source_table;
5 INSERT INTO target_table
6 SELECT *
7 FROM TABLE(etl_pkg.pipelined_fx(rc));
8 DBMS_OUTPUT.PUT_LINE( SQL%ROWCOUNT || ' rows inserted.' );
9 ROLLBACK;
10 END;
11 /
DECLARE
*
ERROR at line 1:
ORA-12801: error signaled in parallel query server P007
ORA-01008: not all variables bound
ORA-06512: at line 9
The cursor variable method works in serial mode. If we disable parallel DML, the statement succeeds as follows.
SQL> ALTER SESSION DISABLE PARALLEL DML;
Session altered.
SQL> DECLARE
2 rc SYS_REFCURSOR;
3 BEGIN
4 OPEN RC FOR SELECT * FROM source_table;
5 INSERT INTO target_table
6 SELECT *
7 FROM TABLE(etl_pkg.pipelined_fx(rc));
8 DBMS_OUTPUT.PUT_LINE( SQL%ROWCOUNT || ' rows inserted.' );
9 ROLLBACK;
10 END;
11 /
10000 rows inserted.
PL/SQL procedure successfully completed.
This issue has been recorded as bug 5349930 and still exists in 11.1.0.6.
issue 2: performance with wide records
One of the uses for pipelined functions is to replace row-by-row inserts/updates with a piped rowsource that is bulk loaded. For example, the following "traditional" PL/SQL-based ETL technique is slow and inefficient.
FOR rec IN (SELECT * FROM source_data) LOOP
...prepare table_A variables...
INSERT INTO table_A VALUES (...);
END LOOP;
Assuming the "prepare table_A variables" stage in the above pseudo-code is sufficiently complex to warrant the use of PL/SQL, pipelined functions can be used to exploit bulk SQL techniques as follows.
INSERT INTO table_A (...)
SELECT ...
FROM TABLE( pipelined_fx( CURSOR(SELECT * FROM source_table) ) );
We can achieve some good performance gains by adopting this technique, especially if we use parallel-enabled pipelined functions. There is, however, an issue with "wide" records where the efficiency of pipelined functions degrades quite dramatically to the point where the row-by-row alternative is faster.
To demonstrate this issue, we are going to manufacture some variable-width records and simply compare the time it takes to load one table using row-by-row inserts and bulk insert from a pipelined function. We will begin by creating a source and target table with just three columns to demonstrate the type of comparison we will be making.
SQL> CREATE TABLE src
2 CACHE
3 AS
4 SELECT 'xxxxxxxxxx' AS c1
5 , 'xxxxxxxxxx' AS c2
6 , 'xxxxxxxxxx' AS c3
7 FROM dual
8 CONNECT BY ROWNUM <= 50000;
Table created.
SQL> CREATE TABLE tgt
2 AS
3 SELECT *
4 FROM src
5 WHERE ROWNUM < 1;
Table created.
We will now create an ETL package containing the two methods of loading that we wish to compare. The specification is as follows.
SQL> CREATE PACKAGE etl_pkg AS
2 PROCEDURE row_by_row;
3 PROCEDURE bulk_from_pipeline;
4 TYPE piped_rows IS TABLE OF tgt%ROWTYPE;
5 FUNCTION pipelined_fx(p_cursor IN SYS_REFCURSOR)
6 RETURN piped_rows PIPELINED;
7 END etl_pkg;
8 /
Package created.
Note that we have defined a PL/SQL-based collection type for our pipelined function. This method can raise issues in itself (as we will see later in this article), but for the purposes of this demonstration, it aligns the collection to however many columns the target table happens to have at runtime (if the table changes, Oracle will recompile the package when we next execute it and create a new underlying collection type to support the pipelined function).
The package body is as follows. We can see that the ROW_BY_ROW and BULK_FROM_PIPELINE procedures implement the two types of loading we saw in pseudo-code earlier. For simplicity, the pipelined function pipes out the input dataset without any modifications.
SQL> CREATE PACKAGE BODY etl_pkg AS
2
3 PROCEDURE row_by_row IS
4 BEGIN
5 FOR r IN (SELECT * FROM src) LOOP
6 INSERT INTO tgt VALUES r;
7 END LOOP;
8 COMMIT;
9 END row_by_row;
10
11 PROCEDURE bulk_from_pipeline IS
12 BEGIN
13 INSERT INTO tgt
14 SELECT *
15 FROM TABLE(
16 etl_pkg.pipelined_fx(
17 CURSOR( SELECT * FROM src )));
18 COMMIT;
19 END bulk_from_pipeline;
20
21 FUNCTION pipelined_fx(p_cursor IN SYS_REFCURSOR)
22 RETURN piped_rows PIPELINED IS
23 nt piped_rows;
24 BEGIN
25 LOOP
26 FETCH p_cursor BULK COLLECT INTO nt LIMIT 100;
27 FOR i IN 1 .. nt.COUNT LOOP
28 PIPE ROW (nt(i));
29 END LOOP;
30 EXIT WHEN p_cursor%NOTFOUND;
31 END LOOP;
32 RETURN;
33 END pipelined_fx;
34
35 END etl_pkg;
36 /
Package body created.
With just three columns to begin, we will compare the two methods using the wall-clock as follows. Note that we have deliberately turned off the PL/SQL compiler optimisation introduced in 10g (this optimisation turns cursor-for-loops into implicit bulk fetches).
SQL> ALTER SESSION SET PLSQL_OPTIMIZE_LEVEL = 0;
Session altered.
SQL> set timing on
SQL> exec etl_pkg.row_by_row;
PL/SQL procedure successfully completed.
Elapsed: 00:00:02.82
SQL> exec etl_pkg.bulk_from_pipeline;
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.59
At such low data volume, the timings are reasonably meaningless (note that the source table data was already queried before the test, so the effects of PIOs vs LIOs should be mitigated). Nevertheless, the pipelined function method is noticeably faster.
We will now create a small procedure that will rebuild our tables with the number of columns we supply. We will use this to compare ROW_BY_ROW and BULK_FROM_PIPELINE at various record-widths.
SQL> CREATE PROCEDURE rebuild_tables( p_cols IN NUMBER ) IS
2
3 v_ddl VARCHAR2(32767) := 'CREATE TABLE src CACHE AS SELECT ';
4
5 PROCEDURE drop_table( p_table IN VARCHAR2 ) IS
6 x_no_such_table EXCEPTION;
7 PRAGMA EXCEPTION_INIT(x_no_such_table, -942);
8 BEGIN
9 EXECUTE IMMEDIATE 'DROP TABLE ' || p_table;
10 EXCEPTION
11 WHEN x_no_such_table THEN NULL;
12 END drop_table;
13
14 BEGIN
15 drop_table('TGT');
16 FOR i IN 1 .. p_cols LOOP
17 v_ddl := v_ddl || '''xxxxxxxxxx'' AS c' || i || ',';
18 END LOOP;
19 v_ddl := RTRIM(v_ddl,',') ||
20 ' FROM dual CONNECT BY ROWNUM <= 50000';
21 EXECUTE IMMEDIATE v_ddl;
22 v_ddl := 'CREATE TABLE tgt
23 AS
24 SELECT *
25 FROM src
26 WHERE ROWNUM < 1';
27 EXECUTE IMMEDIATE v_ddl;
28 DBMS_STATS.GATHER_TABLE_STATS(user,'SRC');
29 v_ddl := 'ALTER PACKAGE etl_pkg COMPILE';
30
31 END;
32 /
Procedure created.
The following table shows the timings for both methods at 50, 100 and 150 columns on 10.2, 10.1 and 9.2 databases. All timings are from a second run of each procedure and, where relevant, 10g PL/SQL optimisation is disabled.
Columns | 10.2 Row (s) | 10.2 Bulk (s) | 10.1 Row (s) | 10.1 Bulk (s) | 9.2 Row (s) | 9.2 Bulk (s) |
50 |
9.07 |
4.37 |
9.90 |
8.65 |
7.07 |
8.00 |
100 |
14.23 |
12.98 |
15.79 |
18.42 |
13.05 |
17.01 |
150 | 14.90 | 19.04 | 24.82 | 30.86 | 18.06 | 29.01 |
The point at which the bulk method becomes slower than row-by-row processing is highlighted for each database version and we can see that it has improved with each release (the patterns are the same with repeated runs of the test). In 9i, the pipelined function performance degrades with records of around 50 attributes or fewer, while in 10g Release 2 (and since confirmed in 11g Release 1) it is with records of somewhere around 150 attributes.
To dig deeper than the "wall-clock" allows, we will compare the row-by-row and bulk pipelined function inserts using a variation on Tom Kyte's RUNSTATS utility (available here). In the following example, we compare the loads at 150 columns on a 10.2 database to see if we can determine the cause of the performance degradation.
SQL> exec runstats_pkg.rs_start;
PL/SQL procedure successfully completed.
SQL> exec etl_pkg.row_by_row;
PL/SQL procedure successfully completed.
SQL> exec runstats_pkg.rs_middle;
PL/SQL procedure successfully completed.
SQL> exec etl_pkg.bulk_from_pipeline;
PL/SQL procedure successfully completed.
SQL> exec runstats_pkg.rs_stop(5000);
Run1 ran in 2111 hsecs
Run2 ran in 2243 hsecs
Run1 ran in 94.12% of the time
Name Run1 Run2 Diff
LATCH.row cache objects 17,417 9,661 -7,756
STAT..IMU Redo allocation size 0 8,672 8,672
LATCH.object queue header oper 74,739 65,960 -8,779
LATCH.session allocation 26,480 17,555 -8,925
LATCH.simulator hash latch 32,361 21,450 -10,911
LATCH.simulator lru latch 29,913 18,400 -11,513
LATCH.cache buffers lru chain 63,670 51,941 -11,729
STAT..table scan blocks gotten 50,176 12,729 -37,447
STAT..consistent gets 66,133 28,152 -37,981
STAT..consistent gets from cac 66,133 28,152 -37,981
STAT..no work - consistent rea 52,058 14,005 -38,053
STAT..execute count 50,870 505 -50,365
LATCH.shared pool 57,480 5,370 -52,110
STAT..redo entries 114,550 52,821 -61,729
STAT..db block gets 143,282 81,419 -61,863
STAT..db block gets from cache 143,282 81,419 -61,863
STAT..db block changes 165,904 66,971 -98,933
STAT..session logical reads 209,415 109,571 -99,844
LATCH.library cache pin 104,611 3,265 -101,346
LATCH.library cache 108,966 6,403 -102,563
STAT..recursive calls 111,548 7,402 -104,146
STAT..table scan rows gotten 204,973 55,321 -149,652
STAT..physical read bytes 319,488 548,864 229,376
STAT..physical read total byte 319,488 548,864 229,376
STAT..session uga memory max 0 261,856 261,856
STAT..session pga memory -327,680 65,536 393,216
LATCH.cache buffers chains 755,709 360,644 -395,065
STAT..undo change vector size 3,181,600 1,004,352 -2,177,248
STAT..redo size 122,201,564 88,791,160 -33,410,404
Run1 latches total versus run2 -- difference and pct
Run1 Run2 Diff Pct
1,350,719 635,670 -715,049 212.49%
PL/SQL procedure successfully completed.
We can see that the row-by-row method uses far more resources than the bulk pipelined function method and yet it is still quicker. Many of the resource differences we see above are directly attributable to row-by-row insert inefficiencies (such as the volume of redo and the additional latching). Note that the "table scan rows gotten" statistic is misleading: we haven't accidentally loaded TGT with 204,000 rows in the row-based version (these additional rows must be related to recursive SQL).
Reversing the running order of the test makes no difference to the overall patterns we've seen in timings and resource statistics. As a final investigation, we will run the load under SQL trace as follows.
SQL> exec DBMS_MONITOR.SESSION_TRACE_ENABLE;
PL/SQL procedure successfully completed.
SQL> exec etl_pkg.row_by_row;
PL/SQL procedure successfully completed.
SQL> exec etl_pkg.bulk_from_pipeline;
PL/SQL procedure successfully completed.
SQL> exec DBMS_MONITOR.SESSION_TRACE_DISABLE;
PL/SQL procedure successfully completed.
If we run the trace file through TKProf, we see the following statements for the row-by-row load.
********************************************************************************
SELECT *
FROM
SRC
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 0 0.00 0.00 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 50001 1.79 1.67 0 50011 0 50000
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 50002 1.79 1.68 0 50011 0 50000
Misses in library cache during parse: 0
Optimizer mode: ALL_ROWS
Parsing user id: 54 (recursive depth: 1)
********************************************************************************
INSERT INTO TGT
VALUES
(:B1 ,:B2 ,:B3 ,:B4 ,:B5 ,:B6 ,:B7 ,:B8 ,:B9 ,:B10 ,:B11 ,:B12 ,:B13 ,:B14 ,
:B15 ,:B16 ,:B17 ,:B18 ,:B19 ,:B20 ,:B21 ,:B22 ,:B23 ,:B24 ,:B25 ,:B26 ,
:B27 ,:B28 ,:B29 ,:B30 ,:B31 ,:B32 ,:B33 ,:B34 ,:B35 ,:B36 ,:B37 ,:B38 ,
:B39 ,:B40 ,:B41 ,:B42 ,:B43 ,:B44 ,:B45 ,:B46 ,:B47 ,:B48 ,:B49 ,:B50 ,
:B51 ,:B52 ,:B53 ,:B54 ,:B55 ,:B56 ,:B57 ,:B58 ,:B59 ,:B60 ,:B61 ,:B62 ,
:B63 ,:B64 ,:B65 ,:B66 ,:B67 ,:B68 ,:B69 ,:B70 ,:B71 ,:B72 ,:B73 ,:B74 ,
:B75 ,:B76 ,:B77 ,:B78 ,:B79 ,:B80 ,:B81 ,:B82 ,:B83 ,:B84 ,:B85 ,:B86 ,
:B87 ,:B88 ,:B89 ,:B90 ,:B91 ,:B92 ,:B93 ,:B94 ,:B95 ,:B96 ,:B97 ,:B98 ,
:B99 ,:B100 ,:B101 ,:B102 ,:B103 ,:B104 ,:B105 ,:B106 ,:B107 ,:B108 ,:B109 ,
:B110 ,:B111 ,:B112 ,:B113 ,:B114 ,:B115 ,:B116 ,:B117 ,:B118 ,:B119 ,:B120
,:B121 ,:B122 ,:B123 ,:B124 ,:B125 ,:B126 ,:B127 ,:B128 ,:B129 ,:B130 ,
:B131 ,:B132 ,:B133 ,:B134 ,:B135 ,:B136 ,:B137 ,:B138 ,:B139 ,:B140 ,:B141
,:B142 ,:B143 ,:B144 ,:B145 ,:B146 ,:B147 ,:B148 ,:B149 ,:B150 )
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 0 0.00 0.00 0 0 0 0
Execute 50000 3.39 7.45 2 11217 145770 50000
Fetch 0 0.00 0.00 0 0 0 0
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 50000 3.39 7.45 2 11217 145770 50000
Misses in library cache during parse: 0
Misses in library cache during execute: 2
Optimizer mode: ALL_ROWS
Parsing user id: 54 (recursive depth: 1)
Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
---------------------------------------- Waited ---------- ------------
db file sequential read 2 0.02 0.02
log buffer space 18 0.45 2.85
log file switch completion 7 0.49 1.24
latch: cache buffers chains 1 0.00 0.00
latch: shared pool 3 0.00 0.00
********************************************************************************
We can see that the individual user-SQL components of the row-by-row load have not really accounted for much time, nor did they spend much time waiting for resources. If we look at the bulk pipelined load SQL, we see the following statistics.
********************************************************************************
SELECT "A3"."C1" "C1","A3"."C2" "C2","A3"."C3" "C3","A3"."C4" "C4","A3"."C5"
"C5","A3"."C6" "C6","A3"."C7" "C7","A3"."C8" "C8","A3"."C9" "C9","A3"."C10"
"C10","A3"."C11" "C11","A3"."C12" "C12","A3"."C13" "C13","A3"."C14" "C14",
"A3"."C15" "C15","A3"."C16" "C16","A3"."C17" "C17","A3"."C18" "C18",
...<snip>...
"A3"."C143" "C143","A3"."C144" "C144","A3"."C145" "C145","A3"."C146" "C146",
"A3"."C147" "C147","A3"."C148" "C148","A3"."C149" "C149","A3"."C150" "C150"
FROM
"SRC" "A3"
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 501 1.71 1.80 0 12511 0 50000
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 503 1.71 1.80 0 12511 0 50000
Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 54 (recursive depth: 2)
********************************************************************************
INSERT INTO TGT SELECT * FROM TABLE( ETL_PKG.PIPELINED_FX( CURSOR( SELECT *
FROM SRC )))
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 0 0.00 0.00 0 0 0 0
Execute 1 16.98 17.50 1 12024 78652 50000
Fetch 0 0.00 0.00 0 0 0 0
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 1 16.98 17.50 1 12024 78652 50000
Misses in library cache during parse: 0
Misses in library cache during execute: 1
Optimizer mode: ALL_ROWS
Parsing user id: 54 (recursive depth: 1)
Elapsed times include waiting on following events:
Event waited on Times Max. Wait Total Waited
---------------------------------------- Waited ---------- ------------
db file sequential read 1 0.00 0.00
log file switch completion 5 0.21 0.29
log buffer space 2 0.13 0.18
********************************************************************************
The source cursor for the bulk load is faster still, but the bulk insert statement has accounted for the majority of the runtime. Again the waits are trivial, but we'd need to be able to investigate lower still to find out what, if anything, is causing Oracle's reduced performance at this level. It has been suggested that it is Oracle's handling of object types (that underpins pipelined functions) that might contribute to this performance issue but we cannot prove this from the above data.
issue 3: versioned objects: ora-04043
The implementation of all pipelined functions is supported by object and collection types. Oracle provides three ways of defining these types, but most developers will either create their own object and collection types explicitly or rely on "versioned types" that Oracle generates from PL/SQL packaged record and collection declarations. There is an interesting bug with versioned types that is worthy of note in this article.
To demonstrate the issue, we will create a pipelined function that relies on versioned types for its implementation. To do this, we simply create a package with a global record type, a collection type based on this record type and the pipelined function itself, as follows.
SQL> CREATE PACKAGE etl_pkg AS
2
3 TYPE plsql_record_type IS RECORD
4 ( a1 VARCHAR2(30)
5 , a2 VARCHAR2(30)
6 , a3 VARCHAR2(30)
7 );
8
9 TYPE plsql_nested_table_type
10 IS TABLE OF plsql_record_type;
11
12 FUNCTION parallel_fx
13 RETURN plsql_nested_table_type
14 PIPELINED;
15
16 END etl_pkg;
17 /
Package created.
Remember that pipelined functions require SQL types to be able to pipe collections of data to the consumer. Because we have only declared PL/SQL types to support our pipelined function, Oracle creates the SQL types on our behalf. We can see these in the dictionary using the following type of query.
SQL> WITH t AS (
2 SELECT object_id AS o
3 FROM user_objects
4 WHERE object_name = 'ETL_PKG'
5 AND object_type = 'PACKAGE'
6 )
7 SELECT type_name, typecode
8 FROM user_types
9 WHERE type_name LIKE 'SYS%'
10 AND type_name LIKE '%' || (SELECT o FROM t) || '%';
TYPE_NAME TYPECODE
------------------------------ ------------------------------
SYS_PLSQL_53640_33_1 COLLECTION
SYS_PLSQL_53640_9_1 OBJECT
SYS_PLSQL_53640_DUMMY_1 COLLECTION
3 rows selected.
The type names are system-generated but contain the object ID of the package that the types were created for (as an aside, if we base our PL/SQL types on a table%ROWTYPE, the corresponding object ID will be that of the table).
The issue we will see arises when we create synonyms for the versioned types. We are unlikely to do this knowingly (we have no need to access these types; access to the package is all that is required to execute the pipelined function). Nevertheless, some environments automatically generate synonyms for all objects created for their applications, so we will replicate something similar. In the following example, we will imagine that the supplied SH schema is our application user, with SCOTT being the application owner. SCOTT creates synonyms in the SH schema for all of its objects, as follows.
SQL> DECLARE
2 v_ddl VARCHAR2(1024) := 'CREATE SYNONYM sh."%s" FOR "%s"';
3 BEGIN
4 FOR r IN (SELECT object_name FROM user_objects) LOOP
5 EXECUTE IMMEDIATE
6 REPLACE(v_ddl, '%s', r.object_name);
7 END LOOP;
8 END;
9 /
PL/SQL procedure successfully completed.
Versioned types are so-named for a reason. If we recompile the package specification and repeat our query over USER_TYPES, we see that the trailing integer in the type name increases, as follows.
SQL> ALTER PACKAGE etl_pkg COMPILE;
Package altered.
SQL> WITH t AS (
2 SELECT object_id AS o
3 FROM user_objects
4 WHERE object_name = 'ETL_PKG'
5 AND object_type = 'PACKAGE'
6 )
7 SELECT type_name, typecode
8 FROM user_types
9 WHERE type_name LIKE 'SYS%'
10 AND type_name LIKE '%' || (SELECT o FROM t) || '%';
TYPE_NAME
------------------------------
SYS_PLSQL_53640_33_2
SYS_PLSQL_53640_9_2
SYS_PLSQL_53640_DUMMY_2
3 rows selected.
Our supporting types have been re-generated by Oracle and a new "version" has been created. If we use CREATE OR REPLACE, the "version numbers" are reset to 1. However, because we have already recompiled this package, it appears as though we have no more chances to change its state, as follows.
SQL> ALTER PACKAGE etl_pkg COMPILE;
ALTER PACKAGE etl_pkg COMPILE
*
ERROR at line 1:
ORA-04043: object SYS_PLSQL_53640_33_1 does not exist
We have hit bug number 3744836. We receive the same message if we try to drop the package, as follows.
SQL> DROP PACKAGE etl_pkg;
DROP PACKAGE etl_pkg
*
ERROR at line 1:
ORA-04043: object SYS_PLSQL_53640_33_1 does not exist
Finally, if we try to replace the package specification, we receive the following compilation errors.
SQL> CREATE OR REPLACE PACKAGE etl_pkg AS
2 c INTEGER;
3 END etl_pkg;
4 /
Warning: Package created with compilation errors.
SQL> sho err
Errors for PACKAGE ETL_PKG:
LINE/COL ERROR
-------- -----------------------------------------------------------------
0/0 ORA-04043: object SYS_PLSQL_53640_33_1 does not exist
Note the version number of the type that we are being told does not exist. We know from above that our system-generated types have been incremented to version 2. We can also verify that we have no version 1 types as follows.
SQL> WITH t AS (
2 SELECT object_id AS o
3 FROM user_objects
4 WHERE object_name = 'ETL_PKG'
5 AND object_type = 'PACKAGE'
6 )
7 SELECT type_name
8 FROM user_types
9 WHERE type_name LIKE 'SYS%'
10 AND type_name LIKE 'SYS_PLSQL_' || (SELECT o FROM t) || '%_1';
no rows selected
Of course, we created some synonyms earlier in the application user's schema (SH) and these are at version 1. But these are in another schema and we are trying to drop our own objects. Fortunately, we are able to see the root of this problem quite easily in a SQL trace file, so we will enable SQL trace and attempt to drop the ETL_PKG package, as follows.
SQL> ALTER SESSION SET SQL_TRACE = TRUE;
Session altered.
SQL> DROP PACKAGE etl_pkg;
DROP PACKAGE etl_pkg
*
ERROR at line 1:
ORA-04043: object SYS_PLSQL_53640_33_1 does not exist
SQL> ALTER SESSION SET SQL_TRACE = FALSE;
Session altered.
The trace file leads us to the cause of this problem. Before Oracle can drop, recompile or replace a package with versioned types, it needs to identify the associated types and drop these first. In 10.2, Oracle uses the following SQL statement to identify the types. Each type is then dropped in turn.
=====================
PARSING IN CURSOR #25 len=109 dep=1 uid=0 oct=3 lid=0 tim=78367384109 hv=2962406971 ad='1d176f3c'
select UNIQUE name from obj$ where name like 'SYS_PLSQL@_53640@_%' escape '@' and type# != 10 order by name
END OF STMT
Note that there is no schema reference in this query. We can run this query directly, as follows.
SQL> SELECT UNIQUE name
2 FROM obj$
3 WHERE name LIKE 'SYS_PLSQL@_53640@_%' ESCAPE '@'
4 AND type# != 10
5 ORDER BY
6 name;
NAME
------------------------------
SYS_PLSQL_53640_33_1
SYS_PLSQL_53640_33_2
SYS_PLSQL_53640_9_1
SYS_PLSQL_53640_9_2
SYS_PLSQL_53640_DUMMY_1
SYS_PLSQL_53640_DUMMY_2
6 rows selected.
The query returns the synonyms before our own schema's types! This means that Oracle will try to drop the version 1 synonym first, which of course doesn't exist as a type in our schema. To resolve this issue on 10.2, we can simply drop the redundant synonyms, as follows.
SQL> BEGIN
2 FOR r IN (SELECT synonym_name
3 FROM dba_synonyms
4 WHERE owner = 'SH'
5 AND synonym_name LIKE 'SYS_PLSQL%')
6 LOOP
7 EXECUTE IMMEDIATE
8 'DROP SYNONYM sh.' || r.synonym_name;
9 END LOOP;
10 END;
11 /
PL/SQL procedure successfully completed.
We can now test that Oracle's recursive SQL statement returns the correct versioned type details, as follows.
SQL> SELECT UNIQUE name
2 FROM obj$
3 WHERE name LIKE 'SYS_PLSQL@_53640@_%' ESCAPE '@'
4 AND type# != 10
5 ORDER BY
6 name;
NAME
------------------------------
SYS_PLSQL_53640_33_2
SYS_PLSQL_53640_9_2
SYS_PLSQL_53640_DUMMY_2
3 rows selected.
We should now be able to drop the package.
SQL> DROP PACKAGE etl_pkg;
Package dropped.
a special note for 11g release 1
Note that this bug is fixed in 11g. Oracle has changed its versioned type naming rules to resolve the issue altogether. In 11g, regardless of whether we recompile or replace a package, Oracle retains the corresponding versioned type names (in other words, their number suffix doesn't increment). Hence, Oracle's recursive query against OBJ$ to determine the corresponding type names will always return the correct values, enabling the versioned types to be dropped.
a special note for 9i and 10g release 1
Prior to 10g Release 2, Oracle used a slightly different recursive statement to identify versioned types belonging to a package. The following is taken from a 10.1 trace file.
=====================
PARSING IN CURSOR #29 len=78 dep=1 uid=0 oct=3 lid=0 tim=79199728262 hv=1397641108 ad='21e1cf10'
select UNIQUE name from obj$ where name like 'SYS_PLSQL_53766_%' order by name
END OF STMT
The main difference with this earlier version of the recursive SQL is that there is no restriction on the OBJ$.TYPE# column. On a 10.1 database with the same example code as above, this query returns the following values both before and after the synonyms are dropped.
SQL> SELECT UNIQUE name
2 FROM obj$
3 WHERE name LIKE 'SYS_PLSQL_53766_%'
4 ORDER BY
5 name;
NAME
------------------------------
SYS_PLSQL_53766_33_1
SYS_PLSQL_53766_33_2
SYS_PLSQL_53766_9_1
SYS_PLSQL_53766_9_2
SYS_PLSQL_53766_DUMMY_1
SYS_PLSQL_53766_DUMMY_2
6 rows selected.
This is critical because when we drop synonyms, Oracle updates the corresponding OBJ$ record to TYPE#=10, rather than delete the record from the dictionary. We can see this below by querying OBJ$ after the synonyms are dropped.
SQL> SELECT name, type#
2 FROM obj$
3 WHERE name LIKE 'SYS_PLSQL_53766_%'
4 ORDER BY
5 name;
NAME TYPE#
------------------------------ ----------
SYS_PLSQL_53766_33_1 10
SYS_PLSQL_53766_33_2 13
SYS_PLSQL_53766_9_1 10
SYS_PLSQL_53766_9_2 13
SYS_PLSQL_53766_DUMMY_1 10
SYS_PLSQL_53766_DUMMY_2 13
6 rows selected.
This means, of course, that we cannot drop, recompile or recreate the package until the obsolete synonym entries (TYPE#=10) are removed from OBJ$. This cleanup will be performed by SMON after a database bounce (or sometimes after an indeterminate period of time), after which point the package and associated versioned types can be dropped.
参考至:http://www.oracle-developer.net/display.php?id=423
本文原创,转载请注明出处、作者
如有错误,欢迎指正
邮箱:czmcj@163.com
相关推荐
第五次作业函数第一题--
本项目旨在利用深度学习方法实现作物病害的自动诊断。作物病害是农业生产中的重要问题,及时诊断和处理对于减少产量损失至关重要。 我们采用深度学习算法,通过分析作物的图像,实现对病害的自动识别和分类。项目使用的数据集包括公开的作物病害图像数据集,如ISIC等,并进行了预处理,包括图像增强、分割和特征提取等。 在运行环境方面,我们使用Python编程语言,基于TensorFlow、PyTorch等深度学习框架进行开发。为了提高计算效率,我们还使用了GPU加速计算。此外,我们还采用了Docker容器技术,确保实验结果的可重复性。 项目完成后,将实现对作物病害的快速、准确诊断,为农业生产提供有力支持,有助于减少产量损失。同时,项目成果也可应用于其他图像识别和分类任务。
机械设计CD驱动印刷设备step非常好的设计图纸100%好用.zip
python烟花代码
附件中是一个简单的烟花效果的代码示例: 在Python中,可以使用多种方式来模拟烟花效果,其中一种常用的方法是使用turtle模块,它提供了一个画布和一个小海龟,可以用来绘制各种图形。 这段代码首先导入了turtle模块和random模块,然后在屏幕上绘制了10次烟花爆炸的效果。每次爆炸都是由5个小圆组成,颜色随机选择,圆的大小也是随机的。 请注意,这段代码需要在支持turtle模块的Python环境中运行,并且需要有图形界面的支持。如果你在没有图形界面的环境中(比如某些服务器或者命令行界面),这段代码可能无法正常运行。
商业化产品经理,到底如何实现产品商业化?.docx
Panduit 工业以太网部件内部销售指南
在Java中,实现一个三维装箱(也称为三维背包问题)的算法通常涉及到组合优化和动态规划。这个问题是一个典型的优化问题,其中目标是在三个维度的限制下最大化价值的总和。下面是一个简单的Java代码示例,它使用动态规划来解决三维装箱问题。 请注意,这个代码只是一个简单的示例,它假设所有物品的第三个维度的大小都是1,并且没有给出如何回溯选择物品的完整逻辑。在实际应用中,三维装箱问题可能更加复杂,需要考虑所有三个维度的限制,并且可能需要更复杂的算法来解决。 此外,这个问题的解决方案可能需要根据具体问题的要求进行调整,例如物品是否可以分割、是否允许超过一个的物品等。如果你有特定的问题描述或者需要进一步的帮助,请提供更多的细节。
常用品牌EPLAN部件库
单片机开发的教程可以分为以下几个步骤: 1. 了解单片机基础知识:在学习单片机开发之前,需要了解单片机的相关知识,包括单片机的基本结构、指令系统、编程语言等。 2. 选择开发板:选择一款适合自己学习开发板的型号和厂商,通常需要关注开发板的性价比、开发环境是否友好等因素。 3. 学习开发环境:根据所选的开发板,学习相关的开发环境和使用方法,例如Keil、IAR等集成开发环境。 4. 掌握编程语言:单片机常用的编程语言包括C语言和汇编语言,根据实际情况选择其中一种进行学习。 5. 基础操作:熟悉单片机的引脚定义和IO口配置,了解单片机的启动代码,可以通过修改启动代码进行基本功能调试。 6. 综合实践:根据具体项目需求,进行单片机开发的综合实践。在实践中需要掌握如何编写程序、如何进行硬件调试、如何使用相关工具软件等技能。 下面是一个单片机开发的简单教程介绍: 首先,确定所使用的单片机型号和开发板类型。在这个阶段,需要查阅相关资料,了解开发板的规格书、芯片规格等基本资料。 其次,安装并配置开发环境。根据所选的开发板,安装相应的集成开发环境(IDE),并配置好开发环境。 接着,学习并掌
Q1.ipynb
(自适应手机端)IT网络建站公司pbootcms模板 互联网营销企业网站源码下载.zip
Bematech 激光扫描器用户手册
激励视频接入文档.pdf
java jdk1.8 202版本下载window linux打包
Lite Beam M5快速指南
互联网金融导论.docx
字节跳动青训营——抖音项目
Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。
Node.js,简称Node,是一个开源且跨平台的JavaScript运行时环境,它允许在浏览器外运行JavaScript代码。Node.js于2009年由Ryan Dahl创立,旨在创建高性能的Web服务器和网络应用程序。它基于Google Chrome的V8 JavaScript引擎,可以在Windows、Linux、Unix、Mac OS X等操作系统上运行。 Node.js的特点之一是事件驱动和非阻塞I/O模型,这使得它非常适合处理大量并发连接,从而在构建实时应用程序如在线游戏、聊天应用以及实时通讯服务时表现卓越。此外,Node.js使用了模块化的架构,通过npm(Node package manager,Node包管理器),社区成员可以共享和复用代码,极大地促进了Node.js生态系统的发展和扩张。 Node.js不仅用于服务器端开发。随着技术的发展,它也被用于构建工具链、开发桌面应用程序、物联网设备等。Node.js能够处理文件系统、操作数据库、处理网络请求等,因此,开发者可以用JavaScript编写全栈应用程序,这一点大大提高了开发效率和便捷性。 在实践中,许多大型企业和组织已经采用Node.js作为其Web应用程序的开发平台,如Netflix、PayPal和Walmart等。它们利用Node.js提高了应用性能,简化了开发流程,并且能更快地响应市场需求。