Not able to compile PL/SQL with BULK COLLECT and FORALL - oracle

I am getting below error while creating this procedure.
CREATE OR replace PROCEDURE Remove_sv_duplicate
IS
TYPE sv_bulk_collect
IS TABLE OF tt%ROWTYPE;
sv_rec SV_BULK_COLLECT;
CURSOR cur_data IS
SELECT *
FROM tt
WHERE ROWID IN (SELECT ROWID
FROM (SELECT ROWID,
Row_number () over (PARTITION BY portingtn,
nnsp
, onsp,
spid,
Trunc(
createddate,
'MI') ORDER BY portingtn) dup
FROM tt)
WHERE dup > 1);
BEGIN
OPEN cur_data;
LOOP
FETCH cur_data BULK COLLECT INTO sv_rec LIMIT 1000;
FORALL i IN 1..sv_rec.COUNT
INSERT INTO soa_temp_sv_refkey_fordelete
(referencekey,
spid,
nnsp,
onsp,
portingtn)
(SELECT referencekey,
spid,
nnsp,
onsp,
portingtn
FROM tt
WHERE portingtn = Sv_rec(i).portingtn
AND spid = Sv_rec(i).spid
AND nnsp = Sv_rec(i).nnsp
AND onsp = Sv_rec(i).onsp
AND svid IS NULL);
EXIT WHEN cur_data%notfound;
END LOOP;
CLOSE cur_data;
COMMIT;
END;
Procedure
Error(23,5): PL/SQL: SQL Statement ignored
Error(25,27): PLS-00382: expression is of wrong type
Error(25,27): PLS-00436: implementation restriction: cannot reference fields of BULK In-BIND table of records
Error(26,27): PLS-00436: implementation restriction: cannot reference fields of BULK In-BIND table of records
Error(26,27): PLS-00382: expression is of wrong type
Error(27,27): PLS-00436: implementation restriction: cannot reference fields of BULK In-BIND table of records
Error(27,27): PLS-00382: expression is of wrong type
Error(28,27): PL/SQL: ORA-22806: not an object or REF
Error(28,27): PLS-00382: expression is of wrong type
Error(28,27): PLS-00436: implementation restriction: cannot reference fields of BULK In-BIND table of records

You cannot* reference individual fields when you're using FORALL - that's why you get the PLS-00436 error.
To get around this, you will have to make use of associative arrays to refer individual
fields.
DECLARE
TYPE tt_rectype IS RECORD (
referencekey tt.referencekey%TYPE,
spid tt.spid%TYPE,
nnsp tt.hiredate%TYPE,
onsp tt.deptno%TYPE,
portingtn tt.portingtn%TYPE);
TYPE tt_aa_type
IS TABLE OF TT_RECTYPE INDEX BY PLS_INTEGER;
tt_aa TT_AA_TYPE;
CURSOR cur_data IS
SELECT *
FROM tt
WHERE ROWID IN (SELECT ROWID
FROM (SELECT ROWID,
Row_number () over (PARTITION BY portingtn
,
nnsp
, onsp,
spid,
Trunc(
createddate
, 'MI') ORDER BY portingtn) dup
FROM tt)
WHERE dup > 1);
BEGIN
OPEN cur_data;
LOOP
FETCH cur_data BULK COLLECT INTO tt_aa LIMIT 1000;
FORALL i IN 1..tt_aa.COUNT
INSERT INTO soa_temp_sv_refkey_fordelete
(referencekey,
spid,
nnsp,
onsp,
portingtn)
(SELECT referencekey,
spid,
nnsp,
onsp,
portingtn
FROM tt
WHERE portingtn = Tt_aa(i).portingtn
AND spid = Tt_aa(i).spid
AND nnsp = Tt_aa(i).nnsp
AND onsp = Tt_aa(i).onsp
AND svid IS NULL);
EXIT WHEN cur_data%notfound;
END LOOP;
CLOSE cur_data;
COMMIT;
END;
*note this limitation is no longer present in Oracle 11g+
In addition, as #jonearles comments, you could just use a single SQL statement....
INSERT INTO soa_temp_sv_refkey_fordelete
(referencekey,
spid,
nnsp,
onsp,
portingtn)
SELECT referencekey,
spid,
nnsp,
onsp,
portingtn
FROM tt
WHERE ROWID IN (SELECT ROWID
FROM (SELECT ROWID,
Row_number () over (PARTITION BY portingtn, nnsp
, onsp,
spid,
Trunc(
createddate
, 'MI') ORDER BY portingtn) dup
FROM tt)
WHERE dup > 1);

