Friday, December 5, 2014

how to use DBMS_SQL.DESCRBE_TABLE



SQL>
SQL> CREATE TABLE EMP (EMPNO NUMBER(4NOT NULL,
  2                    ENAME VARCHAR2(10),
  3                    JOB VARCHAR2(9),
  4                    MGR NUMBER(4),
  5                    HIREDATE DATE,
  6                    SAL NUMBER(72),
  7                    COMM NUMBER(72),
  8                    DEPTNO NUMBER(2));

Table created.

SQL>
SQL>
SQL> INSERT INTO EMP VALUES (7369'SMITH', 'CLERK',    7902, TO_DATE('17-DEC-1980', 'DD-MON-YYYY'), 800, NULL, 20);

row created.

SQL> INSERT INTO EMP VALUES (7499'ALLEN', 'SALESMAN', 7698, TO_DATE('20-FEB-1981', 'DD-MON-YYYY'), 160030030);

row created.

SQL> INSERT INTO EMP VALUES (7521'WARD',  'SALESMAN', 7698, TO_DATE('22-FEB-1981', 'DD-MON-YYYY'), 125050030);

row created.

SQL> INSERT INTO EMP VALUES (7566'JONES', 'MANAGER',  7839, TO_DATE('2-APR-1981',  'DD-MON-YYYY'), 2975, NULL, 20);

row created.

SQL> INSERT INTO EMP VALUES (7654'MARTIN', 'SALESMAN', 7698,TO_DATE('28-SEP-1981', 'DD-MON-YYYY'), 1250140030);

row created.

SQL> INSERT INTO EMP VALUES (7698'BLAKE', 'MANAGER', 7839,TO_DATE('1-MAY-1981', 'DD-MON-YYYY'), 2850, NULL, 30);

row created.

SQL> INSERT INTO EMP VALUES (7782'CLARK', 'MANAGER', 7839,TO_DATE('9-JUN-1981', 'DD-MON-YYYY'), 2450, NULL, 10);

row created.

SQL> INSERT INTO EMP VALUES (7788'SCOTT', 'ANALYST', 7566,TO_DATE('09-DEC-1982', 'DD-MON-YYYY'), 3000, NULL, 20);

row created.

SQL> INSERT INTO EMP VALUES (7839'KING', 'PRESIDENT', NULL,TO_DATE('17-NOV-1981', 'DD-MON-YYYY'), 5000, NULL, 10);

row created.

SQL> INSERT INTO EMP VALUES (7844'TURNER', 'SALESMAN', 7698,TO_DATE('8-SEP-1981', 'DD-MON-YYYY'), 1500030);

row created.

SQL> INSERT INTO EMP VALUES (7876'ADAMS', 'CLERK', 7788,TO_DATE('12-JAN-1983', 'DD-MON-YYYY'), 1100, NULL, 20);

row created.

SQL> INSERT INTO EMP VALUES (7900'JAMES', 'CLERK', 7698,TO_DATE('3-DEC-1981', 'DD-MON-YYYY'), 950, NULL, 30);

row created.

SQL> INSERT INTO EMP VALUES (7902'FORD', 'ANALYST', 7566,TO_DATE('3-DEC-1981', 'DD-MON-YYYY'), 3000, NULL, 20);

row created.

SQL> INSERT INTO EMP VALUES (7934'MILLER', 'CLERK', 7782,TO_DATE('23-JAN-1982', 'DD-MON-YYYY'), 1300, NULL, 10);

row created.

SQL>
SQL> set echo on
SQL>
SQL> create or replace procedure  desc_queryp_query in varchar2 )
  2  is
  3      l_columnValue   varchar2(4000);
  4      l_status        integer;
  5      l_colCnt        number default 0;
  6      l_cnt           number default 0;
  7      l_line          long;
  8      l_descTbl       dbms_sql.desc_tab;
  9      l_theCursor     integer default dbms_sql.open_cursor;
 10  begin
 11      dbms_sql.parse(  l_theCursor,  p_query, dbms_sql.native );
 12      dbms_sql.describe_columnsc       => l_theCursor,
 13                                 col_cnt => l_colCnt,
 14                                 desc_t  => l_descTbl );
 15
 16      for i in .. l_colCnt
 17      loop
 18          dbms_output.put_line'Column Type:' || l_descTbl(i).col_type );
 19          dbms_output.put_line'Max Length:' || l_descTbl(i).col_max_len );
 20          dbms_output.put_line'Name:' || l_descTbl(i).col_name );
 21          dbms_output.put_line'Name Length:' || l_descTbl(i).col_name_len );
 22          dbms_output.put_line'ObjColumn Schema Name.' || l_descTbl(i).col_schema_name );
 23          dbms_output.put_line'Schema Name Length:' || l_descTbl(i).col_schema_name_len );
 24          dbms_output.put_line'Precision:' || l_descTbl(i).col_precision );
 25          dbms_output.put_line'Scale:' || l_descTbl(i).col_scale );
 26          dbms_output.put_line'Charsetid:' || l_descTbl(i).col_Charsetid );
 27          dbms_output.put_line'Charset Form:' || l_descTbl(i).col_charsetform );
 28          if l_desctbl(i).col_null_ok then
 29              dbms_output.put_line'Nullable:Y' );
 30          else
 31              dbms_output.put_line'Nullable:N' );
 32          end if;
 33       end loop;
 34      dbms_sql.close_cursor(l_theCursor);
 35  exception
 36      when others then dbms_sql.close_cursorl_theCursor );
 37          raise;
 38  end desc_query;
 39  /

