Related Entries

Useful SQL*Plus headers
Dino eats frog
Oracle with Active Record
When is he free?
Gentle dive into analytic functions

« MSP2000 - User Interface
» What is the question?

Preventing duplicates in VARRAY columns

Today, I got an interesting question from a visitor to my site.

Question: “A table has a VARRAY column to store phone numbers. How can you create a trigger to ensure that no duplicate values are stored in that column?”

Generally, I tend not to use Oracle’s extensions - for portability. But this was an interesting question.

Here is the SQL*Plus transcript that illustrates a sample solution. It assumes Oracle’s sample schema (scott/tiger) is installed.

scott@vsb>REM assumes sample scott/tiger schema is installed
scott@vsb>
scott@vsb>CREATE OR REPLACE TYPE phone_type AS VARRAY(10) OF NUMBER(6);
  2  /

Type created.

scott@vsb>CREATE TABLE myemp AS SELECT empno, ename FROM emp;

Table created.

scott@vsb>ALTER TABLE myemp ADD phones PHONE_TYPE;

Table altered.

scott@vsb>DESC myemp;
 Name                                                  Null?    Type
 ----------------------------------------------------- -------- ------------------------------------
 EMPNO                                                 NOT NULL NUMBER(4)
 ENAME                                                          VARCHAR2(10)
 PHONES                                                         PHONE_TYPE

scott@vsb>COL phones FORMAT a30;
scott@vsb>SELECT * FROM myemp;

     EMPNO ENAME      PHONES
---------- ---------- ------------------------------
      7369 SMITH
      7499 ALLEN
      7521 WARD
      7566 JONES
      7654 MARTIN
      7698 BLAKE
      7782 CLARK
      7788 SCOTT
      7839 KING
      7844 TURNER
      7876 ADAMS
      7900 JAMES
      7902 FORD
      7934 MILLER

14 rows selected.

scott@vsb>
scott@vsb>UPDATE myemp SET phones=phone_type(123, 456) WHERE empno=7369;

1 row updated.

scott@vsb>SELECT * FROM myemp where empno=7369;

     EMPNO ENAME      PHONES
---------- ---------- ------------------------------
      7369 SMITH      PHONE_TYPE(123, 456)

scott@vsb>
scott@vsb>REM this is what we need to prevent, no duplicates
scott@vsb>UPDATE myemp SET phones=phone_type(123, 456, 123) WHERE empno=7369;

1 row updated.

scott@vsb>SELECT * FROM myemp where empno=7369;

     EMPNO ENAME      PHONES
---------- ---------- ------------------------------
      7369 SMITH      PHONE_TYPE(123, 456, 123)

scott@vsb>
scott@vsb>
scott@vsb>REM some examples on how to get VARRAYs into rows
scott@vsb>SELECT e.empno, p.column_value, COUNT(p.column_value)
  2  FROM myemp e, TABLE(e.phones) p
  3  WHERE e.empno=7369
  4  GROUP BY e.empno, p.column_value;

     EMPNO COLUMN_VALUE COUNT(P.COLUMN_VALUE)
---------- ------------ ---------------------
      7369          123                     2
      7369          456                     1

scott@vsb>
scott@vsb>SELECT e.empno, p.column_value, COUNT(p.column_value)
  2  FROM myemp e, TABLE(e.phones) p
  3  WHERE e.empno=7369
  4  GROUP BY e.empno, p.column_value
  5  HAVING COUNT(p.column_value) > 1;

     EMPNO COLUMN_VALUE COUNT(P.COLUMN_VALUE)
---------- ------------ ---------------------
      7369          123                     2

scott@vsb>
scott@vsb>REM set it to a proper value back
scott@vsb>UPDATE myemp SET phones=phone_type(123, 456) WHERE empno=7369;

1 row updated.

scott@vsb>
scott@vsb>SELECT * FROM myemp where empno=7369;

     EMPNO ENAME      PHONES
---------- ---------- ------------------------------
      7369 SMITH      PHONE_TYPE(123, 456)

scott@vsb>
scott@vsb>CREATE OR REPLACE TRIGGER myemp_upd_varray_trigger
  2  AFTER UPDATE ON myemp FOR EACH ROW
  3  DECLARE
  4  	 l_phones phone_type;
  5  BEGIN
  6  	 l_phones := :NEW.phones;
  7  	 --Now check if the array has duplicates
  8  	 --if at all the query returns something,
  9  	 --it indicates there is a duplicate
 10  	 --we don’t care which is the duplicate
 11  	 FOR x IN (
 12  	     SELECT p.COLUMN_VALUE, COUNT(p.COLUMN_VALUE)
 13  	     FROM DUAL d, TABLE(l_phones) p
 14  	     GROUP BY p.column_value
 15  	     HAVING COUNT(p.column_value) > 1) LOOP
 16  		 RAISE PROGRAM_ERROR;
 17  	 END LOOP;
 18  END;
 19  /

Trigger created.

scott@vsb>
scott@vsb>REM test the trigger; it should generate an error
scott@vsb>UPDATE myemp SET phones=phone_type(123, 456, 123) WHERE empno=7369;
UPDATE myemp SET phones=phone_type(123, 456, 123) WHERE empno=7369
       *
ERROR at line 1:
ORA-06501: PL/SQL: program error
ORA-06512: at "SCOTT.MYEMP_UPD_VARRAY_TRIGGER", line 14
ORA-04088: error during execution of trigger 'SCOTT.MYEMP_UPD_VARRAY_TRIGGER'


scott@vsb>
scott@vsb>REM create similar trigger for insert
scott@vsb>
scott@vsb>REM cleanup samples
scott@vsb>DROP TRIGGER myemp_upd_varray_trigger;

Trigger dropped.

scott@vsb>DROP TABLE myemp;

Table dropped.

scott@vsb>DROP TYPE phone_type;

Type dropped.

The SQL without the output from SQL*Plus is below.

I still don’t think storing collections as columns is in tune with relational database practice. It can cause proliferation of spreadsheet designs. Yet, it helps some times.

  1. I'm with you, I'd much rather normalise my data model and let simple referential integrity take care of it.

    But these features tend to be very popular with Java (or other OO) programmers who "just want to store my objects in the database".

    They can also be useful for quick and dirty solutions but the convenience of storing all of your data in one record is, I think, a bit of a false economy.

    Then again, I am an old database head so what do I know ;-)

    Posted by: Andy Todd on January 4, 2004 05:09 PM
//-->