Related

SQL statement ignored for a index nested loop join

I'm trying to build a data warehouse based on a star schema with 5 dimension tables and 1 facts table using two sets of data, MASTERDATA which holds 100 records and DATASTREAM which holds 10,000 records.
I am reading 100 records from DATASTREAM as an input into a cursor then reading the cursor record by record and then retrieving the relevant records from MASTERDATA on the index product_id as a index nested loop join. After this I am loading the new attributes from the transaction tuple inside the relevant dimension and fact tables.
However, I have a few errors. I'm just looking for help to understand why I am getting the errors I am getting. The errors at the moment are:
Error(98,6):PL/SQL:SQL Statement Ignored
Error(101,5):PL/SQL: ORA-00933: SQL command not properly ended
Error(105,8):PLS-00103:Encountered the symbol "LOOP" when expecting one of the following: if
Error(113):PLS-00103:Encountered the symbol "end-of-file" when expecting one of the following: ;
My code:
CREATE OR REPLACE PROCEDURE transactionINLJ AS TYPE t_cursor is ref cursor;
v_cursor t_cursor;
v_cursor_records DATASTREAM%rowtype;
record_100 varchar2(300);
rec number;
v_customer_id masterdata.customer_id%type;
v_customer_account_type masterdata.customer_account_type%type;
v_product_id masterdata.product_id%type;
v_product_name masterdata.product_name%type;
v_supplier_id masterdata.supplier_id%type;
v_supplier_name masterdata.supplier_name%type;
v_outlet_id masterdata.outlet_id%type;
v_outlet_name masterdata.outlet_name%type;
v_sale_price masterdata.sale_price%type;
t_customer_id int;
t_supplier_id int;
t_product_id int;
t_outlet_id int;
t_date_id int;
t_sales_fact int;
BEGIN
rec := 1;
WHILE (rec <= 10000)
LOOP
record_100 := 'SELECT * FROM datastream WHERE datastream_id between '|| TO_CHAR(rec) ||
' and ' || TO_CHAR(rec+99);
OPEN v_cursor FOR record_100;
LOOP
FETCH v_cursor INTO v_cursor_records;
EXIT WHEN v_cursor%notfound;
SELECT product_id, product_name, supplier_id, supplier_name, sale_price
INTO v_product_id, v_product_name, v_supplier_id, v_supplier_name, v_sale_price
FROM masterdata
WHERE product_id = v_cursor_records.product_id;
SELECT COUNT(0)
INTO t_product_id
FROM product_dim
WHERE product_id = v_cursor_records.product_id;
IF t_product_id = 0 THEN
INSERT INTO product_dim(product_id, product_name)
VALUES (v_cursor_records.product_id, v_cursor_records.product_name);
END IF;
SELECT COUNT(0)
INTO t_customer_id
FROM customer_dim
WHERE customer_id = v_cursor_records.customer_id;
IF t_customer_id = 0 THEN
INSERT INTO customer_dim(customer_id, customer_name,customer_account_type)
VALUES (v_cursor_records.customer_name, v_cursor_records.customer_account_type, v_cursor_records.customer_account_type);
END IF;
SELECT COUNT(0)
INTO t_supplier_id
FROM supplier_dim
WHERE supplier_id = v_cursor_records.supplier_id;
IF t_supplier_id = 0 THEN
INSERT INTO supplier_dim(supplier_id, supplier_name)
VALUES (v_cursor_records.supplier_id, v_cursor_records.supplier_name);
END IF;
SELECT COUNT(0)
INTO t_outlet_id
FROM outlet_dim
WHERE outlet_id = v_cursor_records.outlet_id;
IF t_outlet_id = 0 THEN
INSERT INTO outlet_dim(outlet_id, outlet_name)
VALUES (v_cursor_records.outlet_id, v_cursor_records.outlet_name);
END IF;
SELECT COUNT(0)
INTO t_date_id
FROM date_dim
WHERE d_date = v_cursor_records.d_date;
IF t_date_id = 0 THEN
INSERT INTO date_dim(d_date, d_year, d_quater, d_month, d_day)
VALUES (v_cursor_records.d_date
,EXTRACT(year FROM v_cursor_records.d_date), TO_CHAR(v_cursor_records.d_date,'Q')
,EXTRACT(month FROM v_cursor_records.d_date)
,EXTRACT(day FROM v_cursor_records.d_date));
END IF;
SELECT COUNT(0)
INTO t_sales_fact
FROM sales_fact
WHERE product_id = v_cursor_records.product_id
AND customer_id = v_csr_rec.customer_id
AND supplier_id = v_csr_rec.supplier_id
AND outlet_id = v_csr_rec.outlet_id
AND d_date = v_csr_rec.d_date
AND sale_price = v_csr_rec.sale_price
AND quantity_sold = v_csr_rec.quantity_sold;
IF t_sales_fact = 0 THEN
INSERT INTO sales_fact(customer_id,product_id,outlet_id,supplier_id,d_date,sale_price,total_sale,quantity_sold)
VALUES (v_cursor_records.customer_id, v_cursor_records.product_id, v_cursor_records.outlet_id,v_cursor_records.supplier_id,
v_cursor_records.d_date, v_cursor_records.sale_price, v_cursor_records.quantity_sold*sale_price, v_cursor_records.quantity_sold)
END IF;
COMMIT;
END LOOP;
CLOSE v_cursor;
COMMIT;
rec := rec+100;
END LOOP;
END;
It is an unfortunate truth that occasionally procedural processing is required. But almost all this can be done with just SQL and a tiny bit of PL/SQL extensions. In particular there is no need to "select count..." for any of your target tables, sql handles that quite easily on the INSERT statement itself. Further there is no need to loop through a cursor on a row-by-row (aka slow-by-slow) process, instead use BULK COLLECT and FORALL to handle the entire array (100 rows in this case) all with a single INSERT for each table. With it there is no need to for loop control counters, nor calculating the ID numbers to retrieve, nor the exact the number of rows (what would happen if your source table contained 10050 or 9950 rows instead of exactly 10000). As a side effect you gain considerable performance. The following shows that process:
create or replace procedure transactioninlj as
k_bulk_buffer_size constant integer := 100;
cursor v_cursor is
select d.customer_id
, d.outlet_id
, d.outlet_name
, d.customer_name
, d.customer_account_type
, d.d_date
, d.quantity_sold
, m.product_id
, m.product_name
, m.supplier_id
, m.supplier_name
, m.sale_price
from datastream d
join masterdata m on m.product_id = d.product_id
;
type t_cursor_records is table of v_cursor%rowtype;
v_cursor_records t_cursor_records;
begin
open v_cursor;
loop
fetch v_cursor
bulk collect
into v_cursor_records
limit k_bulk_buffer_size;
forall v_index in 1 .. v_cursor_records.count
insert into product_dim(product_id, product_name)
select v_cursor_records(v_index).product_id
, v_cursor_records(v_index).product_name
from dual
where not exists
( select null
from product_dim
where product_id = v_cursor_records(v_index).product_id
);
forall v_index in 1 .. v_cursor_records.count
insert into supplier_dim(supplier_id, supplier_name)
select v_cursor_records(v_index).supplier_id
, v_cursor_records(v_index).supplier_name
from dual
where not exists
( select null
from supplier_dim
where supplier_id = v_cursor_records(v_index).supplier_id
);
forall v_index in 1 .. v_cursor_records.count
insert into customer_dim(customer_id, customer_name,customer_account_type)
select v_cursor_records(v_index).customer_id
, v_cursor_records(v_index).customer_name
, v_cursor_records(v_index).customer_account_type
from dual
where not exists
( select null
from customer_dim
where customer_id = v_cursor_records(v_index).customer_id
);
forall v_index in 1 .. v_cursor_records.count
insert into outlet_dim(outlet_id, outlet_name)
select v_cursor_records(v_index).outlet_id
, v_cursor_records(v_index).outlet_name
from dual
where not exists
( select null
from outlet_dim
where outlet_id = v_cursor_records(v_index).outlet_id
);
forall v_index in 1 .. v_cursor_records.count
insert into date_dim(d_date, d_year, d_quater, d_month, d_day)
select v_cursor_records(v_index).d_date
, extract(year from v_cursor_records(v_index).d_date)
, to_char(v_cursor_records(v_index).d_date,'Q')
, extract(month from v_cursor_records(v_index).d_date)
, extract(day from v_cursor_records(v_index).d_date)
from dual
where not exists
( select null
from outlet_dim
where outlet_id = v_cursor_records(v_index).outlet_id
);
forall v_index in 1 .. v_cursor_records.count
insert into sales_fact( customer_id
, product_id
, outlet_id
, supplier_id
, d_date
, sale_price
, total_sale
, quantity_sold
)
select v_cursor_records(v_index).customer_id
, v_cursor_records(v_index).product_id
, v_cursor_records(v_index).outlet_id
, v_cursor_records(v_index).supplier_id
, v_cursor_records(v_index).d_date
, v_cursor_records(v_index).sale_price
, v_cursor_records(v_index).quantity_sold
* v_cursor_records(v_index).sale_price
, v_cursor_records(v_index).quantity_sold
from dual
where not exists
( select null
from sales_fact
where product_id = v_cursor_records(v_index).product_id
and customer_id = v_cursor_records(v_index).customer_id
and supplier_id = v_cursor_records(v_index).supplier_id
and outlet_id = v_cursor_records(v_index).outlet_id
and d_date = v_cursor_records(v_index).d_date
and sale_price = v_cursor_records(v_index).sale_price
and quantity_sold = v_cursor_records(v_index).quantity_sold
);
exit when v_cursor_records.count < k_bulk_buffer_size;
end loop;
close v_cursor;
commit;
end transactioninlj;
Note: The DDL for the source tables is not included in your post so I had to "invent" the definition for DATASTREAM. However, you only have 2 source inputs: DATASTREAM and MASTERDATA. Since you only select 5 columns from masterdata, every thing else must come from datastream.