Procedure created.

SQL>
SQL> set serveroutput on
SQL> exec desc_query'select rowid, ename from emp' );
Column Type:11
Max Length:16
Name:ROWID
Name Length:5
ObjColumn Schema Name.
Schema Name Length:0
Precision:0
Scale:0
Charsetid:0
Charset Form:0
Nullable:N
Column Type:1
Max Length:10
Name:ENAME
Name Length:5
ObjColumn Schema Name.
Schema Name Length:0
Precision:0
Scale:0
Charsetid:178
Charset Form:1
Nullable:Y

PL/SQL procedure successfully completed.

SQL>
SQL>
SQL> drop table emp;

Table dropped.

Monday, November 24, 2014

String Aggregation Techniques

Refrerence
On occasion it is necessary to aggregate data from a number of rows into a single row, giving a list of data associated with a specific value. Using the SCOTT.EMP table as an example, we might want to retrieve a list of employees for each department. Below is a list of the base data and the type of output we would like to return from an aggregate query.
Base Data:

    DEPTNO ENAME
---------- ----------
        20 SMITH
        30 ALLEN
        30 WARD
        20 JONES
        30 MARTIN
        30 BLAKE
        10 CLARK
        20 SCOTT
        10 KING
        30 TURNER
        20 ADAMS
        30 JAMES
        20 FORD
        10 MILLER

Desired Output:

    DEPTNO EMPLOYEES
---------- --------------------------------------------------
        10 CLARK,KING,MILLER
        20 SMITH,FORD,ADAMS,SCOTT,JONES
        30 ALLEN,BLAKE,MARTIN,TURNER,JAMES,WARD
This article is based on a thread from asktom.oracle.com and contains several methods to achieve the desired results.

LISTAGG Analystic Function in 11g Release 2

The LISTAGG analytic function was introduced in Oracle 11g Release 2, making it very easy to aggregate strings. The nice thing about this function is it also allows us to order the elements in the concatenated list. If you are using 11g Release 2 you should use this function for string aggregation.
COLUMN employees FORMAT A50

SELECT deptno, LISTAGG(ename, ',') WITHIN GROUP (ORDER BY ename) AS employees
FROM   emp
GROUP BY deptno;

    DEPTNO EMPLOYEES
---------- --------------------------------------------------
        10 CLARK,KING,MILLER
        20 ADAMS,FORD,JONES,SCOTT,SMITH
        30 ALLEN,BLAKE,JAMES,MARTIN,TURNER,WARD

3 rows selected.

WM_CONCAT Built-in Function (Not Supported)

If you are not running 11g Release 2, but are running a version of the database where the WM_CONCAT function is present, then it is a zero effort solution as it performs the aggregation for you. It is actually an example of a user defined aggregate function described below, but Oracle have done all the work for you.
COLUMN employees FORMAT A50

SELECT deptno, wm_concat(ename) AS employees
FROM   emp
GROUP BY deptno;

    DEPTNO EMPLOYEES
