It's All About ORACLE

Oracle - The number one Database Management System. Hope this Blog will teach a lot about oracle.

Modifying External Tables

Altering External Tables

You can use any of the ALTER TABLE clauses shown in Table 1 below to change the characteristics of an external table. No other clauses are permitted.

Table 1: ALTER TABLE Clauses for External Tables
ALTER TABLE ClauseDescriptionExample
REJECT LIMITChanges the reject limit
ALTER TABLE admin_ext_employees
   REJECT LIMIT 100;
PROJECT COLUMNDetermines how the access driver validates rows in subsequent queries:
  • PROJECT COLUMN REFERENCED: the access driver processes only the columns in the select list of the query. This setting may not provide a consistent set of rows when querying a different column list from the same external table. This is the default.
  • PROJECT COLUMN ALL: the access driver processes all of the columns defined on the external table. This setting always provides a consistent set of rows when querying an external table.
ALTER TABLE admin_ext_employees
   PROJECT COLUMN REFERNCED;

ALTER TABLE admin_ext_employees
   PROJECT COLUMN ALL;
DEFAULT DIRECTORYChanges the default directory specification
ALTER TABLE admin_ext_employees 
    DEFAULT DIRECTORY admin_dat2_dir;
ACCESS PARAMETERSAllows access parameters to be changed without dropping and re-creating the external table metadata
ALTER TABLE admin_ext_employees
    ACCESS PARAMETERS
       (FIELDS TERMINATED BY ';');
LOCATIONAllows data sources to be changed without dropping and re-creating the external table metadata
ALTER TABLE admin_ext_employees
   LOCATION ('empxt3.txt',
             'empxt4.txt');
PARALLELNo difference from regular tables. Allows degree of parallelism to be changed.No new syntax
ADD COLUMNNo difference from regular tables. Allows a column to be added to an external table. Virtual columns are not permitted.No new syntax
MODIFY COLUMNNo difference from regular tables. Allows an external table column to be modified. Virtual columns are not permitted.No new syntax
SET UNUSEDTransparently converted into an ALTER TABLE DROP COLUMN command. Because external tables consist of metadata only in the database, theDROP COLUMN command performs equivalently to the SET UNUSEDcommand.No new syntax
DROP COLUMNNo difference from regular tables. Allows an external table column to be dropped.No new syntax
RENAME TONo difference from regular tables. Allows external table to be renamed.No new syntax

Behavior Differences Between SQL*Loader and External Tables

This section describes important differences between loading data with external tables, using the ORACLE_LOADER access driver, as opposed to loading data with SQL*Loader conventional and direct path loads. This information does not apply to the ORACLE_DATAPUMP access driver.

Multiple Primary Input Datafiles

If there are multiple primary input datafiles with SQL*Loader loads, a bad file and a discard file are created for each input datafile. With external table loads, there is only one bad file and one discard file for all input datafiles. If parallel access drivers are used for the external table load, each access driver has its own bad file and discard file.

Syntax and Datatypes

The following are not supported with external table loads:
  • Use of CONTINUEIF or CONCATENATE to combine multiple physical records into a single logical record.
  • Loading of the following SQL*Loader datatypes: GRAPHICGRAPHIC EXTERNAL, and VARGRAPHIC
  • Use of the following database column types: LONGs, nested tables, VARRAYs, REFs, primary key REFs, and SIDs

Byte-Order Marks

With SQL*Loader, if a primary datafile uses a Unicode character set (UTF8 or UTF16) and it also contains a byte-order mark (BOM), then the byte-order mark is written at the beginning of the corresponding bad and discard files. With external table loads, the byte-order mark is not written at the beginning of the bad and discard files.

Default Character Sets, Date Masks, and Decimal Separator

For fields in a datafile, the settings of NLS environment variables on the client determine the default character set, date mask, and decimal separator. For fields in external tables, the database settings of the NLS parameters determine the default character set, date masks, and decimal separator.

Use of the Backslash Escape Character

In SQL*Loader, you can use the backslash (\) escape character to mark a single quotation mark as a single quotation mark, as follows:
FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '\''
In external tables, the use of the backslash escape character within a string will raise an error. The workaround is to use double quotation marks to mark the separation string, as follows:
TERMINATED BY ',' ENCLOSED BY "'"

DETERMINISTIC Function and Function Based Index on Deterministic Function

Exploring DETERMINISTIC Function and Function based indexes we found: 
  • Deterministic functions can be created on a function having DML command on it, however you cannot refer such function from SELECT SQL.
  • You can create Function Based Index on a Deterministic Function only. In other words a function must be deterministic so that Index could be created on it.
  • Once you create function based index on a deterministic function,  all possible value from that function is stored in Index and function body will not be executed at all.
  • Even if you have written DML in Deterministic function body that DML will execute only when you create function based index on that Deterministic Function and DML will not execute in any call to that function ever.
  • Even if you re-create function with different arithmetic for return value, referring it will give same result in SQL SELECT statement as of previous function return on which Index was created .
  • Deterministic function behave same as other functions when we called them in plsql code and not with a SELECT statement. Inside plsql block or Sql*Plus environment the DML body will execute even if it has index created on it.
  • Table used in SELECT query in Deterministic code and Table on which Function Based Index is created using that Deterministic Function should not be same. Else we will get ORA-04091: table is mutating, trigger/function may not see it error.