How to store the number of affected records and return it as a parameter?

I have an in parameter (set = 0), to keep track of the count of entries I will be modifying. I am trying to merge data into a table called Table1, the records that have been updated in a different table (Table2) since the last time Table1 has been updated. The conditional statement will compare the Table1.LastUpdate column to the max(Modified_date) column of Table2 and only insert entries where the table1.last_update column is greater than the table2.max(modified_date) column. Then I will need to store this number and return it as an out parameter. What I have is follows:
create or replace procedure test_proc (rUpdated_Row_Count IN NUMBER, rUpdated_Row_Count_2 OUT NUMBER) is
CURSOR c1 is
select max(modified_date) as max_modified_date
from table1;
l_var c1%ROWTYPE;
-----------
CURSOR c2 is
select table2_id
, last_update
from table2;
k_var c2%ROWTYPE;
BEGIN
LOOP
Open c1;
Fetch c1 into l_var;
Open c2;
Fetch c2 into k_var;
EXIT WHEN c1%NOTFOUND;
IF k_var.last_update > l_var.max_modified_date THEN
Insert into table2(table2_id, last_update)
values(null, k_var.last_update);
commit;
rUpdated_Row_Count_2 := rUpdated_Row_Count + 1;
END IF;
END LOOP;
Close c1;
Close c2;
END test_proc;
Thanks in advance!
Modified my code (after doing further research):
create or replace procedure test_proc (rUpdated_Row_Count IN NUMBER, rUpdated_Row_Count_2 OUT NUMBER) is
CURSOR c1 is
select max(modified_date) as max_modified_date
from table1;
l_var c1%ROWTYPE;
-----------
CURSOR c2 is
select table2_id
, last_update
from table2;
k_var c2%ROWTYPE;
BEGIN
Open c1;
Open c2;
LOOP
Fetch c1 into l_var;
Fetch c2 into k_var;
EXIT WHEN c2%NOTFOUND;
IF k_var.last_update > l_var.max_modified_date THEN
Insert into table2(table2_id, last_update)
values(null, k_var.last_update);
commit;
rUpdated_Row_Count_2 := rUpdated_Row_Count + 1;
END IF;
END LOOP;
Close c1;
Close c2;
END test_proc;
Reproducable data / Code is below:
Create table1
(
table1_id number,
modified_date date
);
Create table2
(
table2_id number,
last_update date
);
insert into table1(table1_id, modified_date) values(1, sysdate);
insert into table1(table1_id, modified_date) values(2, sysdate);
insert into table1(table1_id, modified_date) values(3, sysdate -1);
insert into table2(table2_id, last_update) values(1, sysdate + 1);
insert into table2(table2_id, last_update) values(2, sysdate + 2);
Not quite sure what the "IN" parameter is for. Also not quite sure about the overall rationale. However, here's how I'd write a first version of your procedure:
create or replace procedure test_proc2 (
rUpdated_Row_Count IN NUMBER
, rUpdated_Row_Count_2 IN OUT NUMBER )
is
max_modified_date date ;
begin
select max( modified_date ) into max_modified_date from table1 ;
for rec_ in (
select table2_id, last_update
from table2
) loop
if rec_.last_update > max_modified_date then
insert into table2( table2_id, last_update )
values( null, rec_.last_update ) ;
rUpdated_Row_Count_2 := rUpdated_Row_Count_2 + 1 ;
end if ;
end loop;
end ;
/
Using your test tables (your DDL code should be: CREATE TABLE table1 ... by the way), we can use the following anonymous block for executing the procedure.
-- not sure what the "IN" parameter is used for
declare
rowcount_in number := 0 ; -- not needed
rowcount_out number := 0 ;
begin
test_proc2( rowcount_in, rowcount_out ) ;
dbms_output.put_line( 'updated rows: ' || rowcount_out ) ;
end;
/
updated rows: 2
After executing the anonymous block the tables contain ...
SQL> select * from table1 ;
TABLE1_ID MODIFIED_DATE
1 15-MAY-18
2 15-MAY-18
3 14-MAY-18
SQL> select * from table2 ;
TABLE2_ID LAST_UPDATE
1 16-MAY-18
2 17-MAY-18
NULL 16-MAY-18
NULL 17-MAY-18
Many people will tell you that you should use BULK operations (BULK COLLECT, FORALL etc) whenever possible. Does all that help you?