---------- --------------------------------------------------
        10 CLARK,KING,MILLER
        20 SMITH,FORD,ADAMS,SCOTT,JONES
        30 ALLEN,BLAKE,MARTIN,TURNER,JAMES,WARD

3 rows selected.
Note. WM_CONCAT is an undocumented function and as such is not supported by Oracle for user applications (MOS Note ID 1336219.1). If this concerns you, use a User-Defined Aggregate Function described below.

User-Defined Aggregate Function

The WM_CONCAT function described above is an example of a user-defined aggregate function that Oracle have already created for you. If you don't want to use WM_CONCAT, you can create your own user-defined aggregate function as described at asktom.oracle.com.
CREATE OR REPLACE TYPE t_string_agg AS OBJECT
(
  g_string  VARCHAR2(32767),

  STATIC FUNCTION ODCIAggregateInitialize(sctx  IN OUT  t_string_agg)
    RETURN NUMBER,

  MEMBER FUNCTION ODCIAggregateIterate(self   IN OUT  t_string_agg,
                                       value  IN      VARCHAR2 )
     RETURN NUMBER,

  MEMBER FUNCTION ODCIAggregateTerminate(self         IN   t_string_agg,
                                         returnValue  OUT  VARCHAR2,
                                         flags        IN   NUMBER)
    RETURN NUMBER,

  MEMBER FUNCTION ODCIAggregateMerge(self  IN OUT  t_string_agg,
                                     ctx2  IN      t_string_agg)
    RETURN NUMBER
);
/
SHOW ERRORS


CREATE OR REPLACE TYPE BODY t_string_agg IS
  STATIC FUNCTION ODCIAggregateInitialize(sctx  IN OUT  t_string_agg)
    RETURN NUMBER IS
  BEGIN
    sctx := t_string_agg(NULL);
    RETURN ODCIConst.Success;
  END;

  MEMBER FUNCTION ODCIAggregateIterate(self   IN OUT  t_string_agg,
                                       value  IN      VARCHAR2 )
    RETURN NUMBER IS
  BEGIN
    SELF.g_string := self.g_string || ',' || value;
    RETURN ODCIConst.Success;
  END;

  MEMBER FUNCTION ODCIAggregateTerminate(self         IN   t_string_agg,
                                         returnValue  OUT  VARCHAR2,
                                         flags        IN   NUMBER)
    RETURN NUMBER IS
  BEGIN
    returnValue := RTRIM(LTRIM(SELF.g_string, ','), ',');
    RETURN ODCIConst.Success;
  END;

  MEMBER FUNCTION ODCIAggregateMerge(self  IN OUT  t_string_agg,
                                     ctx2  IN      t_string_agg)
    RETURN NUMBER IS
  BEGIN
    SELF.g_string := SELF.g_string || ',' || ctx2.g_string;
    RETURN ODCIConst.Success;
  END;
END;
/
SHOW ERRORS


CREATE OR REPLACE FUNCTION string_agg (p_input VARCHAR2)
RETURN VARCHAR2
PARALLEL_ENABLE AGGREGATE USING t_string_agg;
/
SHOW ERRORS
The aggregate function is implemented using a type and type body, and is used within a query.
COLUMN employees FORMAT A50

SELECT deptno, string_agg(ename) AS employees
FROM   emp
GROUP BY deptno;

    DEPTNO EMPLOYEES
---------- --------------------------------------------------
        10 CLARK,KING,MILLER
        20 SMITH,FORD,ADAMS,SCOTT,JONES
        30 ALLEN,BLAKE,MARTIN,TURNER,JAMES,WARD

3 rows selected.

Specific Function

One approach is to write a specific function to solve the problems. The get_employees function listed below returns a list of employees for the specified department.
CREATE OR REPLACE FUNCTION get_employees (p_deptno  in  emp.deptno%TYPE)
  RETURN VARCHAR2
IS
  l_text  VARCHAR2(32767) := NULL;
BEGIN
  FOR cur_rec IN (SELECT ename FROM emp WHERE deptno = p_deptno) LOOP
    l_text := l_text || ',' || cur_rec.ename;
  END LOOP;
  RETURN LTRIM(l_text, ',');
END;
/
SHOW ERRORS
The function can then be incorporated into a query as follows.
COLUMN employees FORMAT A50

SELECT deptno,
       get_employees(deptno) AS employees
FROM   emp
GROUP by deptno;

    DEPTNO EMPLOYEES