1. Index creation failed on Non-Deterministic Function:
SQL> CREATE OR REPLACE FUNCTION determinst_test(v_value NUMBER)
  2  RETURN NUMBER
  3  IS
  4  BEGIN
  5  RETURN v_value+1000;
  6  END;
  7  /

Function created.

SQL> CREATE INDEX deter_ind1 ON EMP2(determinst_test(sal));
CREATE INDEX deter_ind1 ON EMP2(determinst_test(sal))
                                *
ERROR at line 1:
ORA-30553: The function is not deterministic 

SQL> CREATE OR REPLACE FUNCTION determinst_test(v_value NUMBER)
 2  RETURN NUMBER DETERMINISTIC
 3  IS
 4  BEGIN
 5  RETURN v_value+1000;
 6  END;
 7  /

SQL> CREATE INDEX deter_ind1 ON EMP2(determinst_test(sal));

Index created.

2. DML inside a Deterministic function which is referred in SELECT query is acceptable if it has index created on it else it will through ORA-14551.

SQL> drop index DETER_IND1;

Index dropped.


SQL> CREATE OR REPLACE FUNCTION determinst_test(v_value NUMBER)
  2  RETURN NUMBER DETERMINISTIC
  3  IS
  4  BEGIN
  5   INSERT INTO DETER_INSRT
  6   VALUES (v_value, 'Value is '||v_value);
  7  RETURN v_value+1000;
  8  END;
  9  /

Function created.

SQL>  SELECT EMPNO, ENAME, sal, determinst_test(sal)
  2     FROM EMP2
  3     WHERE determinst_test(sal) >= 3000;
   WHERE determinst_test(sal) >= 3000
         *
ERROR at line 3:
ORA-14551: cannot perform a DML operation inside a query
ORA-06512: at "SCOTT.DETERMINST_TEST", line 5

SQL> CREATE INDEX  DETER_IND1 on EMP2(determinst_test(sal));

Index created.

SQL> SELECT EMPNO, ENAME, sal, determinst_test(sal)
  2    FROM EMP2
  3    WHERE determinst_test(sal) >= 3000;

     EMPNO ENAME             SAL DETERMINST_TEST(SAL)
---------- ---------- ---------- --------------------
      7698 BLAKE            2850                 3850
      7566 JONES            2975                 3975
      7788 SCOTT            3000                 4000
      7902 FORD             3000                 4000

3. Change in function return arithmetic will not give different result in SELECT SQL, however Sql*Plus call will make it behave as normal function call.

SQL> CREATE OR REPLACE FUNCTION determinst_test(v_value NUMBER)
  2  RETURN NUMBER DETERMINISTIC
  3  IS
  4  BEGIN
  5  RETURN v_value+5000;
  6  END;
  7  /

Function created.

SQL> SELECT EMPNO, ENAME, sal, determinst_test(sal)
  2    FROM EMP2
  3    WHERE determinst_test(sal) >= 3000;

     EMPNO ENAME             SAL DETERMINST_TEST(SAL)
---------- ---------- ---------- --------------------
      7698 BLAKE            2850                 3850
      7566 JONES            2975                 3975
      7788 SCOTT            3000                 4000
      7902 FORD             3000                 4000

SQL> EXEC :v_deter := determinst_test(300);

PL/SQL procedure successfully completed.

SQL> print v_deter;

   V_DETER
----------
      5300

4. DML inside Deterministic function is executed either when we create index on it or when we call it from plsql or sql*plus Exec command. DML is not executed when it is referred in SELECT.

SQL> SELECT * FROM DETER_INSRT;

no rows selected

SQL> CREATE INDEX  DETER_IND1 on EMP2(determinst_test(sal));

Index created.

SQL> SELECT * FROM DETER_INSRT;

        C1 C2
---------- --------------------
       300 Value is 300
       800 Value is 800
      1600 Value is 1600
      1250 Value is 1250
      2975 Value is 2975
      2850 Value is 2850
      3000 Value is 3000
      1500 Value is 1500
      1100 Value is 1100
       950 Value is 950

However DML executes if we call this function not from SELECT SQL.


SQL> EXEC :VAL := determinst_test(1000);

PL/SQL procedure successfully completed.

SQL> SELECT * FROM DETER_INSRT;
        C1 C2
---------- --------------------
       300 Value is 300
       800 Value is 800
      1600 Value is 1600
      1250 Value is 1250
      2975 Value is 2975
      2850 Value is 2850
      3000 Value is 3000
      1500 Value is 1500
      1100 Value is 1100
       950 Value is 950
      2000 Value is 2000

5. It has been observed that if we create a deterministic function and then a function based index using that DETERMINISTIC function, we cannot update values of table on which it has been referred. 
Conclusion: Table used in SELECT query in Deterministic code and Table on which Function Based Index is created using that Deterministic Function should not be same. 

We created following DETERMINISTIC function:
 CREATE OR REPLACE FUNCTION determinst_test(v_value NUMBER)
  RETURN NUMBER  DETERMINISTIC
  IS
 v_sal NUMBER;
 BEGIN
 SELECT SAL into v_sal FROM emp2 where empno=v_value;
  RETURN v_value+1000+v_sal;
  END;
 /

