Skip to Main Content
  • Questions
  • Coexistence of CAST/MULTISET operators and member functions?

Breadcrumb

Question and Answer

Tom Kyte

Thanks for the question, Art.

Asked: August 28, 2002 - 11:02 am UTC

Last updated: January 19, 2008 - 10:52 pm UTC

Version: 8.1.7

Viewed 10K+ times! This question is

You Asked

Good morning, Tom.

I am studying your book, in particular today chapter 20, "Using
Object Relational Features".

Is it possible to apply CAST and MULTISET operators to a nested table
collection whose object contains a member function or procedure? If
so, what is the syntax?

SQL> CREATE OR REPLACE TYPE myScalarType AS OBJECT (
2 ename VARCHAR2(10)
3 , sal NUMBER(7,2)
4 )
5 /

Type created.

SQL> CREATE OR REPLACE TYPE myTableType AS TABLE OF myScalarType;
2 /

Type created.

SQL> DECLARE
2 l_emp_sals myTableType;
3 BEGIN
4 SELECT CAST( MULTISET( SELECT e.ename
5 , e.sal
6 FROM emp e
7 ORDER BY e.ename )
8 AS myTableType )
9 INTO l_emp_sals
10 FROM DUAL;
11
12 FOR i IN 1..l_emp_sals.COUNT LOOP
13 DBMS_OUTPUT.PUT_LINE(l_emp_sals(i).ename || ' earns ' || l_emp_sals(i).sal);
14 END LOOP;
15 END;
16 /

ADAMS earns 1100
ALLEN earns 1600
BLAKE earns 2850
CLARK earns 2450
FORD earns 3000
JAMES earns 950
JONES earns 2975
KING earns 5000
MARTIN earns 1250
MILLER earns 1300
SCOTT earns 3000
SMITH earns 800
TURNER earns 1500
WARD earns 1250

PL/SQL procedure successfully completed.

SQL> DROP TYPE myScalarType FORCE;

Type dropped.

SQL> CREATE OR REPLACE TYPE myScalarType AS OBJECT (
2 ename VARCHAR2(10)
3 , sal NUMBER(7,2)
4 , MEMBER FUNCTION toString RETURN VARCHAR2
5 )
6 /

Type created.

SQL> CREATE OR REPLACE TYPE BODY myScalarType
2 AS
3 MEMBER FUNCTION toString RETURN VARCHAR2
4 IS
5 BEGIN
6 RETURN (SELF.ename || ' earns ' || SELF.sal );
7 END;
8 END;
9 /

Type body created.

SQL> DECLARE
2 l_emp_sals myTableType;
3 BEGIN
4 SELECT CAST( MULTISET( SELECT e.ename
5 , e.sal
6 FROM emp e
7 ORDER BY e.ename )
8 AS myTableType )
9 INTO l_emp_sals
10 FROM DUAL;
11 FOR i IN 1..l_emp_sals.COUNT LOOP
12 DBMS_OUTPUT.PUT_LINE(l_emp_sals(i).toString());
13 END LOOP;
14 END;
15 /

DECLARE
*
ERROR at line 1:
ORA-06550: line 1, column 21:
PLS-00570: different number of columns in the multiset and cast expressions
ORA-06550: line 4, column 5:
PL/SQL: SQL Statement ignored

Thanks, Tom.

Art.

and Tom said...

It is a PLSQL issue (fixed in 9i with the common sql parsers).

This shows it won't work statically but does dynamically -- a workaround until you get to 9i:

ops$tkyte@ORA817DEV.US.ORACLE.COM> DECLARE
2 l_emp_sals myTableType;
3 BEGIN
4 SELECT CAST( MULTISET( SELECT e.ename
5 , e.sal
6 FROM emp e
7 ORDER BY e.ename )
8 AS myTableType )
9 INTO l_emp_sals
10 FROM DUAL;
11
12 FOR i IN 1..l_emp_sals.COUNT LOOP
13 DBMS_OUTPUT.PUT_LINE(l_emp_sals(i).ename || ' earns ' || l_emp_sals(i).sal);
14 END LOOP;
15 END;
16 /
l_emp_sals myTableType;
*
ERROR at line 2:
ORA-06550: line 1, column 21:
PLS-00570: different number of columns in the multiset and cast expressions
ORA-06550: line 4, column 5:
PL/SQL: SQL Statement ignored


ops$tkyte@ORA817DEV.US.ORACLE.COM> DECLARE
2 l_emp_sals myTableType;
3 BEGIN
4 execute immediate
5 'SELECT CAST( MULTISET( SELECT e.ename
6 , e.sal
7 FROM emp e
8 ORDER BY e.ename )
9 AS myTableType )
10 FROM DUAL'
11 INTO l_emp_sals;
12
13 FOR i IN 1..l_emp_sals.COUNT LOOP
14 DBMS_OUTPUT.PUT_LINE(l_emp_sals(i).ename || ' earns ' || l_emp_sals(i).sal);
15 END LOOP;
16 END;
17 /
ADAMS earns 1100
ALLEN earns 1600
BLAKE earns 2850
CLARK earns 2450
FORD earns 3000
JAMES earns 950
JONES earns 2975
KING earns 5000
MARTIN earns 1250
MILLER earns 1300
SCOTT earns 3000
SMITH earns 800
TURNER earns 1500
WARD earns 1250