---------- --------------------------------------------------
        10 CLARK,KING,MILLER
        20 SMITH,JONES,SCOTT,ADAMS,FORD
        30 ALLEN,WARD,MARTIN,BLAKE,TURNER,JAMES

3 rows selected.
To reduce the number of calls to the function, and thereby improve performance, we might want to filter the rows in advance.
COLUMN employees FORMAT A50

SELECT e.deptno,
       get_employees(e.deptno) AS employees
FROM   (SELECT DISTINCT deptno
        FROM   emp) e;

    DEPTNO EMPLOYEES
---------- --------------------------------------------------
        10 CLARK,KING,MILLER
        20 SMITH,JONES,SCOTT,ADAMS,FORD
        30 ALLEN,WARD,MARTIN,BLAKE,TURNER,JAMES
        
3 rows selected.

Generic Function using Ref Cursor

An alternative approach is to write a function to concatenate values passed using a ref cursor. This is essentially the same as the previous example, except that the cursor is passed in making it generic, as shown below.
CREATE OR REPLACE FUNCTION concatenate_list (p_cursor IN  SYS_REFCURSOR)
  RETURN  VARCHAR2
IS
  l_return  VARCHAR2(32767); 
  l_temp    VARCHAR2(32767);
BEGIN
  LOOP
    FETCH p_cursor
    INTO  l_temp;
    EXIT WHEN p_cursor%NOTFOUND;
    l_return := l_return || ',' || l_temp;
  END LOOP;
  RETURN LTRIM(l_return, ',');
END;
/
SHOW ERRORS
The CURSOR function is used to allow a query to be passed to the function as a ref cursor, as shown below.
COLUMN employees FORMAT A50

SELECT e1.deptno,
       concatenate_list(CURSOR(SELECT e2.ename FROM emp e2 WHERE e2.deptno = e1.deptno)) employees
FROM   emp e1
GROUP BY e1.deptno;

    DEPTNO EMPLOYEES
---------- --------------------------------------------------
        10 CLARK,KING,MILLER
        20 SMITH,JONES,SCOTT,ADAMS,FORD
        30 ALLEN,WARD,MARTIN,BLAKE,TURNER,JAMES

3 rows selected.
Once again, the total number of function calls can be reduced by filtering the distinct values, rather than calling the function for each row.
COLUMN employees FORMAT A50

SELECT deptno,
       concatenate_list(CURSOR(SELECT e2.ename FROM emp e2 WHERE e2.deptno = e1.deptno)) employees
FROM   (SELECT DISTINCT deptno
        FROM emp) e1;

    DEPTNO EMPLOYEES
---------- --------------------------------------------------
        10 CLARK,KING,MILLER
        20 SMITH,JONES,SCOTT,ADAMS,FORD
        30 ALLEN,WARD,MARTIN,BLAKE,TURNER,JAMES

3 rows selected.

ROW_NUMBER() and SYS_CONNECT_BY_PATH functions in Oracle 9i

An example on williamrobertson.net uses the ROW_NUMBER() and SYS_CONNECT_BY_PATH functions to achieve the same result without the use of PL/SQL or additional type definitions.
SELECT deptno,
       LTRIM(MAX(SYS_CONNECT_BY_PATH(ename,','))
       KEEP (DENSE_RANK LAST ORDER BY curr),',') AS employees
FROM   (SELECT deptno,
               ename,
               ROW_NUMBER() OVER (PARTITION BY deptno ORDER BY ename) AS curr,
               ROW_NUMBER() OVER (PARTITION BY deptno ORDER BY ename) -1 AS prev
        FROM   emp)
GROUP BY deptno
CONNECT BY prev = PRIOR curr AND deptno = PRIOR deptno
START WITH curr = 1;

    DEPTNO EMPLOYEES
---------- --------------------------------------------------
        10 CLARK,KING,MILLER
        20 ADAMS,FORD,JONES,SCOTT,SMITH
        30 ALLEN,BLAKE,JAMES,MARTIN,TURNER,WARD

3 rows selected.

COLLECT function in Oracle 10g

An example on oracle-developer.net uses the COLLECT function in Oracle 10g to get the same result. This method requires a table type and a function to convert the contents of the table type to a string. I've altered his method slightly to bring it in line with this article.
CREATE OR REPLACE TYPE t_varchar2_tab AS TABLE OF VARCHAR2(4000);
/