Then Index on table, using this deterministic function:

SQL>  CREATE INDEX  DETER_IND1 on EMP2(determinst_test(sal));

Index created.

Then we tried to update value in this table:

SQL> update emp2 set sal=sal+10000 where empno=7876;
update emp2 set sal=sal+10000 where empno=7876
*
ERROR at line 1:
ORA-04091: table SCOTT.EMP2 is mutating, trigger/function may not see it
ORA-06512: at "SCOTT.DETERMINST_TEST", line 6

Solution: If we drop the index or create the index on some different table then we can update the record.

DETERMINISTIC Functions

Problem - Searching for data records by a function result

There are different real life problems where you have to use a function in the WHERE statement of your query which will often slow it down to a level where it's not acceptable anymore.
Go through the script below and you'll probably understand in which situations you'd want to use a deterministic function.

Recipe #1 - Using a deterministic function to improve your query

First, a quick word about deterministic. A deterministic function always returns the same value if the input parameters are identical. 1+1 is always equal to 2 but a function called Get_Customer_Name(4711) won't return the same value because it fetches data from the database which changes.

Let's create an example including a custom function to show this possible improvement.
CREATE TABLE deterministic_test (column_a NUMBER, column_b VARCHAR2(100));

After you've created that table, use the query below to insert some example data. 100'000 records took about one second on my server, if you're running it on a slow computer you'll have to wait for a bit more.
INSERT INTO deterministic_test
SELECT ROWNUM N, DBMS_RANDOM.STRING ('X', 16) FROM dual  
CONNECT BY LEVEL <= 100000;

Now that we have our test able including some data in it, we have to create our test function. In this case we're going to create a simple function which will return the average letter of a string. For example: If you use AC it will return B since the average ASCII code of A and C is B. Probably not very useful but simple enough to show you the concept of deterministic functions. 
CREATE OR REPLACE FUNCTION Get_Average_Char(input_ VARCHAR2) RETURN VARCHAR2
IS
  i_   NUMBER;
  sum_ NUMBER := 0;
BEGIN
  FOR i_ IN 1..LENGTH(input_) LOOP
     sum_ := sum_ + ASCII(SUBSTR(input_,i_,1));
  END LOOP;
 
  RETURN CHR(sum_/LENGTH(input_));
END Get_Average_Char;

Now let's try to find all rows where the average character of column B is equal to G:
SELECT COUNT(*) FROM deterministic_test dt WHERE Get_Average_Char(column_b) = 'G';

Even on a fast computer this is going to take a while. Let's look at the explain plan:
DescriptionObject NameCostCardinalityBytes
SELECT STATEMENT, GOAL = ALL_ROWS 5661202
  SORT AGGREGATE  1202
    TABLE ACCESS FULLDETERMINISTIC_TEST566862174124

Let's create that function again but this time using the magic keyword DETERMINISTIC:
CREATE OR REPLACE FUNCTION Get_Average_Char(input_ VARCHAR2)
   RETURN VARCHAR2 DETERMINISTIC
IS
  i_   NUMBER;
  sum_ NUMBER := 0;
BEGIN
  FOR i_ IN 1..LENGTH(input_) LOOP
     sum_ := sum_ + ASCII(SUBSTR(input_,i_,1));
  END LOOP;
 
  RETURN CHR(sum_/LENGTH(input_));
END Get_Average_Char;

Now that we have a deterministic function we can tell our database to put an index on the result of that function. It's pretty straight forward - works exactly the same as it does for a normal table column:
CREATE INDEX DETERMINISTIC_TEST_IX1
   ON DETERMINISTIC_TEST (Get_Average_Char(column_b));

Run the same query from above again:
SELECT COUNT(*) FROM deterministic_test dt WHERE Get_Average_Char(column_b) = 'G';

The query returns in no time and when we look at the explain plan we can clearly see why:
DescriptionObject NameCostCardinalityBytes
SELECT STATEMENT, GOAL = ALL_ROWS 11202
  SORT AGGREGATE  1202
    INDEX RANGE SCANDETERMINISTIC_TEST_IX11862174124

We were able to replace the FULL SCAN with a much better INDEX RANGE SCAN and suddenly the cost dropped from 566 to 1. In this case Oracle doesn't have to execute the function at all after it created the index. It simply looks for the value in the index and returns the data records matching the requested value.

This works as well for built Oracle methods like UPPER when you want to create a case insensitive query on a database which is set up to do case sensitive matches.

It's not something you'll need on a daily basis but if there's a chance to add the keyword DETERMINISTIC, do it and you'll get a much better result in no time! 

MERGE Statement Enhancements in Oracle Database 10g

Oracle 10g includes a number of amendments to the MERGE statement making it more flexible.
  • Test Table
  • Optional Clauses
  • Conditional Operations
  • DELETE Clause

Test Table

The following examples use the table defined below.
CREATE TABLE test1 AS
SELECT *
FROM   all_objects
WHERE  1=2;

Optional Clauses