PL/SQL procedure successfully completed.

ops$tkyte@ORA817DEV.US.ORACLE.COM> spool off


Rating

  (7 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

CAST/MULTISET operators and member functions

rama, August 29, 2002 - 11:23 am UTC

Nice workaound, only why dont the manuals publish these linitations (like only dynamic method would work in 8. versions)???

Tom Kyte
August 29, 2002 - 8:11 pm UTC

They did not anticipate this.

You see - the plsql engine used to use (in 8i and before) its own sql parser. They write the manuals as they write plsql. Meanwhile, the server guys are off expanding SQL - but the plsql guys didn't know about it.

So, when it all comes together -- they had a mismatch in parser functionality. In the past, this wasn't so bad -- but as things move faster and faster and faster, this got bad in 8i -- so, in 9i, they use a common parser and exceptions like this just don't/won't happen anymore.



cast(multiset) v/s bulk collect

GMA, September 17, 2004 - 4:26 pm UTC

Very useful, as always.

I was wondering what the difference is between
(1) Doing a select and BULK COLLECTING into a collection
(2) Doing a select cast(multicast()) into a collection

It seems like the results are the same. Is there a performance difference or some other kind of difference?

create type gaelyn_test2
as object(org_id char(4), bk_id char(4), acct_id char(12));

create type gaelyn_test2_a as table of gaelyn_test2;

CREATE OR REPLACE procedure test_proc1
AS
           
      l_test_coll      gaelyn_test2_a;
      
      l_test_coll2     gaelyn_test2_a ;
      l_test_coll3     gaelyn_test2_a;
begin
     
     l_test_coll := gaelyn_test2_a();
     
     l_test_coll.extend(4);
     
     l_test_coll(1) := gaelyn_test2('ABC', '123', '100');
     l_test_coll(2) := gaelyn_test2('DEF', '456', '101');
     l_test_coll(3) := gaelyn_test2('GHI', '789', '102');
      l_test_coll(4) := gaelyn_test2('JKL', '111', '103');
              
      dbms_output.put_line('Test Coll - ' || l_test_coll.count);
    
    -- scenario (1)
    select gaelyn_test2(org_id, bk_id, acct_id)
    bulk collect into l_test_coll2
    from table(cast(l_test_coll as gaelyn_test2_a)) g ;
    
    dbms_output.put_line('TestColl_2 - ' || l_test_coll2.count);
    
    -- (scenario 2)
    select cast(multiset(SELECT * 
                            FROM table(cast(l_test_coll as gaelyn_test2_a)) G)
           AS gaelyn_test2_a)
    into l_test_coll3
    from dual;
    
    dbms_output.put_line('TestColl_3 - ' || l_test_coll2.count);    
     
end;
/

SQL> exec test_proc1
Test Coll - 4
TestColl_2 - 4
TestColl_3 - 4 

Tom Kyte
September 17, 2004 - 7:57 pm UTC

1) lets you use LIMIT, crucial if you have more than a couple of rows

2) does "select into" processing, not really relevant here.


I think 1) is more intuitive, flexible and readable.

thank you

A reader, September 20, 2004 - 9:46 am UTC


thank you

GMA, September 20, 2004 - 9:47 am UTC


Research into bulk collect

Jeffrey Robertson, October 23, 2005 - 10:55 pm UTC

This response clarified how to bulk collect into an object.

Casting to Package Declared Types

Marcus Z, April 17, 2007 - 6:36 pm UTC

In the test below I'm trying to cast a subquery to a package defined type vs. a database defined type. As it stands I can't get it to work. I end up getting an ORA-00902: invalid datatype error.

Could you explain why this is and whether or not there is a work around for this (besides creating the type in the database)? I have some limitations as a developer to where I'm not allowed to create database level types.

Setup
CREATE OR REPLACE TYPE t_string_ary2 AS TABLE OF VARCHAR2(200);
/

CREATE OR REPLACE PACKAGE FOO_PKG
AS
  TYPE t_string_ary IS TABLE OF VARCHAR2(200);

  --Takes a collection of varchars and returns it as a
  -- single string.
  FUNCTION implode ( p_ary t_string_ary, p_delim VARCHAR2 := ', ')
    RETURN VARCHAR2;

  FUNCTION implode2 ( p_ary t_string_ary2, p_delim VARCHAR2 := ', ')
    RETURN VARCHAR2;
END;
/
show errors