CREATE OR REPLACE FUNCTION tab_to_string (p_varchar2_tab  IN  t_varchar2_tab,
                                          p_delimiter     IN  VARCHAR2 DEFAULT ',') RETURN VARCHAR2 IS
  l_string     VARCHAR2(32767);
BEGIN
  FOR i IN p_varchar2_tab.FIRST .. p_varchar2_tab.LAST LOOP
    IF i != p_varchar2_tab.FIRST THEN
      l_string := l_string || p_delimiter;
    END IF;
    l_string := l_string || p_varchar2_tab(i);
  END LOOP;
  RETURN l_string;
END tab_to_string;
/
The query below shows the COLLECT function in action.
COLUMN employees FORMAT A50

SELECT deptno,
       tab_to_string(CAST(COLLECT(ename) AS t_varchar2_tab)) AS employees
FROM   emp
GROUP BY deptno;
       
    DEPTNO EMPLOYEES
---------- --------------------------------------------------
        10 CLARK,KING,MILLER
        20 SMITH,JONES,SCOTT,ADAMS,FORD
        30 ALLEN,WARD,MARTIN,BLAKE,TURNER,JAMES
        
3 rows selected.


Tuesday, November 4, 2014

Data File Media recovery

Recover the Corrupt file using the following command

SQL> recover datafile 'D:\ORACLE\ORADATA\SONA\SYSTEM01.DBF';
Media recovery complete.
Once the recovery is complete then restart the database

Viewing the Alert Log

You can view the alert log with a text editor, with Enterprise Manager, or with the ADRCI utility.
To view the alert log with Enterprise Manager:
  1. Access the Database Home page in Enterprise Manager.
    For Oracle Enterprise Manager Database Control, see Oracle Database 2 Day DBA for instructions. For Oracle Enterprise Manager Grid Control, go to the desired database target.
  2. Under Related Links, click Alert Log Contents.
    The View Alert Log Contents page appears.
  3. Select the number of entries to view, and then click Go.
To view the alert log with a text editor:
  1. Connect to the database with SQL*Plus or another query tool, such as SQL Developer.
  2. Query the V$DIAG_INFO view as shown in "Viewing ADR Locations with the V$DIAG_INFO View".
  3. To view the text-only alert log, without the XML tags, complete these steps:
    1. In the V$DIAG_INFO query results, note the path that corresponds to the Diag Trace entry, and change directory to that path.
    2. Open file alert_SID.log with a text editor.
  4. To view the XML-formatted alert log, complete these steps:
    1. In the V$DIAG_INFO query results, note the path that corresponds to the Diag Alert entry, and change directory to that path.
    2. Open the file log.xml with a text editor.

Tuesday, January 28, 2014

8 Bulk Update Methods Compared

Reference - http://www.orafaq.com/node/2450

What I love about writing SQL Tuning articles is that I very rarely end up publishing the findings I set out to achieve. With this one, I set out to demonstrate the advantages of PARALLEL DML, didn't find what I thought I would, and ended up testing 8 different techniques to find out how they differed. And guess what? I still didn't get the results I expected. Hey, at least I learned something.
As an ETL designer, I hate updates. They are just plain nasty. I spend an inordinate proportion of design time of an ETL system worrying about the relative proportion of rows inserted vs updated. I worry about how ETL tools apply updates (did you know DataStage applys updates singly, but batches inserts in arrays?), how I might cluster rows together that are subject to updates, and what I might do if I just get too many updates to handle.
It would be fair to say I obsess about them. A little bit.
The two most common forms of Bulk Updates are:

  1. Update (almost) every row in the table. This is common when applying data patches and adding new columns.
  2. Updating a small proportion of rows in a very large table.
Case 1 is uninteresting. The fastest way to update every row in the table is to rebuild the table from scratch. All of these methods below will perform worse.
Case 2 is common in Data Warehouses and overnight batch jobs. We have a table containing years worth of data, most of which is static; we are updating selected rows that were recently inserted and are still volatile. This case is the subject of our test. For the purposes of the test, we will assume that the target table of the update is arbitrarily large, and we want to avoid things like full-scans and index rebuilds.

And the nominees are...