The MATCHED and NOT MATCHED clauses are now optional making all of the following examples valid.
-- Both clauses present.
MERGE INTO test1 a
  USING all_objects b
    ON (a.object_id = b.object_id)
  WHEN MATCHED THEN
    UPDATE SET a.status = b.status
  WHEN NOT MATCHED THEN
    INSERT (object_id, status)
    VALUES (b.object_id, b.status);

-- No matched clause, insert only.
MERGE INTO test1 a
  USING all_objects b
    ON (a.object_id = b.object_id)
  WHEN NOT MATCHED THEN
    INSERT (object_id, status)
    VALUES (b.object_id, b.status);

-- No not-matched clause, update only.
MERGE INTO test1 a
  USING all_objects b
    ON (a.object_id = b.object_id)
  WHEN MATCHED THEN
    UPDATE SET a.status = b.status;

Conditional Operations

Conditional inserts and updates are now possible by using a WHERE clause on these statements.
-- Both clauses present.
MERGE INTO test1 a
  USING all_objects b
    ON (a.object_id = b.object_id)
  WHEN MATCHED THEN
    UPDATE SET a.status = b.status
    WHERE  b.status != 'VALID'
  WHEN NOT MATCHED THEN
    INSERT (object_id, status)
    VALUES (b.object_id, b.status)
    WHERE  b.status != 'VALID';

-- No matched clause, insert only.
MERGE INTO test1 a
  USING all_objects b
    ON (a.object_id = b.object_id)
  WHEN NOT MATCHED THEN
    INSERT (object_id, status)
    VALUES (b.object_id, b.status)
    WHERE  b.status != 'VALID';

-- No not-matched clause, update only.
MERGE INTO test1 a
  USING all_objects b
    ON (a.object_id = b.object_id)
  WHEN MATCHED THEN
    UPDATE SET a.status = b.status
    WHERE  b.status != 'VALID';

DELETE Clause

An optional DELETE WHERE clause can be added to the MATCHED clause to clean up after a merge operation. Only those rows in the destination table that match both the ON clause and the DELETE WHERE are deleted. Depending on which table the DELETE WHERE references, it can target the rows prior or post update. The following examples clarify this.
Create a source table with 5 rows as follows.
CREATE TABLE source AS
SELECT level AS id,
       CASE
         WHEN MOD(level, 2) = 0 THEN 10
         ELSE 20
       END AS status,
       'Description of level ' || level AS description
FROM   dual
CONNECT BY level <= 5;

SELECT * FROM source;

        ID     STATUS DESCRIPTION
---------- ---------- -----------------------
         1         20 Description of level 1
         2         10 Description of level 2
         3         20 Description of level 3
         4         10 Description of level 4
         5         20 Description of level 5

5 rows selected.

SQL>
Create the destination table using a similar query, but this time with 10 rows.
CREATE TABLE destination AS
SELECT level AS id,
       CASE
         WHEN MOD(level, 2) = 0 THEN 10
         ELSE 20
       END AS status,
       'Description of level ' || level AS description
FROM   dual
CONNECT BY level <= 10;

SELECT * FROM destination;

         1         20 Description of level 1
         2         10 Description of level 2
         3         20 Description of level 3
         4         10 Description of level 4
         5         20 Description of level 5
         6         10 Description of level 6
         7         20 Description of level 7
         8         10 Description of level 8
         9         20 Description of level 9
        10         10 Description of level 10

10 rows selected.

SQL>
The following MERGE statement will update all the rows in the destination table that have a matching row in the source table. The additional DELETE WHERE clause will delete only those rows that were matched, already in the destination table, and meet the criteria of the DELETE WHERE clause.
MERGE INTO destination d
  USING source s
    ON (s.id = d.id)
  WHEN MATCHED THEN
    UPDATE SET d.description = 'Updated'
    DELETE WHERE d.status = 10;

5 rows merged.

SQL>

SELECT * FROM destination;

        ID     STATUS DESCRIPTION
---------- ---------- -----------------------
         1         20 Updated
         3         20 Updated
         5         20 Updated
         6         10 Description of level 6
         7         20 Description of level 7
         8         10 Description of level 8
         9         20 Description of level 9
        10         10 Description of level 10

8 rows selected.

SQL>
Notice there are rows with a status of "10" that were not deleted. This is because there was no match between the source and destination for these rows, so the delete was not applicable.
The following example shows the DELETE WHERE can be made to match against values of the rows before the update operation, not after. In this case, all matching rows have their status changed to "10", but the DELETE WHERE references the source data, so the status is checked against the source, not the updated values.
ROLLBACK;

MERGE INTO destination d
  USING source s
    ON (s.id = d.id)
  WHEN MATCHED THEN
    UPDATE SET  d.description = 'Updated',
                d.status = 10
    DELETE WHERE s.status = 10;

5 rows merged.

SQL>
  
SELECT * FROM destination;

        ID     STATUS DESCRIPTION
---------- ---------- -----------------------
         1         10 Updated
         3         10 Updated
         5         10 Updated
         6         10 Description of level 6
         7         20 Description of level 7
         8         10 Description of level 8
         9         20 Description of level 9
        10         10 Description of level 10

8 rows selected.

SQL>
Notice, no extra rows were deleted compared to the previous example.
By switching the DELETE WHERE to reference the destination table, the extra updated rows can be deleted also.

ROLLBACK;