CREATE OR REPLACE PACKAGE BODY FOO_PKG
AS
    FUNCTION implode 
      ( p_ary t_string_ary, p_delim VARCHAR2 := ', ')
      RETURN VARCHAR2
    IS
      l_rtn_str  VARCHAR2(32000);
      l_element  VARCHAR2(80);
      l_index    NUMBER;
      l_length   PLS_INTEGER := 0;
      l_del_len  PLS_INTEGER := LENGTH(p_delim);
    BEGIN
      
      IF p_ary.FIRST IS NOT NULL THEN 
        FOR l_index IN p_ary.FIRST .. p_ary.LAST 
        LOOP
          l_element := p_ary(l_index);
          l_length  := l_length + l_del_len + LENGTH(NVL(l_element, ' '));
          IF l_length < 32000 THEN 
            l_rtn_str := l_rtn_str || p_delim || l_element;
          ELSE
            EXIT;
          END IF;
        END LOOP;
      END IF;
      
    IF ( l_rtn_str IS NOT NULL) THEN 
      l_rtn_str := SUBSTR(l_rtn_str, LENGTH(p_delim)+1);
    END IF;
    RETURN l_rtn_str;
    
  END;
  
    FUNCTION implode2 
      ( p_ary t_string_ary2, p_delim VARCHAR2 := ', ')
      RETURN VARCHAR2
    IS
      l_rtn_str  VARCHAR2(32000);
      l_element  VARCHAR2(80);
      l_index    NUMBER;
      l_length   PLS_INTEGER := 0;
      l_del_len  PLS_INTEGER := LENGTH(p_delim);
    BEGIN
      
      IF p_ary.FIRST IS NOT NULL THEN 
        FOR l_index IN p_ary.FIRST .. p_ary.LAST 
        LOOP
          l_element := p_ary(l_index);
          l_length  := l_length + l_del_len + LENGTH(NVL(l_element, ' '));
          IF l_length < 32000 THEN 
            l_rtn_str := l_rtn_str || p_delim || l_element;
          ELSE
            EXIT;
          END IF;
        END LOOP;
      END IF;
      
    IF ( l_rtn_str IS NOT NULL) THEN 
      l_rtn_str := SUBSTR(l_rtn_str, LENGTH(p_delim)+1);
    END IF;
    RETURN l_rtn_str;
    
  END;
END;
/
show errors


CREATE TABLE dept(
  dept_id  NUMBER,
  name     VARCHAR2(50)
);


CREATE TABLE emp(
  emp_id   NUMBER,
  fname    VARCHAR2(50),
  dept_id  NUMBER
);


INSERT INTO dept VALUES (1, 'Office 1');
INSERT INTO dept VALUES (2, 'Office 2');
INSERT INTO dept VALUES (3, 'Office 3');
INSERT INTO emp VALUES (1, 'Joe', 1);
INSERT INTO emp VALUES (2, 'Amy', 1);
INSERT INTO emp VALUES (3, 'Sue', 2);
INSERT INTO emp VALUES (4, 'Allen', 2);
INSERT INTO emp VALUES (5, 'Barbara', 3);



Tests

*********** TEST *********** Using database type
SELECT name, 
       foo_pkg.implode2( 
           CAST( 
               MULTISET(
                   SELECT fname 
                     FROM emp
                    WHERE dept_id = d.dept_id
                   ) AS t_string_ary2
           ) 
       ) "Employees"
FROM dept d;

NAME
--------------------------------------------------
Employees
------------------------------------------------------------
Office 1
Joe, Amy

Office 2
Sue, Allen

Office 3
Barbara



*********** TEST *********** Using package type
SELECT name, 
       foo_pkg.implode( 
           CAST( 
               MULTISET(
                   SELECT fname 
                     FROM emp
                    WHERE dept_id = d.dept_id
                   ) AS foo_pkg.t_string_ary
           ) 
       ) "Employees"
FROM dept d;

) AS foo_pkg.t_string_ary
*
ERROR at line 8:
ORA-00902: invalid datatype




*********** TEST *********** PL/SQL Test
DECLARE
  l_ary   foo_pkg.t_string_ary;
  l_index NUMBER;
BEGIN 
   SELECT fname 
     BULK COLLECT INTO l_ary
     FROM emp;
  
  dbms_output.put_line('Implode - '|| foo_pkg.implode(l_ary));
END;
/

Implode - Joe, Amy, Sue, Allen, Barbara


Thanks,

Marcus
Tom Kyte
April 18, 2007 - 11:49 am UTC

because sql only sees sql types - it does not, will not see plsql types.

Cast With Nested Calls

AVS, January 19, 2008 - 12:58 am UTC

Reading the example I got the usage of cast and multiset. But Can you let me know if I can use the nested cast function like:

For i IN (SELECT * FROM TABLE CAST(get_hier_classification
(CAST(get_classification(l_data_1, p_aggr_hier_id) as instrcatlgtbltype_tbl), p_aggr_hier_id, p_eff_dt)
AS Hierinstrcatlgtbltype_tbl))
loop
dbms_output.put_line (i.instr_dim_key);
End LOOP;


Tom Kyte
January 19, 2008 - 10:52 pm UTC

not knowing what you are trying to do, who knows...


can you cast a cast? sure

ops$tkyte%ORA10GR2> select cast( cast( 1 as varchar2(50) ) as number ) from dual;

CAST(CAST(1ASVARCHAR2(50))ASNUMBER)
-----------------------------------
                                  1

More to Explore

PL/SQL demos

Check out more PL/SQL tutorials on our LiveSQL tool.

PL/SQL docs

PL/SQL reference manual from the Oracle documentation library