The methods covered include both PL/SQL and SQL approaches. I want to test on a level playing field and remove special factors that unfairly favour one method, so there are some rules:
  • Accumulating data for the update can be arbitrarily complex. SQL updates can have joins with grouping and sub-queries and what-not; PL/SQL can have cursor loops with nested calls to other procedures. I'm not testing the relative merits of how to accumulate the data, so each test will use pre-preared update data residing in a Global Temporary Table.
  • Some methods - such as MERGE - allow the data source to be joined to the update target using SQL. Other methods don't have this capability and must use Primary Key lookups on the update target. To make these methods comparable, the "joinable" techniques will use a Nested Loops join to most closely mimic the Primary Key lookup of the other methods. Even though a Hash join may be faster than Nested Loops for some distributions of data, that is not always the case and - once again - we're assuming an arbitraily large target table, so a full scan is not necessarily feasible.
  • Having said that we're not comparing factors outside of the UPDATE itself, some of the methods do have differences unrelated to the UPDATE. I have included these deliberately because they are reasonably common and have different performance profiles; I wouldn't want anyone to think that because their statements were *similar* to those shown here that they have the same performance profile.
The 8 methods I am benchmarking here are as follows (in rough order of complexity):

  1. Explicit Cursor Loop
  2. Implicit Cursor Loop
  3. UPDATE with nested SET subquery
  4. BULK COLLECT / FORALL UPDATE
  5. Updateable Join View
  6. MERGE
  7. Parallel DML MERGE
  8. Parallel PL/SQL
For all of the tests, the following table structures will be used:
TEST{n} (Update Source) - 100K rows             TEST (Update target) - 10M rows

Name                           Type             Name                           Type             
------------------------------ ------------     ------------------------------ ------------
PK                             NUMBER           PK                             NUMBER
FK                             NUMBER           FK                             NUMBER
FILL                           VARCHAR2(40)     FILL                           VARCHAR2(40)
The data has the following characteristics:

  • TEST.PK will contain values 0 .. 9999999, but not in that order.
  • TEST.PK is poorly clustered. It is generated by reversing the digits in LPAD(ROWNUM, '0', 7). PK values of 1,2, and 3 are adjacent in the primary key index but one-million rows apart in the table.
  • TEST.FK will contain values 0 .. 99, unevenly distributed to favour lower numbers.
  • For the first round of testing, the column FK will be indexed with a simple b-tree index.

Method 1: Explicit Cursor Loop

Not many people code this way, but there are some Pro*C programmers out there who are used to Explicit Cursor Loops (OPEN, FETCH and CLOSE commands) and translate these techniques directly to PL/SQL. The UPDATE portion of the code works in an identical fashion to the Implicit Cursor Loop, so this is not really a separate "UPDATE" method as such. The interesting thing about this method is that it performs a context-switch between PL/SQL and SQL for every FETCH; this is less efficient. I include it here because it allows us to compare the cost of context-switches to the cost of updates.
DECLARE
    CURSOR c1 IS
        SELECT *
        FROM test6;

    rec_cur c1%rowtype;
BEGIN
    OPEN c1;
    LOOP
        FETCH c1 INTO rec_cur;
        EXIT WHEN c1%notfound;

        UPDATE test
        SET    fk = rec_cur.fk
        ,      fill = rec_cur.fill
        WHERE  pk = rec_cur.pk;
    END LOOP;
    CLOSE C1;
END;
/

Method 2: Implicit Cursor Loop

This is the simplest PL/SQL method and very common in hand-coded PL/SQL applications. Update-wise, it looks as though it should perform the same as the Explicit Cursor Loop. The difference is that the Implicit Cursor internally performs bulk fetches, which should be faster than the Explicit Cursor because of the reduced context switches.
BEGIN
    FOR rec_cur IN (
        SELECT *
        FROM test3
    ) LOOP
        UPDATE test
        SET    fk = rec_cur.fk
        ,      fill = rec_cur.fill
        WHERE  pk = rec_cur.pk;
    END LOOP;
END;
/

Method 3: UPDATE with nested SET subquery

This method is pretty common. I generally recommend against it for high-volume updates because the SET sub-query is nested, meaning it is performed once for each row updated. To support this method, I needed to create an index on TEST8.PK.
UPDATE test
SET    (fk, fill) = (
           SELECT test8.fk, test8.fill
           FROM   test8
           WHERE  pk = test.pk
)
WHERE  pk IN (
           SELECT pk
           FROM   test8
);