Check if Exists PLS-00405: subquery not allowed in this context

I have cursor it selects from TableA then Fetch Loop that inserts into TableB.
I want to check if the value already exists in the TableB.
If it exists then I want to skip the insert.
create or replace
PROCEDURE DAILY_RPT (
v_start IN DATE,
v_end IN DATE)
IS
ao_out_no out_pair.out_no%type;
cursor get is
SELECT ao_out_no from tableA;
BEGIN
open get;
LOOP
fetch get into ao_out_no;
EXIT WHEN get%NOTFOUND;
if (ao_out_no = (select out_no from TableA where out_no = ao_out_no) THEN
--DO NOTHING
else
INSERT INTO TABLEB(OUT_NO) VALUES (ao_out_no);
end if;
END LOOP;
close get;
END;
I used IF CONDITION however, I used variable into if condition & I am getting below.
PLS-00405: subquery not allowed in this context
if (ao_out_no = (select out_no from TableA where out_no = ao_out_no) THEN
You don't need cursor or PL/SQL at all:
INSERT INTO TABLEB(OUT_NO)
SELECT ao_out_no
FROM tableA ta
WHERE ... -- filtering rows
AND NOT EXISTS (SELECT * From TableB tb WHERE tb.OUT_NO = ta.ao_out_no);
Use the following :
for i in (
select out_no from TableA where out_no
)
loop
if i.out_no = ao_out_no
then
-- DO NOTHING
else
...
or
create a new variable named x, and then assign a value to it by
select out_no into x from TableA where out_no = ao_out_no;
and check returning value for x.
With corrected syntax, it would be something like this:
create or replace procedure daily_rpt
( v_start in date
, v_end in date )
as
begin
for r in (
select ao_out_no, 0 as exists_check
from tablea
)
loop
select count(*) into exists_check
from tablea
where out_no = r.ao_out_no
and rownum = 1;
if r.exists_check > 0 then
--DO NOTHING
else
insert into tableb (out_no) values (r.ao_out_no);
end if;
end loop;
end;
However, it's inefficient to query all of the rows and then do an additional lookup for each row to decide whether you want to use it, as SQL can do that kind of thing for you. So version 2 might be something like:
create or replace procedure daily_rpt
( v_start in date
, v_end in date )
as
begin
for r in (
select ao_out_no
from tablea
where not exists
( select count(*)
from tablea
where out_no = r.ao_out_no
and rownum = 1 )
)
loop
insert into tableb (out_no) values (r.ao_out_no);
end loop;
end;
at which point you might replace the whole loop with an insert ... where not exists (...) statement.

How to create dynamic sql for with sys_refcursor in oracle

I have function which returns sys_refcursor I give you my code's example.
function myfunc( p_city IN VARCHAR2,
p_order IN VARCHAR2)
RETURN SYS_REFCURSOR IS
v_result SYS_REFCURSOR;
begin
OPEN v_result FOR WITH all_prb AS(
select * from tableA ta inner join tableB tb)
'select * from all_prb ff where (:p_city is null or (LOWER(ff.city) like ''%''||:p_city||''%'') ) order by ' || p_order || 'asc' using p_city,p_city;
return v_result;
end myfunc;
and when i am trying to compile it i have ORA-00928: missing SELECT keyword and this error targets line where i have dynamic sql 'select * from all_prb ff where ...'
How can i fix it and how can i write correct dynamic sql ? I am writing dynamic sql for ordering .
I'm not sure why you're bothering with the with clause, it's simpler without a CTE; you just need to identify which table the city column is in:
function myfunc(p_city IN VARCHAR2,
p_order IN VARCHAR2)
RETURN SYS_REFCURSOR IS
v_result SYS_REFCURSOR;
begin
OPEN v_result FOR
'select * from tableA ta
inner join tableB tb on tb.some_col = ta.some_col
where :p_city is null or LOWER(ta.city) like ''%''||:p_city||''%''
order by ' || p_order || ' asc'
using p_city, p_city;
return v_result;
end myfunc;
/
I've guessed it's table A, just change the alias if it's the other one. You also need to specify the join condition between the two tables. (Also noticed I added a space before asc to stop that being concatenated into the order-by string).
This compiles without errors; when run I get ORA-00942: table or view does not exist which is reasonable. If I create dummy data:
create table tablea (some_col number, city varchar2(30));
create table tableb (some_col number);
insert into tablea values (1, 'London');
insert into tablea values (2, 'Londonderry');
insert into tablea values (3, 'East London');
insert into tablea values (4, 'New York');
insert into tableb values (1);
insert into tableb values (2);
insert into tableb values (3);
insert into tableb values (4);
then calling it gets:
select myfunc('lond', 'city') from dual;
SOME_COL CITY SOME_COL
---------- ------------------------------ ----------
3 East London 3
1 London 1
2 Londonderry 2
If you really want to stick with the CTE for some reason then (as #boneist said) that needs to be part of the dynamic statement:
OPEN v_result FOR
'with all_prb as (
select * from tableA ta
inner join tableB tb on tb.some_col = ta.some_col
)
select * from all_prb ff
where :p_city is null or LOWER(ff.city) like ''%''||:p_city||''%''
order by ' || p_order || ' asc'
using p_city, p_city;

XMLAGG - ORA-00932: inconsistent datatypes: expected - got CLOB on CLOB

I have the following SQL query:
SELECT DISTINCT
prod_no,
prod_text,
RTRIM (
XMLAGG (XMLELEMENT (e, prod_desc, ',').EXTRACT (
'//text()') ORDER BY prod_desc).getclobval (),
',')
FROM mytable
WHERE prod_no = 'XCY'
GROUP BY prod_no,
prod_text
When I execute I am getting
ORA-00932: inconsistent datatypes: expected - got CLOB
Update 1
DDL and sample data
CREATE TABLE mytable
(
prod_no VARCHAR2 (30 BYTE) NOT NULL,
prod_text VARCHAR2 (30 BYTE) NOT NULL,
prod_desc CLOB
);
SET DEFINE OFF;
INSERT INTO mytable (prod_no, prod_text, prod_desc)
VALUES ('XCY', 'DECKS', 'THIS IS TEST');
INSERT INTO mytable (prod_no, prod_text, prod_desc)
VALUES ('ABC', 'DECKS', 'THIS IS TEST 2');
COMMIT;
Issue is with DISTINCT and ORDER BY. Oracle doesn't allow these operation on CLOB. You are using group by, so you don't need DISTINCT anyway.
The below will work, if you don't mind the order of description.
SELECT
prod_no,
prod_text,
RTRIM (
XMLAGG (XMLELEMENT (e, prod_desc, ',') ).EXTRACT (
'//text()').getclobval (),
',')
FROM mytable
WHERE prod_no = 'XCY'
GROUP BY prod_no,
prod_text;
If you must order by it, you can cast the CLOB to varchar2 and order by it:
SELECT
prod_no,
prod_text,
RTRIM (
XMLAGG (XMLELEMENT (e, prod_desc, ',')
ORDER BY cast(prod_desc as varchar2(4000))).EXTRACT (
'//text()').getclobval (),
',')
FROM mytable
WHERE prod_no = 'XCY'
GROUP BY prod_no,
prod_text

Resources