MERGE INTO destination d
  USING source s
    ON (s.id = d.id)
  WHEN MATCHED THEN
    UPDATE SET  d.description = 'Updated',
                d.status = 10
    DELETE WHERE d.status = 10;

5 rows merged.

SQL>
  
SELECT * FROM destination;

        ID     STATUS DESCRIPTION
---------- ---------- -----------------------
         6         10 Description of level 6
         7         20 Description of level 7
         8         10 Description of level 8
         9         20 Description of level 9
        10         10 Description of level 10

5 rows selected.

SQL>

Invalid column type: 16

You might have encountered with Error: Invalid column type: 16 or java.sql.SQLException: Invalid column type: 16 when calling a procedure/function in in Java Environment. 

Cause of this Error:
We get this error while executing an oracle DB stored function which has return type as "BOOLEAN", in Java Environment:

CREATE OR REPLACE FUNCTION Check_Available_Money (v_card_no VARCHAR2, v_amount NUMBER)
  RETURN BOOLEAN
IS
  v_cur_Amount NUMBER(5);
BEGIN
  SELECT Balance into v_cur_Amount 
  FROM Card_Details
  WHERE cardno = v_card_no;

  IF v_cur_Amount> v_amount THEN
RETURN TRUE;
  ELSE
RETURN FALSE;
  END IF;
END;

/

Solution:
https://forums.oracle.com/message/10572948

BOOLEAN is a PL/SQL type and cannot be used in SQL.

You will need to write you own wrapper function that takes an integer and converts it to a boolean and calls your function.

Or you can use the 'sys.diutil.int_to_bool' which does that conversion.

BOOLEAN Data Type
The PL/SQL data type BOOLEAN stores logical values, which are the Boolean values TRUE and FALSE and the value NULL. NULL represents an unknown value.

Because SQL has no data type equivalent to BOOLEAN, you cannot:
* Assign a BOOLEAN value to a database table column
* Select or fetch the value of a database table column into a BOOLEAN variable
* Use a BOOLEAN value in a SQL statement, SQL function, or PL/SQL function invoked from a SQL statement.

According to Oracle's own FAQ, Oracle's JDBC driver doesn't support boolean datatypes sent/returned from the database. As the FAQ says the solution is:
* For example, to wrap a stored procedure that uses PL/SQL booleans, you can create a stored procedure that takes a character or number from JDBC and passes it to the original procedure as BOOLEAN, or, for an output parameter, accepts a BOOLEAN argument from the original procedure and passes it as a CHAR or NUMBER to JDBC. 
* Obviously if you can't change the database you've a problem with this raw JDBC approach. Maybe have a look to JPublisher to see what it can do for you in this case.

Workaround for such situation is:
Creating a another function that uses that and return String:

CREATE OR REPLACE FUNCTION Check_Withdraw_Amount(v_card_no VARCHAR2, v_amount NUMBER)
RETURN VARCHAR2
IS
Exist BOOLEAN;
BEGIN
 Exist := Check_Available_Money(v_card_no, v_amount);
 IF Exist THEN
 RETURN 'TRUE';
 ELSE 
 RETURN 'FALSE';
 END IF;
END;
/

Using Collections Methods

PL/SQL provides the built-in collection methods that make collections easier to use. The following table lists the methods and their purpose:
S.N.Method Name & Purpose
1EXISTS(n)
Returns TRUE if the nth element in a collection exists; otherwise returns FALSE.
2COUNT
Returns the number of elements that a collection currently contains.
3LIMIT
Checks the Maximum Size of a Collection.
4FIRST
Returns the first (smallest) index numbers in a collection that uses integer subscripts.
5LAST
Returns the last (largest) index numbers in a collection that uses integer subscripts.
6PRIOR(n)
Returns the index number that precedes index n in a collection.
7NEXT(n)
Returns the index number that succeeds index n.
8EXTEND
Appends one null element to a collection.
9EXTEND(n)
Appends n null elements to a collection.
10EXTEND(n,i)
Appends n copies of the ith element to a collection.
11TRIM
Removes one element from the end of a collection.
12TRIM(n)
Removes n elements from the end of a collection.
13DELETE
Removes all elements from a collection, setting COUNT to 0.
14DELETE(n)
Removes the nth element from an associative array with a numeric key or a nested table. If the associative array has a string key, the element corresponding to the key value is deleted. If n is null, DELETE(n) does nothing.
15DELETE(m,n)
Removes all elements in the range m..n from an associative array or nested table. If m is larger than n or if m or n is null, DELETE(m,n) does nothing.


A collection method is a built-in function or procedure that operates on collections and is called using dot notation.

Collection methods cannot be called from SQL statements.

EXTEND and TRIM cannot be used with associative arrays.

EXISTS, COUNT, LIMIT, FIRST, LAST, PRIOR, and NEXT are functions; EXTEND,
TRIM, and DELETE are procedures.

EXISTS, PRIOR, NEXT, TRIM, EXTEND, and DELETE take parameters corresponding to
collection subscripts, which are usually integers but can also be strings for associative arrays.

Only EXISTS can be applied to atomically null collections. If you apply another method to such collections, PL/SQL raises COLLECTION_IS_NULL.

Collection Exceptions