Method 4: BULK COLLECT / FORALL UPDATE

This one is gaining in popularity. Using BULK COLLECT and FORALL statements is the new de-facto standard for PL/SQL programmers concerned about performance because it reduces context switching overheads between the PL/SQL and SQL engines.
The biggest drawback to this method is readability. Since Oracle does not yet provide support for record collections in FORALL, we need to use scalar collections, making for long declarations, INTO clauses, and SET clauses.
DECLARE
    CURSOR rec_cur IS
    SELECT *
    FROM test4;

    TYPE num_tab_t IS TABLE OF NUMBER(38);
    TYPE vc2_tab_t IS TABLE OF VARCHAR2(4000);

    pk_tab NUM_TAB_T;
    fk_tab NUM_TAB_T;
    fill_tab VC2_TAB_T;
BEGIN
    OPEN rec_cur;
    LOOP
        FETCH rec_cur BULK COLLECT INTO pk_tab, fk_tab, fill_tab LIMIT 1000;
        EXIT WHEN pk_tab.COUNT() = 0;

        FORALL i IN pk_tab.FIRST .. pk_tab.LAST
           UPDATE test
            SET    fk = fk_tab(i)
            ,      fill = fill_tab(i)
            WHERE  pk = pk_tab(i);
    END LOOP;
    CLOSE rec_cur;
END;
/

Method 5: Updateable Join View

This is really a deprecated pre-9i method; the modern equivalent is the MERGE statement. This needs a unique index on TEST1.PK in order to enforce key preservation.
UPDATE (
        SELECT /*+ ordered use_nl(old)*/ new.pk as new_pk
        ,       new.fk as new_fk
        ,       new.fill as new_fill
        ,       old.*
        FROM test1 new
        JOIN test old ON (old.pk = new.pk)
)
SET fk = new_fk
,   fill = new_fill
/

Method 6: MERGE

The modern equivalent of the Updateable Join View. Gaining in popularity due to its combination of brevity and performance, it is primarily used to INSERT and UPDATE in a single statement. We are using the update-only version here. Note that I have included a FIRST_ROWS hint to force an indexed nested loops plan. This is to keep the playing field level when comparing to the other methods, which also perform primary key lookups on the target table. A Hash join may or may not be faster, that's not the point - I could increase the size of the target TEST table to 500M rows and Hash would be slower for sure.
MERGE /*+ FIRST_ROWS*/ INTO test
USING test2 new ON (test.pk = new.pk)
WHEN MATCHED THEN UPDATE SET
        fk = new.fk
,       fill = new.fill
/
Here is the Explain Plan
-------------------------------------------------------------------------------
| Id  | Operation                      | Name    | Rows  | Bytes | Cost (%CPU)|
-------------------------------------------------------------------------------
|   0 | MERGE STATEMENT                |         |   130K|  9921K|   258K  (1)|
|   1 |  MERGE                         | TEST    |       |       |            |
|   2 |   VIEW                         |         |       |       |            |
|   3 |    NESTED LOOPS                |         |   130K|    11M|   258K  (1)|
|   4 |     TABLE ACCESS FULL          | TEST2   |   128K|  6032K|   172   (5)|
|   5 |     TABLE ACCESS BY INDEX ROWID| TEST    |     1 |    48 |     2   (0)|
|   6 |      INDEX UNIQUE SCAN         | TEST_PK |     1 |       |     1   (0)|
-------------------------------------------------------------------------------

Method 7: Parallel DML MERGE

Now we're getting clever... This is the MERGE example on steroids. It uses Oracle's Parallel DML capability to spread the load over multiple slave threads.
ALTER SESSION ENABLE PARALLEL DML;

MERGE /*+ first_rows parallel(test) parallel(test2) */ INTO test
USING test5 new ON (test.pk = new.pk)
WHEN MATCHED THEN UPDATE SET
        fk = new.fk