The following table provides the collection exceptions and when they are raised:
Collection ExceptionRaised in Situations
COLLECTION_IS_NULLYou try to operate on an atomically null collection.
NO_DATA_FOUNDA subscript designates an element that was deleted, or a nonexistent element of an associative array.
SUBSCRIPT_BEYOND_COUNTA subscript exceeds the number of elements in a collection.
SUBSCRIPT_OUTSIDE_LIMITA subscript is outside the allowed range.
VALUE_ERRORA subscript is null or not convertible to the key type. This exception might occur if the key is defined as a PLS_INTEGER range, and the subscript is outside this range.

Checking if collection Element Exists ( EXISTS Method)

EXISTS(n) returns TRUE if the nth element in a collection exists. Otherwise, EXISTS(n) returns FALSE. By combining EXISTS with DELETE, you can work with sparse nested tables. You can also use EXISTS to avoid referencing a nonexistent element, which raises an exception. When passed an out-of-range subscript, EXISTS returns FALSE instead of raising SUBSCRIPT_OUTSIDE_LIMIT Exception.

DECLARE
TYPE NumList IS TABLE OF INTEGER;
n NumList := NumList(1,3,5,7);
BEGIN
n.DELETE(2); -- Delete the second element
IF n.EXISTS(1) THEN
dbms_output.put_line('OK, element #1 exists.');
END IF;
IF n.EXISTS(2) = FALSE THEN
dbms_output.put_line('OK, element #2 has been deleted.');
END IF;
IF n.EXISTS(99) = FALSE THEN
dbms_output.put_line('OK, element #99 does not exist at all.');
END IF;
END;
/

Counting the elements in a collection ( COUNT method)

COUNT returns the number of elements that a collection currently contains:

DECLARE
TYPE NumList IS TABLE OF NUMBER;
n NumList := NumList(2,4,6,8); -- Collection starts with 4 elements.
BEGIN
dbms_output.put_line('There are ' || n.COUNT || ' elements in N.');
n.EXTEND(3); -- Add 3 new elements at the end.
dbms_output.put_line('Now there are ' || n.COUNT || ' elements in N.');
n := NumList(86,99); -- Assign a completely new value with 2 elements.
dbms_output.put_line('Now there are ' || n.COUNT || ' elements in N.');
n.TRIM(2); -- Remove the last 2 elements, leaving none.
dbms_output.put_line('Now there are ' || n.COUNT || ' elements in N.');
END;
/

 COUNT is useful because the current size of a collection is not always known. For example, you can fetch a column of Oracle data into a nested table, where the number of elements depends on the size of the result set.

 For varrays, COUNT always equals LAST. You can increase or decrease the size of a varray using the EXTEND and TRIM methods, so the value of COUNT can change, up to the value of the LIMIT method.

 For nested tables, COUNT normally equals LAST. But, if you delete elements from the middle of a nested table, COUNT becomes smaller than LAST. When tallying elements, COUNT ignores deleted elements.

Checking the maximum size of  a Collection ( LIMIT method)


For nested tables and associative arrays, which have no maximum size, LIMIT returns NULL. For varrays, LIMIT returns the maximum number of elements that a varray can contain. You specify this limit in the type definition, and can change it later with the TRIM and EXTEND methods. For instance, if the maximum size of varray PROJECTS is 25 elements, the following IF condition is true:

DECLARE
TYPE Colors IS VARRAY(7) OF VARCHAR2(64);
c Colors := Colors('Gold','Silver');
BEGIN
dbms_output.put_line('C has ' || c.COUNT || ' elements now.');
dbms_output.put_line('C''s type can hold a maximum of ' || c.LIMIT || '
elements.');
dbms_output.put_line('The maximum number you can use with C.EXTEND() is ' ||
(c.LIMIT - c.COUNT));
END;
/

Finding the First or Last collection Element ( FIRST and LAST Methods)

FIRST and LAST return the first and last (smallest and largest) index numbers in a collection that uses integer subscripts.
For an associative array with VARCHAR2 key values, the lowest and highest key values are returned. By default, the order is based on the binary values of the characters in the string. If the NLS_COMP initialization parameter is set to ANSI, the order is based on the locale-specific sort order specified by the NLS_SORT initialization parameter. 

If the collection is empty, FIRST and LAST return NULL.
If the collection contains only one element, FIRST and LAST return the same index value.

The following example shows how to use FIRST and LAST to iterate through the elements in a collection that has consecutive subscripts:

DECLARE
TYPE NumList IS TABLE OF NUMBER;
n NumList := NumList(1,3,5,7);
counter INTEGER;
BEGIN
dbms_output.put_line('N''s first subscript is ' || n.FIRST);
dbms_output.put_line('N''s last subscript is ' || n.LAST);
-- When the subscripts are consecutive starting at 1, it's simple to loop through
them.
FOR i IN n.FIRST .. n.LAST
LOOP
dbms_output.put_line('Element #' || i || ' = ' || n(i));
END LOOP;
n.DELETE(2); -- Delete second element.
-- When the subscripts have gaps or the collection might be uninitialized,
-- the loop logic is more extensive. We start at the first element, and
-- keep looking for the next element until there are no more.
IF n IS NOT NULL THEN
counter := n.FIRST;
WHILE counter IS NOT NULL
LOOP
dbms_output.put_line('Element #' || counter || ' = ' || n(counter));
counter := n.NEXT(counter);
END LOOP;
ELSE
dbms_output.put_line('N is null, nothing to do.');
END IF;
END;
/

For varrays, FIRST always returns 1 and LAST always equals COUNT. 
For nested tables, normally FIRST returns 1 and LAST equals COUNT. But if you delete elements from the beginning of a nested table, FIRST returns a number larger than 1.
If you delete elements from the middle of a nested table, LAST becomes larger than COUNT.
When scanning elements, FIRST and LAST ignore deleted elements.

Looping Through Collection Elements (PRIOR and NEXT Methods)

PRIOR(n) returns the index number that precedes index n in a collection. NEXT(n) returns the index number that succeeds index n. If n has no predecessor, PRIOR(n) returns NULL. If n has no successor, NEXT(n) returns NULL.

For associative arrays with VARCHAR2 keys, these methods return the appropriate key value; ordering is based on the binary values of the characters in the string, unless the NLS_COMP initialization parameter is set to ANSI, in which case the ordering is based on the locale-specific sort order specified by the NLS_SORT initialization parameter.

These methods are more reliable than looping through a fixed set of subscript values, because elements might be inserted or deleted from the collection during the loop.
This is especially true for associative arrays, where the subscripts might not be in consecutive order and so the sequence of subscripts might be (1,2,4,8,16) or ('A','E','I','O','U').

DECLARE
TYPE NumList IS TABLE OF NUMBER;
n NumList := NumList(1966,1971,1984,1989,1999);
BEGIN
dbms_output.put_line('The element after #2 is #' || n.NEXT(2));
dbms_output.put_line('The element before #2 is #' || n.PRIOR(2));
n.DELETE(3); -- Delete an element to show how NEXT can handle gaps.
dbms_output.put_line('Now the element after #2 is #' || n.NEXT(2));
IF n.PRIOR(n.FIRST) IS NULL THEN
dbms_output.put_line('Can''t get PRIOR of the first element or NEXT of the
last.');
END IF;
END;

/

o/p:

The element after #2 is #3
The element before #2 is #1
Now the element after #2 is #4
Can't get PRIOR of the first element or NEXT of the last.


PL/SQL procedure successfully completed.

You can use PRIOR or NEXT to traverse collections indexed by any series of subscripts. The following example uses NEXT to traverse a nested table from which some elements have been deleted:

DECLARE
TYPE NumList IS TABLE OF NUMBER;
n NumList := NumList(1,3,5,7);
counter INTEGER;
BEGIN
n.DELETE(2); -- Delete second element.
-- When the subscripts have gaps, the loop logic is more extensive. We start at
the
-- first element, and keep looking for the next element until there are no more.

counter := n.FIRST;
WHILE counter IS NOT NULL
LOOP
dbms_output.put_line('Counting up: Element #' || counter || ' = ' ||
n(counter));
counter := n.NEXT(counter);
END LOOP;
-- Run the same loop in reverse order.
counter := n.LAST;
WHILE counter IS NOT NULL
LOOP
dbms_output.put_line('Counting down: Element #' || counter || ' = ' ||
n(counter));
counter := n.PRIOR(counter);
END LOOP;
END;
/

When traversing elements, PRIOR and NEXT skip over deleted elements.

Increasing the Size of a Collection (EXTEND Method)

To increase the size of a nested table or varray, use EXTEND.

You cannot use EXTEND with index-by tables.
This procedure has three forms:
■ EXTEND appends one null element to a collection.
■ EXTEND(n) appends n null elements to a collection.
■ EXTEND(n,i) appends n copies of the ith element to a collection.

You cannot use EXTEND to add elements to an uninitialized.
If you impose the NOT NULL constraint on a TABLE or VARRAY type, you cannot apply the first two forms of EXTEND to collections of that type.

EXTEND operates on the internal size of a collection, which includes any deleted elements. If EXTEND encounters deleted elements, it includes them in its tally. PL/SQL keeps placeholders for deleted elements so that you can re-create them by assigning new values.

DECLARE
TYPE NumList IS TABLE OF INTEGER;
n NumList := NumList(2,4,6,8);
x NumList := NumList(1,3);
PROCEDURE print_numlist(the_list NumList) IS
output VARCHAR2(128);
BEGIN
FOR i IN the_list.FIRST .. the_list.LAST
LOOP
output := output || NVL(TO_CHAR(the_list(i)),'NULL') || ' ';
END LOOP;
dbms_output.put_line(output);
END;
BEGIN
dbms_output.put_line('At first, N has ' || n.COUNT || ' elements.');
n.EXTEND(5); -- Add 5 elements at the end.
dbms_output.put_line('Now N has ' || n.COUNT || ' elements.');
-- Elements 5, 6, 7, 8, and 9 are all NULL.
print_numlist(n);
dbms_output.put_line('At first, X has ' || x.COUNT || ' elements.');
x.EXTEND(4,2); -- Add 4 elements at the end.
dbms_output.put_line('Now X has ' || x.COUNT || ' elements.');
-- Elements 3, 4, 5, and 6 are copies of element #2.
print_numlist(x);
END;
/

When it includes deleted elements, the internal size of a nested table differs from the values returned by COUNT and LAST. For instance, if you initialize a nested table with five elements, then delete elements 2 and 5, the internal size is 5, COUNT returns 3, and LAST returns 4. All deleted elements, regardless of position, are treated alike.

Decreasing the Size of a Collection (TRIM Method)

This procedure has two forms:
■ TRIM removes one element from the end of a collection.
■ TRIM(n) removes n elements from the end of a collection.

DECLARE
TYPE NumList IS TABLE OF NUMBER;
n NumList := NumList(1,2,3,5,7,11);
PROCEDURE print_numlist(the_list NumList) IS
output VARCHAR2(128);
BEGIN
IF n.COUNT = 0 THEN
dbms_output.put_line('No elements in collection.');
ELSE
FOR i IN the_list.FIRST .. the_list.LAST
LOOP
output := output || NVL(TO_CHAR(the_list(i)),'NULL') || ' ';
END LOOP;
dbms_output.put_line(output);
END IF;
END;
BEGIN
print_numlist(n);
n.TRIM(2); -- Remove last 2 elements.
print_numlist(n);
n.TRIM; -- Remove last element.
print_numlist(n);
n.TRIM(n.COUNT); -- Remove all remaining elements.
print_numlist(n);
-- If too many elements are specified, TRIM raises the exception SUBSCRIPT_BEYOND_
COUNT.
BEGIN
n := NumList(1,2,3);
n.TRIM(100);
EXCEPTION
WHEN SUBSCRIPT_BEYOND_COUNT THEN
dbms_output.put_line('I guess there weren''t 100 elements that could be trimmed.');
END;
-- When elements are removed by DELETE, placeholders are left behind. TRIM counts
these
-- placeholders as it removes elements from the end.
n := NumList(1,2,3,4);
n.DELETE(3); -- delete element 3
-- At this point, n contains elements (1,2,4).
-- TRIMming the last 2 elements removes the 4 and the placeholder, not 4 and 2.
n.TRIM(2);
print_numlist(n);
END;
/
END;
/

If n is too large, TRIM(n) raises SUBSCRIPT_BEYOND_COUNT.
TRIM operates on the internal size of a collection. If TRIM encounters deleted elements, it includes them in its tally. Consider the following example:

DECLARE
TYPE CourseList IS TABLE OF VARCHAR2(10);
courses CourseList;
BEGIN
courses := CourseList('Biol 4412', 'Psyc 3112', 'Anth 3001');
courses.DELETE(courses.LAST); -- delete element 3
/* At this point, COUNT equals 2, the number of valid
elements remaining. So, you might expect the next
statement to empty the nested table by trimming
elements 1 and 2. Instead, it trims valid element 2
and deleted element 3 because TRIM includes deleted
elements in its tally. */
courses.TRIM(courses.COUNT);
dbms_output.put_line(courses(1)); -- prints 'Biol 4412'
END;
/
In general, do not depend on the interaction between TRIM and DELETE. It is better to treat nested tables like fixed-size arrays and use only DELETE, or to treat them like stacks and use only TRIM and EXTEND.
Because PL/SQL does not keep placeholders for trimmed elements, you cannot replace a trimmed element simply by assigning it a new value.

Deleting Collection Elements (DELETE Method)

This procedure has various forms:
■ DELETE removes all elements from a collection.
■ DELETE(n) removes the nth element from an associative array with a numeric key or a nested table. If the associative array has a string key, the element corresponding to the key value is deleted. If n is null, DELETE(n) does nothing.
■ DELETE(m,n) removes all elements in the range m..n from an associative array or nested table. If m is larger than n or if m or n is null, DELETE(m,n) does nothing.

For example:
DECLARE
TYPE NumList IS TABLE OF NUMBER;
n NumList := NumList(10,20,30,40,50,60,70,80,90,100);
TYPE NickList IS TABLE OF VARCHAR2(64) INDEX BY VARCHAR2(32);
nicknames NickList;
BEGIN
n.DELETE(2); -- deletes element 2
n.DELETE(3,6); -- deletes elements 3 through 6
n.DELETE(7,7); -- deletes element 7
n.DELETE(6,3); -- does nothing since 6 > 3
n.DELETE; -- deletes all elements
nicknames('Bob') := 'Robert';
nicknames('Buffy') := 'Esmerelda';
nicknames('Chip') := 'Charles';
nicknames('Dan') := 'Daniel';
nicknames('Fluffy') := 'Ernestina';
nicknames('Rob') := 'Robert';
nicknames.DELETE('Chip'); -- deletes element denoted by this key
nicknames.DELETE('Buffy','Fluffy'); -- deletes elements with keys in this
alphabetic range
END;
/

Varrays always have consecutive subscripts, so you cannot delete individual elements except from the end (by using the TRIM method).
If an element to be deleted does not exist, DELETE simply skips it; no exception is raised. PL/SQL keeps placeholders for deleted elements, so you can replace a deleted element by assigning it a new value.
DELETE lets you maintain sparse nested tables. You can store sparse nested tables in the database, just like any other nested tables.
The amount of memory allocated to a nested table can increase or decrease dynamically. As you delete elements, memory is freed page by page. If you delete the entire table, all the memory is freed.

You Might Also Like

Related Posts with Thumbnails

Pages