,       fill = new.fill
/
Note the differences in the Explain Plan.
--------------------------------------------------------------------------------------------------------------------
| Id  | Operation                             | Name     | Rows  | Bytes | Cost (%CPU)|    TQ  |IN-OUT| PQ Distrib |
--------------------------------------------------------------------------------------------------------------------
|   0 | MERGE STATEMENT                       |          |   109K|  8325K|  1880   (1)|        |      |            |
|   1 |  PX COORDINATOR                       |          |       |       |            |        |      |            |
|   2 |   PX SEND QC (RANDOM)                 | :TQ10002 |   109K|    10M|  1880   (1)|  Q1,02 | P->S | QC (RAND)  |
|   3 |    INDEX MAINTENANCE                  | TEST     |       |       |            |  Q1,02 | PCWP |            |
|   4 |     PX RECEIVE                        |          |   109K|    10M|  1880   (1)|  Q1,02 | PCWP |            |
|   5 |      PX SEND RANGE                    | :TQ10001 |   109K|    10M|  1880   (1)|  Q1,01 | P->P | RANGE      |
|   6 |       MERGE                           | TEST     |       |       |            |  Q1,01 | PCWP |            |
|   7 |        PX RECEIVE                     |          |   109K|    10M|  1880   (1)|  Q1,01 | PCWP |            |
|   8 |         PX SEND HYBRID (ROWID PKEY)   | :TQ10000 |   109K|    10M|  1880   (1)|  Q1,00 | P->P | HYBRID (ROW|
|   9 |          VIEW                         |          |       |       |            |  Q1,00 | PCWP |            |
|  10 |           NESTED LOOPS                |          |   109K|    10M|  1880   (1)|  Q1,00 | PCWP |            |
|  11 |            PX BLOCK ITERATOR          |          |       |       |            |  Q1,00 | PCWC |            |
|  12 |             TABLE ACCESS FULL         | TEST2    |   107K|  5062K|     2   (0)|  Q1,00 | PCWP |            |
|  13 |            TABLE ACCESS BY INDEX ROWID| TEST     |     1 |    48 |     0   (0)|  Q1,00 | PCWP |            |
|  14 |             INDEX UNIQUE SCAN         | TEST_PK  |     1 |       |     0   (0)|  Q1,00 | PCWP |            |
--------------------------------------------------------------------------------------------------------------------

Method 8: Parallel PL/SQL

This is much easier to do with DataStage than with native PL/SQL. The goal is to have several separate sessions applying UPDATE statements at once, rather than using the sometimes restrictive PARALLEL DML alternative. It's a bit of a kludge, but we can do this in PL/SQL using a Parallel Enable Table Function. Here's the function:
CREATE OR REPLACE FUNCTION test_parallel_update (
 test_cur IN SYS_REFCURSOR
) 
RETURN test_num_arr
PARALLEL_ENABLE (PARTITION test_cur BY ANY)
PIPELINED
IS
 PRAGMA AUTONOMOUS_TRANSACTION;

 test_rec TEST%ROWTYPE;
 TYPE num_tab_t IS TABLE OF NUMBER(38);
 TYPE vc2_tab_t IS TABLE OF VARCHAR2(4000);

 pk_tab NUM_TAB_T;
 fk_tab NUM_TAB_T;
 fill_tab VC2_TAB_T;

 cnt INTEGER := 0;
BEGIN
 LOOP
  FETCH test_cur BULK COLLECT INTO pk_tab, fk_tab, fill_tab LIMIT 1000;
  EXIT WHEN pk_tab.COUNT() = 0;

  FORALL i IN pk_tab.FIRST .. pk_tab.LAST
   UPDATE test
   SET    fk = fk_tab(i)
   ,      fill = fill_tab(i)
   WHERE  pk = pk_tab(i);

  cnt := cnt + pk_tab.COUNT;
 END LOOP;

        CLOSE test_cur;

 COMMIT;
 PIPE ROW(cnt);
 RETURN;
END;
/
Note that it receives its data via a Ref Cursor parameter. This is a feature of Oracle's parallel-enabled functions; they will apportion the rows of a single Ref Cursor amongst many parallel slaves, with each slave running over a different subset of the input data set.
Here is the statement that calls the Parallel Enabled Table Function:
SELECT sum(column_value)
FROM   TABLE(test_parallel_update(CURSOR(SELECT * FROM test7)));
Note that we are using a SELECT statement to call a function that performs an UPDATE. Yeah, I know, it's nasty. You need to make the function an AUTONOMOUS TRANSACTION to stop it from throwing an error. But just bear with me, it is the closest PL/SQL equivalent I can make to a third-party ETL Tool such as DataStage with native parallelism.