How to split string into rows based on condition in plsql [duplicate] - oracle
I know this has been answered to some degree with PHP and MYSQL, but I was wondering if someone could teach me the simplest approach to splitting a string (comma delimited) into multiple rows in Oracle 10g (preferably) and 11g.
The table is as follows:
Name | Project | Error
108 test Err1, Err2, Err3
109 test2 Err1
I want to create the following:
Name | Project | Error
108 Test Err1
108 Test Err2
108 Test Err3
109 Test2 Err1
I've seen a few potential solutions around stack, however they only accounted for a single column (being the comma delimited string). Any help would be greatly appreciated.
This may be an improved way (also with regexp and connect by):
with temp as
(
select 108 Name, 'test' Project, 'Err1, Err2, Err3' Error from dual
union all
select 109, 'test2', 'Err1' from dual
)
select distinct
t.name, t.project,
trim(regexp_substr(t.error, '[^,]+', 1, levels.column_value)) as error
from
temp t,
table(cast(multiset(select level from dual connect by level <= length (regexp_replace(t.error, '[^,]+')) + 1) as sys.OdciNumberList)) levels
order by name
EDIT:
Here is a simple (as in, "not in depth") explanation of the query.
length (regexp_replace(t.error, '[^,]+')) + 1 uses regexp_replace to erase anything that is not the delimiter (comma in this case) and length +1 to get how many elements (errors) are there.
The select level from dual connect by level <= (...) uses a hierarchical query to create a column with an increasing number of matches found, from 1 to the total number of errors.
Preview:
select level, length (regexp_replace('Err1, Err2, Err3', '[^,]+')) + 1 as max
from dual connect by level <= length (regexp_replace('Err1, Err2, Err3', '[^,]+')) + 1
table(cast(multiset(.....) as sys.OdciNumberList)) does some casting of oracle types.
The cast(multiset(.....)) as sys.OdciNumberList transforms multiple collections (one collection for each row in the original data set) into a single collection of numbers, OdciNumberList.
The table() function transforms a collection into a resultset.
FROM without a join creates a cross join between your dataset and the multiset.
As a result, a row in the data set with 4 matches will repeat 4 times (with an increasing number in the column named "column_value").
Preview:
select * from
temp t,
table(cast(multiset(select level from dual connect by level <= length (regexp_replace(t.error, '[^,]+')) + 1) as sys.OdciNumberList)) levels
trim(regexp_substr(t.error, '[^,]+', 1, levels.column_value)) uses the column_value as the nth_appearance/ocurrence parameter for regexp_substr.
You can add some other columns from your data set (t.name, t.project as an example) for easy visualization.
Some references to Oracle docs:
REGEXP_REPLACE
REGEXP_SUBSTR
Extensibility Constants, Types, and Mappings (OdciNumberList)
CAST (multiset)
Hierarchical Queries
regular expressions is a wonderful thing :)
with temp as (
select 108 Name, 'test' Project, 'Err1, Err2, Err3' Error from dual
union all
select 109, 'test2', 'Err1' from dual
)
SELECT distinct Name, Project, trim(regexp_substr(str, '[^,]+', 1, level)) str
FROM (SELECT Name, Project, Error str FROM temp) t
CONNECT BY instr(str, ',', 1, level - 1) > 0
order by Name
There is a huge difference between the below two:
splitting a single delimited string
splitting delimited strings for multiple rows in a table.
If you do not restrict the rows, then the CONNECT BY clause would produce multiple rows and will not give the desired output.
For single delimited string, look at Split single comma delimited string into rows
For splitting delimited strings in a table, look at Split comma delimited strings in a table
Apart from Regular Expressions, a few other alternatives are using:
XMLTable
MODEL clause
Setup
SQL> CREATE TABLE t (
2 ID NUMBER GENERATED ALWAYS AS IDENTITY,
3 text VARCHAR2(100)
4 );
Table created.
SQL>
SQL> INSERT INTO t (text) VALUES ('word1, word2, word3');
1 row created.
SQL> INSERT INTO t (text) VALUES ('word4, word5, word6');
1 row created.
SQL> INSERT INTO t (text) VALUES ('word7, word8, word9');
1 row created.
SQL> COMMIT;
Commit complete.
SQL>
SQL> SELECT * FROM t;
ID TEXT
---------- ----------------------------------------------
1 word1, word2, word3
2 word4, word5, word6
3 word7, word8, word9
SQL>
Using XMLTABLE:
SQL> SELECT id,
2 trim(COLUMN_VALUE) text
3 FROM t,
4 xmltable(('"'
5 || REPLACE(text, ',', '","')
6 || '"'))
7 /
ID TEXT
---------- ------------------------
1 word1
1 word2
1 word3
2 word4
2 word5
2 word6
3 word7
3 word8
3 word9
9 rows selected.
SQL>
Using MODEL clause:
SQL> WITH
2 model_param AS
3 (
4 SELECT id,
5 text AS orig_str ,
6 ','
7 || text
8 || ',' AS mod_str ,
9 1 AS start_pos ,
10 Length(text) AS end_pos ,
11 (Length(text) - Length(Replace(text, ','))) + 1 AS element_count ,
12 0 AS element_no ,
13 ROWNUM AS rn
14 FROM t )
15 SELECT id,
16 trim(Substr(mod_str, start_pos, end_pos-start_pos)) text
17 FROM (
18 SELECT *
19 FROM model_param MODEL PARTITION BY (id, rn, orig_str, mod_str)
20 DIMENSION BY (element_no)
21 MEASURES (start_pos, end_pos, element_count)
22 RULES ITERATE (2000)
23 UNTIL (ITERATION_NUMBER+1 = element_count[0])
24 ( start_pos[ITERATION_NUMBER+1] = instr(cv(mod_str), ',', 1, cv(element_no)) + 1,
25 end_pos[iteration_number+1] = instr(cv(mod_str), ',', 1, cv(element_no) + 1) )
26 )
27 WHERE element_no != 0
28 ORDER BY mod_str ,
29 element_no
30 /
ID TEXT
---------- --------------------------------------------------
1 word1
1 word2
1 word3
2 word4
2 word5
2 word6
3 word7
3 word8
3 word9
9 rows selected.
SQL>
A couple of more examples of the same:
SELECT trim(regexp_substr('Err1, Err2, Err3', '[^,]+', 1, LEVEL)) str_2_tab
FROM dual
CONNECT BY LEVEL <= regexp_count('Err1, Err2, Err3', ',')+1
/
SELECT trim(regexp_substr('Err1, Err2, Err3', '[^,]+', 1, LEVEL)) str_2_tab
FROM dual
CONNECT BY LEVEL <= length('Err1, Err2, Err3') - length(REPLACE('Err1, Err2, Err3', ',', ''))+1
/
Also, may use DBMS_UTILITY.comma_to_table & table_to_comma:
http://www.oracle-base.com/articles/9i/useful-procedures-and-functions-9i.php#DBMS_UTILITY.comma_to_table
I would like to propose a different approach using a PIPELINED table function. It's somewhat similar to the technique of the XMLTABLE, except that you are providing your own custom function to split the character string:
-- Create a collection type to hold the results
CREATE OR REPLACE TYPE typ_str2tbl_nst AS TABLE OF VARCHAR2(30);
/
-- Split the string according to the specified delimiter
CREATE OR REPLACE FUNCTION str2tbl (
p_string VARCHAR2,
p_delimiter CHAR DEFAULT ','
)
RETURN typ_str2tbl_nst PIPELINED
AS
l_tmp VARCHAR2(32000) := p_string || p_delimiter;
l_pos NUMBER;
BEGIN
LOOP
l_pos := INSTR( l_tmp, p_delimiter );
EXIT WHEN NVL( l_pos, 0 ) = 0;
PIPE ROW ( RTRIM( LTRIM( SUBSTR( l_tmp, 1, l_pos-1) ) ) );
l_tmp := SUBSTR( l_tmp, l_pos+1 );
END LOOP;
END str2tbl;
/
-- The problem solution
SELECT name,
project,
TRIM(COLUMN_VALUE) error
FROM t, TABLE(str2tbl(error));
Results:
NAME PROJECT ERROR
---------- ---------- --------------------
108 test Err1
108 test Err2
108 test Err3
109 test2 Err1
The problem with this type of approach is that often the optimizer won't know the cardinality of the table function and it will have to make a guess. This could be potentialy harmful to your execution plans, so this solution can be extended to provide execution statistics for the optimizer.
You can see this optimizer estimate by running an EXPLAIN PLAN on the query above:
Execution Plan
----------------------------------------------------------
Plan hash value: 2402555806
----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 16336 | 366K| 59 (0)| 00:00:01 |
| 1 | NESTED LOOPS | | 16336 | 366K| 59 (0)| 00:00:01 |
| 2 | TABLE ACCESS FULL | T | 2 | 42 | 3 (0)| 00:00:01 |
| 3 | COLLECTION ITERATOR PICKLER FETCH| STR2TBL | 8168 | 16336 | 28 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------
Even though the collection has only 3 values, the optimizer estimated 8168 rows for it (default value). This may seem irrelevant at first, but it may be enough for the optimizer to decide for a sub-optimal plan.
The solution is to use the optimizer extensions to provide statistics for the collection:
-- Create the optimizer interface to the str2tbl function
CREATE OR REPLACE TYPE typ_str2tbl_stats AS OBJECT (
dummy NUMBER,
STATIC FUNCTION ODCIGetInterfaces ( p_interfaces OUT SYS.ODCIObjectList )
RETURN NUMBER,
STATIC FUNCTION ODCIStatsTableFunction ( p_function IN SYS.ODCIFuncInfo,
p_stats OUT SYS.ODCITabFuncStats,
p_args IN SYS.ODCIArgDescList,
p_string IN VARCHAR2,
p_delimiter IN CHAR DEFAULT ',' )
RETURN NUMBER
);
/
-- Optimizer interface implementation
CREATE OR REPLACE TYPE BODY typ_str2tbl_stats
AS
STATIC FUNCTION ODCIGetInterfaces ( p_interfaces OUT SYS.ODCIObjectList )
RETURN NUMBER
AS
BEGIN
p_interfaces := SYS.ODCIObjectList ( SYS.ODCIObject ('SYS', 'ODCISTATS2') );
RETURN ODCIConst.SUCCESS;
END ODCIGetInterfaces;
-- This function is responsible for returning the cardinality estimate
STATIC FUNCTION ODCIStatsTableFunction ( p_function IN SYS.ODCIFuncInfo,
p_stats OUT SYS.ODCITabFuncStats,
p_args IN SYS.ODCIArgDescList,
p_string IN VARCHAR2,
p_delimiter IN CHAR DEFAULT ',' )
RETURN NUMBER
AS
BEGIN
-- I'm using basically half the string lenght as an estimator for its cardinality
p_stats := SYS.ODCITabFuncStats( CEIL( LENGTH( p_string ) / 2 ) );
RETURN ODCIConst.SUCCESS;
END ODCIStatsTableFunction;
END;
/
-- Associate our optimizer extension with the PIPELINED function
ASSOCIATE STATISTICS WITH FUNCTIONS str2tbl USING typ_str2tbl_stats;
Testing the resulting execution plan:
Execution Plan
----------------------------------------------------------
Plan hash value: 2402555806
----------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 23 | 59 (0)| 00:00:01 |
| 1 | NESTED LOOPS | | 1 | 23 | 59 (0)| 00:00:01 |
| 2 | TABLE ACCESS FULL | T | 2 | 42 | 3 (0)| 00:00:01 |
| 3 | COLLECTION ITERATOR PICKLER FETCH| STR2TBL | 1 | 2 | 28 (0)| 00:00:01 |
----------------------------------------------------------------------------------------------
As you can see the cardinality on the plan above is not the 8196 guessed value anymore. It's still not correct because we are passing a column instead of a string literal to the function.
Some tweaking to the function code would be necessary to give a closer estimate in this particular case, but I think the overall concept is pretty much explained here.
The str2tbl function used in this answer was originally developed by Tom Kyte:
https://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:110612348061
The concept of associating statistics with object types can be further explored by reading this article:
http://www.oracle-developer.net/display.php?id=427
The technique described here works in 10g+.
Starting from Oracle 12c you could use JSON_TABLE and JSON_ARRAY:
CREATE TABLE tab(Name, Project, Error) AS
SELECT 108,'test' ,'Err1, Err2, Err3' FROM dual UNION
SELECT 109,'test2','Err1' FROM dual;
And query:
SELECT *
FROM tab t
OUTER APPLY (SELECT TRIM(p) AS p
FROM JSON_TABLE(REPLACE(JSON_ARRAY(t.Error), ',', '","'),
'$[*]' COLUMNS (p VARCHAR2(4000) PATH '$'))) s;
Output:
┌──────┬─────────┬──────────────────┬──────┐
│ Name │ Project │ Error │ P │
├──────┼─────────┼──────────────────┼──────┤
│ 108 │ test │ Err1, Err2, Err3 │ Err1 │
│ 108 │ test │ Err1, Err2, Err3 │ Err2 │
│ 108 │ test │ Err1, Err2, Err3 │ Err3 │
│ 109 │ test2 │ Err1 │ Err1 │
└──────┴─────────┴──────────────────┴──────┘
db<>fiddle demo
REGEXP_COUNT wasn't added until Oracle 11i. Here's an Oracle 10g solution, adopted from Art's solution.
SELECT trim(regexp_substr('Err1, Err2, Err3', '[^,]+', 1, LEVEL)) str_2_tab
FROM dual
CONNECT BY LEVEL <=
LENGTH('Err1, Err2, Err3')
- LENGTH(REPLACE('Err1, Err2, Err3', ',', ''))
+ 1;
Here is an alternative implementation using XMLTABLE that allows for casting to different data types:
select
xmltab.txt
from xmltable(
'for $text in tokenize("a,b,c", ",") return $text'
columns
txt varchar2(4000) path '.'
) xmltab
;
... or if your delimited strings are stored in one or more rows of a table:
select
xmltab.txt
from (
select 'a;b;c' inpt from dual union all
select 'd;e;f' from dual
) base
inner join xmltable(
'for $text in tokenize($input, ";") return $text'
passing base.inpt as "input"
columns
txt varchar2(4000) path '.'
) xmltab
on 1=1
;
I had the same problem, and xmltable helped me:
SELECT id, trim(COLUMN_VALUE) text
FROM t, xmltable(('"' || REPLACE(text, ',', '","') || '"'))
I'd like to add another method. This one uses recursive querys, something I haven't seen in the other answers. It is supported by Oracle since 11gR2.
with cte0 as (
select phone_number x
from hr.employees
), cte1(xstr,xrest,xremoved) as (
select x, x, null
from cte0
union all
select xstr,
case when instr(xrest,'.') = 0 then null else substr(xrest,instr(xrest,'.')+1) end,
case when instr(xrest,'.') = 0 then xrest else substr(xrest,1,instr(xrest,'.') - 1) end
from cte1
where xrest is not null
)
select xstr, xremoved from cte1
where xremoved is not null
order by xstr
It is quite flexible with the splitting character. Simply change it in the INSTR calls.
Without using connect by or regexp:
with mytable as (
select 108 name, 'test' project, 'Err1,Err2,Err3' error from dual
union all
select 109, 'test2', 'Err1' from dual
)
,x as (
select name
,project
,','||error||',' error
from mytable
)
,iter as (SELECT rownum AS pos
FROM all_objects
)
select x.name,x.project
,SUBSTR(x.error
,INSTR(x.error, ',', 1, iter.pos) + 1
,INSTR(x.error, ',', 1, iter.pos + 1)-INSTR(x.error, ',', 1, iter.pos)-1
) error
from x, iter
where iter.pos < = (LENGTH(x.error) - LENGTH(REPLACE(x.error, ','))) - 1;
In Oracle 11g and later, you can use a recursive sub-query and simple string functions (which may be faster than regular expressions and correlated hierarchical sub-queries):
Oracle Setup:
CREATE TABLE table_name ( name, project, error ) as
select 108, 'test', 'Err1, Err2, Err3' from dual union all
select 109, 'test2', 'Err1' from dual;
Query:
WITH table_name_error_bounds ( name, project, error, start_pos, end_pos ) AS (
SELECT name,
project,
error,
1,
INSTR( error, ', ', 1 )
FROM table_name
UNION ALL
SELECT name,
project,
error,
end_pos + 2,
INSTR( error, ', ', end_pos + 2 )
FROM table_name_error_bounds
WHERE end_pos > 0
)
SELECT name,
project,
CASE end_pos
WHEN 0
THEN SUBSTR( error, start_pos )
ELSE SUBSTR( error, start_pos, end_pos - start_pos )
END AS error
FROM table_name_error_bounds
Output:
NAME | PROJECT | ERROR
---: | :------ | :----
108 | test | Err1
109 | test2 | Err1
108 | test | Err2
108 | test | Err3
db<>fiddle here
If you have Oracle APEX 5.1 or later installed, you can use the convenient APEX_STRING.split function, e.g.:
select q.Name, q.Project, s.column_value as Error
from mytable q,
APEX_STRING.split(q.Error, ',') s
The second parameter is the delimiter string. It also accepts a 3rd parameter to limit how many splits you want it to perform.
https://docs.oracle.com/en/database/oracle/application-express/20.1/aeapi/SPLIT-Function-Signature-1.html#GUID-3BE7FF37-E54F-4503-91B8-94F374E243E6
i had used the DBMS_UTILITY.comma_to _table function actually its working
the code as follows
declare
l_tablen BINARY_INTEGER;
l_tab DBMS_UTILITY.uncl_array;
cursor cur is select * from qwer;
rec cur%rowtype;
begin
open cur;
loop
fetch cur into rec;
exit when cur%notfound;
DBMS_UTILITY.comma_to_table (
list => rec.val,
tablen => l_tablen,
tab => l_tab);
FOR i IN 1 .. l_tablen LOOP
DBMS_OUTPUT.put_line(i || ' : ' || l_tab(i));
END LOOP;
end loop;
close cur;
end;
i had used my own table and column names
Related
Count column comma delimited values oracle
Is it possible to count and also group by comma delimited values in the oracle database table? This is a table data example: id | user | title | 1 | foo | a,b,c | 2 | bar | a,d | 3 | tee | b | The expected result would be: title | count a | 2 b | 2 c | 1 d | 1 I wanted to use concat like this: SELECT a.title FROM Account a WHERE concat(',', a.title, ',') LIKE 'a' OR concat(',', a.title, ',') LIKE 'b' ... GROUP BY a.title? But I'm getting invalid number of arguments on concat. The title values are predefined, therefore I don't mind if I have to list all of them in the query. Any help is greatly appreciated.
This uses simple string functions and a recursive sub-query factoring and may be faster than using regular expressions and correlated joins: Oracle Setup: CREATE TABLE account ( id, "user", title ) AS SELECT 1, 'foo', 'a,b,c' FROM DUAL UNION ALL SELECT 2, 'bar', 'a,d' FROM DUAL UNION ALL SELECT 3, 'tee', 'b' FROM DUAL; Query: WITH positions ( title, start_pos, end_pos ) AS ( SELECT title, 1, INSTR( title, ',', 1 ) FROM account UNION ALL SELECT title, end_pos + 1, INSTR( title, ',', end_pos + 1 ) FROM positions WHERE end_pos > 0 ), items ( item ) AS ( SELECT CASE end_pos WHEN 0 THEN SUBSTR( title, start_pos ) ELSE SUBSTR( title, start_pos, end_pos - start_pos ) END FROM positions ) SELECT item, COUNT(*) FROM items GROUP BY item ORDER BY item; Output: ITEM | COUNT(*) :--- | -------: a | 2 b | 2 c | 1 d | 1 db<>fiddle here
Split titles to rows and count them. SQL> with test (id, title) as 2 (select 1, 'a,b,c' from dual union all 3 select 2, 'a,d' from dual union all 4 select 3, 'b' from dual 5 ), 6 temp as 7 (select regexp_substr(title, '[^,]', 1, column_value) val 8 from test cross join table(cast(multiset(select level from dual 9 connect by level <= regexp_count(title, ',') + 1 10 ) as sys.odcinumberlist)) 11 ) 12 select val as title, 13 count(*) 14 From temp 15 group by val 16 order by val; TITLE COUNT(*) -------------------- ---------- a 2 b 2 c 1 d 1 SQL> If titles aren't that simple, then modify REGEXP_SUBSTR (add + sign) in line #7, e.g. SQL> with test (id, title) as 2 (select 1, 'Robin Hood,Avatar,Star Wars Episode III' from dual union all 3 select 2, 'Mickey Mouse,Avatar' from dual union all 4 select 3, 'The Godfather' from dual 5 ), 6 temp as 7 (select regexp_substr(title, '[^,]+', 1, column_value) val 8 from test cross join table(cast(multiset(select level from dual 9 connect by level <= regexp_count(title, ',') + 1 10 ) as sys.odcinumberlist)) 11 ) 12 select val as title, 13 count(*) 14 From temp 15 group by val 16 order by val; TITLE COUNT(*) ------------------------------ ---------- Avatar 2 Mickey Mouse 1 Robin Hood 1 Star Wars Episode III 1 The Godfather 1 SQL>
How to perform opposite of LISTAGG in Oracle [duplicate]
I know this has been answered to some degree with PHP and MYSQL, but I was wondering if someone could teach me the simplest approach to splitting a string (comma delimited) into multiple rows in Oracle 10g (preferably) and 11g. The table is as follows: Name | Project | Error 108 test Err1, Err2, Err3 109 test2 Err1 I want to create the following: Name | Project | Error 108 Test Err1 108 Test Err2 108 Test Err3 109 Test2 Err1 I've seen a few potential solutions around stack, however they only accounted for a single column (being the comma delimited string). Any help would be greatly appreciated.
This may be an improved way (also with regexp and connect by): with temp as ( select 108 Name, 'test' Project, 'Err1, Err2, Err3' Error from dual union all select 109, 'test2', 'Err1' from dual ) select distinct t.name, t.project, trim(regexp_substr(t.error, '[^,]+', 1, levels.column_value)) as error from temp t, table(cast(multiset(select level from dual connect by level <= length (regexp_replace(t.error, '[^,]+')) + 1) as sys.OdciNumberList)) levels order by name EDIT: Here is a simple (as in, "not in depth") explanation of the query. length (regexp_replace(t.error, '[^,]+')) + 1 uses regexp_replace to erase anything that is not the delimiter (comma in this case) and length +1 to get how many elements (errors) are there. The select level from dual connect by level <= (...) uses a hierarchical query to create a column with an increasing number of matches found, from 1 to the total number of errors. Preview: select level, length (regexp_replace('Err1, Err2, Err3', '[^,]+')) + 1 as max from dual connect by level <= length (regexp_replace('Err1, Err2, Err3', '[^,]+')) + 1 table(cast(multiset(.....) as sys.OdciNumberList)) does some casting of oracle types. The cast(multiset(.....)) as sys.OdciNumberList transforms multiple collections (one collection for each row in the original data set) into a single collection of numbers, OdciNumberList. The table() function transforms a collection into a resultset. FROM without a join creates a cross join between your dataset and the multiset. As a result, a row in the data set with 4 matches will repeat 4 times (with an increasing number in the column named "column_value"). Preview: select * from temp t, table(cast(multiset(select level from dual connect by level <= length (regexp_replace(t.error, '[^,]+')) + 1) as sys.OdciNumberList)) levels trim(regexp_substr(t.error, '[^,]+', 1, levels.column_value)) uses the column_value as the nth_appearance/ocurrence parameter for regexp_substr. You can add some other columns from your data set (t.name, t.project as an example) for easy visualization. Some references to Oracle docs: REGEXP_REPLACE REGEXP_SUBSTR Extensibility Constants, Types, and Mappings (OdciNumberList) CAST (multiset) Hierarchical Queries
regular expressions is a wonderful thing :) with temp as ( select 108 Name, 'test' Project, 'Err1, Err2, Err3' Error from dual union all select 109, 'test2', 'Err1' from dual ) SELECT distinct Name, Project, trim(regexp_substr(str, '[^,]+', 1, level)) str FROM (SELECT Name, Project, Error str FROM temp) t CONNECT BY instr(str, ',', 1, level - 1) > 0 order by Name
There is a huge difference between the below two: splitting a single delimited string splitting delimited strings for multiple rows in a table. If you do not restrict the rows, then the CONNECT BY clause would produce multiple rows and will not give the desired output. For single delimited string, look at Split single comma delimited string into rows For splitting delimited strings in a table, look at Split comma delimited strings in a table Apart from Regular Expressions, a few other alternatives are using: XMLTable MODEL clause Setup SQL> CREATE TABLE t ( 2 ID NUMBER GENERATED ALWAYS AS IDENTITY, 3 text VARCHAR2(100) 4 ); Table created. SQL> SQL> INSERT INTO t (text) VALUES ('word1, word2, word3'); 1 row created. SQL> INSERT INTO t (text) VALUES ('word4, word5, word6'); 1 row created. SQL> INSERT INTO t (text) VALUES ('word7, word8, word9'); 1 row created. SQL> COMMIT; Commit complete. SQL> SQL> SELECT * FROM t; ID TEXT ---------- ---------------------------------------------- 1 word1, word2, word3 2 word4, word5, word6 3 word7, word8, word9 SQL> Using XMLTABLE: SQL> SELECT id, 2 trim(COLUMN_VALUE) text 3 FROM t, 4 xmltable(('"' 5 || REPLACE(text, ',', '","') 6 || '"')) 7 / ID TEXT ---------- ------------------------ 1 word1 1 word2 1 word3 2 word4 2 word5 2 word6 3 word7 3 word8 3 word9 9 rows selected. SQL> Using MODEL clause: SQL> WITH 2 model_param AS 3 ( 4 SELECT id, 5 text AS orig_str , 6 ',' 7 || text 8 || ',' AS mod_str , 9 1 AS start_pos , 10 Length(text) AS end_pos , 11 (Length(text) - Length(Replace(text, ','))) + 1 AS element_count , 12 0 AS element_no , 13 ROWNUM AS rn 14 FROM t ) 15 SELECT id, 16 trim(Substr(mod_str, start_pos, end_pos-start_pos)) text 17 FROM ( 18 SELECT * 19 FROM model_param MODEL PARTITION BY (id, rn, orig_str, mod_str) 20 DIMENSION BY (element_no) 21 MEASURES (start_pos, end_pos, element_count) 22 RULES ITERATE (2000) 23 UNTIL (ITERATION_NUMBER+1 = element_count[0]) 24 ( start_pos[ITERATION_NUMBER+1] = instr(cv(mod_str), ',', 1, cv(element_no)) + 1, 25 end_pos[iteration_number+1] = instr(cv(mod_str), ',', 1, cv(element_no) + 1) ) 26 ) 27 WHERE element_no != 0 28 ORDER BY mod_str , 29 element_no 30 / ID TEXT ---------- -------------------------------------------------- 1 word1 1 word2 1 word3 2 word4 2 word5 2 word6 3 word7 3 word8 3 word9 9 rows selected. SQL>
A couple of more examples of the same: SELECT trim(regexp_substr('Err1, Err2, Err3', '[^,]+', 1, LEVEL)) str_2_tab FROM dual CONNECT BY LEVEL <= regexp_count('Err1, Err2, Err3', ',')+1 / SELECT trim(regexp_substr('Err1, Err2, Err3', '[^,]+', 1, LEVEL)) str_2_tab FROM dual CONNECT BY LEVEL <= length('Err1, Err2, Err3') - length(REPLACE('Err1, Err2, Err3', ',', ''))+1 / Also, may use DBMS_UTILITY.comma_to_table & table_to_comma: http://www.oracle-base.com/articles/9i/useful-procedures-and-functions-9i.php#DBMS_UTILITY.comma_to_table
I would like to propose a different approach using a PIPELINED table function. It's somewhat similar to the technique of the XMLTABLE, except that you are providing your own custom function to split the character string: -- Create a collection type to hold the results CREATE OR REPLACE TYPE typ_str2tbl_nst AS TABLE OF VARCHAR2(30); / -- Split the string according to the specified delimiter CREATE OR REPLACE FUNCTION str2tbl ( p_string VARCHAR2, p_delimiter CHAR DEFAULT ',' ) RETURN typ_str2tbl_nst PIPELINED AS l_tmp VARCHAR2(32000) := p_string || p_delimiter; l_pos NUMBER; BEGIN LOOP l_pos := INSTR( l_tmp, p_delimiter ); EXIT WHEN NVL( l_pos, 0 ) = 0; PIPE ROW ( RTRIM( LTRIM( SUBSTR( l_tmp, 1, l_pos-1) ) ) ); l_tmp := SUBSTR( l_tmp, l_pos+1 ); END LOOP; END str2tbl; / -- The problem solution SELECT name, project, TRIM(COLUMN_VALUE) error FROM t, TABLE(str2tbl(error)); Results: NAME PROJECT ERROR ---------- ---------- -------------------- 108 test Err1 108 test Err2 108 test Err3 109 test2 Err1 The problem with this type of approach is that often the optimizer won't know the cardinality of the table function and it will have to make a guess. This could be potentialy harmful to your execution plans, so this solution can be extended to provide execution statistics for the optimizer. You can see this optimizer estimate by running an EXPLAIN PLAN on the query above: Execution Plan ---------------------------------------------------------- Plan hash value: 2402555806 ---------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ---------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 16336 | 366K| 59 (0)| 00:00:01 | | 1 | NESTED LOOPS | | 16336 | 366K| 59 (0)| 00:00:01 | | 2 | TABLE ACCESS FULL | T | 2 | 42 | 3 (0)| 00:00:01 | | 3 | COLLECTION ITERATOR PICKLER FETCH| STR2TBL | 8168 | 16336 | 28 (0)| 00:00:01 | ---------------------------------------------------------------------------------------------- Even though the collection has only 3 values, the optimizer estimated 8168 rows for it (default value). This may seem irrelevant at first, but it may be enough for the optimizer to decide for a sub-optimal plan. The solution is to use the optimizer extensions to provide statistics for the collection: -- Create the optimizer interface to the str2tbl function CREATE OR REPLACE TYPE typ_str2tbl_stats AS OBJECT ( dummy NUMBER, STATIC FUNCTION ODCIGetInterfaces ( p_interfaces OUT SYS.ODCIObjectList ) RETURN NUMBER, STATIC FUNCTION ODCIStatsTableFunction ( p_function IN SYS.ODCIFuncInfo, p_stats OUT SYS.ODCITabFuncStats, p_args IN SYS.ODCIArgDescList, p_string IN VARCHAR2, p_delimiter IN CHAR DEFAULT ',' ) RETURN NUMBER ); / -- Optimizer interface implementation CREATE OR REPLACE TYPE BODY typ_str2tbl_stats AS STATIC FUNCTION ODCIGetInterfaces ( p_interfaces OUT SYS.ODCIObjectList ) RETURN NUMBER AS BEGIN p_interfaces := SYS.ODCIObjectList ( SYS.ODCIObject ('SYS', 'ODCISTATS2') ); RETURN ODCIConst.SUCCESS; END ODCIGetInterfaces; -- This function is responsible for returning the cardinality estimate STATIC FUNCTION ODCIStatsTableFunction ( p_function IN SYS.ODCIFuncInfo, p_stats OUT SYS.ODCITabFuncStats, p_args IN SYS.ODCIArgDescList, p_string IN VARCHAR2, p_delimiter IN CHAR DEFAULT ',' ) RETURN NUMBER AS BEGIN -- I'm using basically half the string lenght as an estimator for its cardinality p_stats := SYS.ODCITabFuncStats( CEIL( LENGTH( p_string ) / 2 ) ); RETURN ODCIConst.SUCCESS; END ODCIStatsTableFunction; END; / -- Associate our optimizer extension with the PIPELINED function ASSOCIATE STATISTICS WITH FUNCTIONS str2tbl USING typ_str2tbl_stats; Testing the resulting execution plan: Execution Plan ---------------------------------------------------------- Plan hash value: 2402555806 ---------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ---------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 23 | 59 (0)| 00:00:01 | | 1 | NESTED LOOPS | | 1 | 23 | 59 (0)| 00:00:01 | | 2 | TABLE ACCESS FULL | T | 2 | 42 | 3 (0)| 00:00:01 | | 3 | COLLECTION ITERATOR PICKLER FETCH| STR2TBL | 1 | 2 | 28 (0)| 00:00:01 | ---------------------------------------------------------------------------------------------- As you can see the cardinality on the plan above is not the 8196 guessed value anymore. It's still not correct because we are passing a column instead of a string literal to the function. Some tweaking to the function code would be necessary to give a closer estimate in this particular case, but I think the overall concept is pretty much explained here. The str2tbl function used in this answer was originally developed by Tom Kyte: https://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:110612348061 The concept of associating statistics with object types can be further explored by reading this article: http://www.oracle-developer.net/display.php?id=427 The technique described here works in 10g+.
Starting from Oracle 12c you could use JSON_TABLE and JSON_ARRAY: CREATE TABLE tab(Name, Project, Error) AS SELECT 108,'test' ,'Err1, Err2, Err3' FROM dual UNION SELECT 109,'test2','Err1' FROM dual; And query: SELECT * FROM tab t OUTER APPLY (SELECT TRIM(p) AS p FROM JSON_TABLE(REPLACE(JSON_ARRAY(t.Error), ',', '","'), '$[*]' COLUMNS (p VARCHAR2(4000) PATH '$'))) s; Output: ┌──────┬─────────┬──────────────────┬──────┐ │ Name │ Project │ Error │ P │ ├──────┼─────────┼──────────────────┼──────┤ │ 108 │ test │ Err1, Err2, Err3 │ Err1 │ │ 108 │ test │ Err1, Err2, Err3 │ Err2 │ │ 108 │ test │ Err1, Err2, Err3 │ Err3 │ │ 109 │ test2 │ Err1 │ Err1 │ └──────┴─────────┴──────────────────┴──────┘ db<>fiddle demo
REGEXP_COUNT wasn't added until Oracle 11i. Here's an Oracle 10g solution, adopted from Art's solution. SELECT trim(regexp_substr('Err1, Err2, Err3', '[^,]+', 1, LEVEL)) str_2_tab FROM dual CONNECT BY LEVEL <= LENGTH('Err1, Err2, Err3') - LENGTH(REPLACE('Err1, Err2, Err3', ',', '')) + 1;
Here is an alternative implementation using XMLTABLE that allows for casting to different data types: select xmltab.txt from xmltable( 'for $text in tokenize("a,b,c", ",") return $text' columns txt varchar2(4000) path '.' ) xmltab ; ... or if your delimited strings are stored in one or more rows of a table: select xmltab.txt from ( select 'a;b;c' inpt from dual union all select 'd;e;f' from dual ) base inner join xmltable( 'for $text in tokenize($input, ";") return $text' passing base.inpt as "input" columns txt varchar2(4000) path '.' ) xmltab on 1=1 ;
I had the same problem, and xmltable helped me: SELECT id, trim(COLUMN_VALUE) text FROM t, xmltable(('"' || REPLACE(text, ',', '","') || '"'))
I'd like to add another method. This one uses recursive querys, something I haven't seen in the other answers. It is supported by Oracle since 11gR2. with cte0 as ( select phone_number x from hr.employees ), cte1(xstr,xrest,xremoved) as ( select x, x, null from cte0 union all select xstr, case when instr(xrest,'.') = 0 then null else substr(xrest,instr(xrest,'.')+1) end, case when instr(xrest,'.') = 0 then xrest else substr(xrest,1,instr(xrest,'.') - 1) end from cte1 where xrest is not null ) select xstr, xremoved from cte1 where xremoved is not null order by xstr It is quite flexible with the splitting character. Simply change it in the INSTR calls.
Without using connect by or regexp: with mytable as ( select 108 name, 'test' project, 'Err1,Err2,Err3' error from dual union all select 109, 'test2', 'Err1' from dual ) ,x as ( select name ,project ,','||error||',' error from mytable ) ,iter as (SELECT rownum AS pos FROM all_objects ) select x.name,x.project ,SUBSTR(x.error ,INSTR(x.error, ',', 1, iter.pos) + 1 ,INSTR(x.error, ',', 1, iter.pos + 1)-INSTR(x.error, ',', 1, iter.pos)-1 ) error from x, iter where iter.pos < = (LENGTH(x.error) - LENGTH(REPLACE(x.error, ','))) - 1;
In Oracle 11g and later, you can use a recursive sub-query and simple string functions (which may be faster than regular expressions and correlated hierarchical sub-queries): Oracle Setup: CREATE TABLE table_name ( name, project, error ) as select 108, 'test', 'Err1, Err2, Err3' from dual union all select 109, 'test2', 'Err1' from dual; Query: WITH table_name_error_bounds ( name, project, error, start_pos, end_pos ) AS ( SELECT name, project, error, 1, INSTR( error, ', ', 1 ) FROM table_name UNION ALL SELECT name, project, error, end_pos + 2, INSTR( error, ', ', end_pos + 2 ) FROM table_name_error_bounds WHERE end_pos > 0 ) SELECT name, project, CASE end_pos WHEN 0 THEN SUBSTR( error, start_pos ) ELSE SUBSTR( error, start_pos, end_pos - start_pos ) END AS error FROM table_name_error_bounds Output: NAME | PROJECT | ERROR ---: | :------ | :---- 108 | test | Err1 109 | test2 | Err1 108 | test | Err2 108 | test | Err3 db<>fiddle here
If you have Oracle APEX 5.1 or later installed, you can use the convenient APEX_STRING.split function, e.g.: select q.Name, q.Project, s.column_value as Error from mytable q, APEX_STRING.split(q.Error, ',') s The second parameter is the delimiter string. It also accepts a 3rd parameter to limit how many splits you want it to perform. https://docs.oracle.com/en/database/oracle/application-express/20.1/aeapi/SPLIT-Function-Signature-1.html#GUID-3BE7FF37-E54F-4503-91B8-94F374E243E6
i had used the DBMS_UTILITY.comma_to _table function actually its working the code as follows declare l_tablen BINARY_INTEGER; l_tab DBMS_UTILITY.uncl_array; cursor cur is select * from qwer; rec cur%rowtype; begin open cur; loop fetch cur into rec; exit when cur%notfound; DBMS_UTILITY.comma_to_table ( list => rec.val, tablen => l_tablen, tab => l_tab); FOR i IN 1 .. l_tablen LOOP DBMS_OUTPUT.put_line(i || ' : ' || l_tab(i)); END LOOP; end loop; close cur; end; i had used my own table and column names
Understanding MultiSet in Oracle & advantages,importance of it
I have seen a lot of answer's using Multiset & AS SYS.ODCIVARCHAR2LIST etc.. and I am not able to understand the logic behind/ how it works out. Please find below example (actually taken from another question). Request you to please explain the workflow/working for understanding multiset. And yes I tried to read the documents related to it. CREATE TABLE table_name ( Id, Column1, Column2 ) AS SELECT 1, 'A,B,C', 'H' FROM DUAL UNION ALL SELECT 2, 'D,E', 'J,K' FROM DUAL UNION ALL SELECT 3, 'F', 'L,M,N' FROM DUAL; Query: SELECT t.id, c1.COLUMN_VALUE AS c1, c2.COLUMN_VALUE AS c2 FROM table_name t CROSS JOIN TABLE( CAST( MULTISET( SELECT REGEXP_SUBSTR( t.Column1, '[^,]+', 1, LEVEL ) FROM DUAL CONNECT BY LEVEL <= REGEXP_COUNT( t.Column1, '[^,]+' ) ) AS SYS.ODCIVARCHAR2LIST ) ) c1 CROSS JOIN TABLE( CAST( MULTISET( SELECT REGEXP_SUBSTR( t.Column2, '[^,]+', 1, LEVEL ) FROM DUAL CONNECT BY LEVEL <= REGEXP_COUNT( t.Column2, '[^,]+' ) ) AS SYS.ODCIVARCHAR2LIST ) ) c2 Result:: | ID | C1 | C2 | |----|----|----| | 1 | A | H | | 1 | B | H | | 1 | C | H | | 2 | D | J | | 2 | D | K | | 2 | E | J | | 2 | E | K | | 3 | F | L | | 3 | F | M | | 3 | F | N | Thanks in advance.
Oracle offers three different types of collection, on other languages term "array" is more common. Associative array (or index-by table) VARRAY (variable-size array) Nested table Have a look at Collections Types in order to see the differences and where which one can be used. Start with something simple, e.g. create type number_table_type as table of number; You can use them in several ways, the three selects are equivalent. They all select table emp and stores emp_id values into Nested table emp_T. create table emp ( emp_id number, emp_name varchar2(100)); insert into emp values (10, 'Scott'); insert into emp values (20, 'King'); insert into emp values (30, 'Tiger'); declare emp_T number_table_type: begin select cast(collect(emp_id) as number_table_type) into emp_T from emp; select emp_id bulk collect into emp_T from emp; SELECT CAST(MULTISET(SELECT emd_id FROM emp) AS number_table_type) into emp_T FROM dual; emp_T := number_table_type(10,20,30); for i in 1..3 loop emp_T.EXTEND; emp_T(i) := 10*i; end loop; end; For the opposite way, i.e. transform a Nested table into "normal" table use TABLE function: select * from TABLE(emp_T); Oracle provides Multiset Operators and Multiset Conditions. They offer function like join two arrays, make values distinct, etc. Many times I see developers writing a LOOP in their code where a Mulitset Operator/Condition would do the same stuff with a single command. Once you are familiar with such basic array you could also try more complex structures. However, according to my feeling such complex structures are quite common in training material and documentation but hardly used in "real life". Oracle strength is to store and manipulate relational data, rather than object oriented data. SYS.ODCIVARCHAR2LIST, et al. are just some predefined types. Actually it does not matter wether you use them or create you own types.
Well, you should review such a code from the deepest level and navigate up, in order to figure out what's going on. This is based on the first row of your table: as you can see, that "regexp" magic converts your comma separated values (i.e. column) into rows. SQL> with test as 2 (select 1, 'A,B,C' column1 from dual) 3 select regexp_substr(t.column1, '[^,]+', 1, level) 4 from test t 5 connect by level <= regexp_count(t.column1, '[^,]+'); REGEXP_SUBSTR(T.COLU -------------------- A B C SQL> MULTISET creates a "collection" of those values, while CAST "converts" it into a SYS.ODCIVARCHAR2LIST type. It is, as you can see, owned by SYS and acts as if you created your own type (using the CREATE TYPE command) which contains VARCHAR2 values. When types are "simple" as this one, or the one that contains numbers (so you'd use SYS.ODCINUMBERLIST), you can use predefined one instead of creating your own. Therefore, that collection contains A, B and C. Finally, TABLE function produces a collection of rows that can be queried, just as if it was an "ordinary" table.
REGEXP to capture values delimited by a set of delimiters
My column value looks something like below: [Just an example i created] {BASICINFOxxxFyyy100x} {CONTACTxxx12345yyy20202x} It can contain 0 or more blocks of data... I have created the below query to split the blocks with x as (select '{BASICINFOxxxFyyy100x}{CONTACTxxx12345yyy20202x}' a from dual) select REGEXP_SUBSTR(a,'({.*?x})',1,rownum,null,1) from x connect by rownum <= REGEXP_COUNT(a,'x}') However I would like to further split the output into 3 columns like below: ColumnA | ColumnB | ColumnC ------------------------------ BASICINFO | F |100 CONTACT | 12345 |20202 The delimiters are always standard. I failed to create a pretty query which gives me the desired output. Thanks in advance.
SQL Fiddle Oracle 11g R2 Schema Setup: CREATE TABLE your_table ( str ) AS SELECT '{BASICINFOxxxFyyy100x}{CONTACTxxx12345yyy20202x}' from dual / Query 1: select REGEXP_SUBSTR( t.str, '\{([^}]*?)xxx([^}]*?)yyy([^}]*?)x\}', 1, l.COLUMN_VALUE, NULL, 1 ) AS col1, REGEXP_SUBSTR( str, '\{([^}]*?)xxx([^}]*?)yyy([^}]*?)x\}', 1, l.COLUMN_VALUE, NULL, 2 ) AS col2, REGEXP_SUBSTR( str, '\{([^}]*?)xxx([^}]*?)yyy([^}]*?)x\}', 1, l.COLUMN_VALUE, NULL, 3 ) AS col3 FROM your_table t CROSS JOIN TABLE( CAST( MULTISET( SELECT LEVEL FROM DUAL CONNECT BY LEVEL <= REGEXP_COUNT( t.str,'\{([^}]*?)xxx([^}]*?)yyy([^}]*?)x\}') ) AS SYS.ODCINUMBERLIST ) ) l Results: | COL1 | COL2 | COL3 | |-----------|-------|-------| | BASICINFO | F | 100 | | CONTACT | 12345 | 20202 | Note: Your query: select REGEXP_SUBSTR(a,'({.*?x})',1,rownum,null,1) from x connect by rownum <= REGEXP_COUNT(a,'x}') Will not work when you have multiple rows of input - In the CONNECT BY clause, the hierarchical query has nothing to restrict it connecting Row1-Level2 to Row1-Level1 or to Row2-Level1 so it will connect it to both and as the depth of the hierarchies gets greater it will create exponentially more duplicate copies of the output rows. There are hacks you can use to stop this but it is much more efficient to put the row generator into a correlated sub-query which can then be CROSS JOINed back to the original table (it is correlated so it won't join to the wrong rows) if you are going to use hierarchical queries. Better yet would be to fix your data structure so you are not storing multiple values in delimited strings.
SQL> with x as 2 (select '{BASICINFOxxxFyyy100x}{CONTACTxxx12345yyy20202x}' a from dual 3 ), 4 y as ( 5 select REGEXP_SUBSTR(a,'({.*?x})',1,rownum,null,1) c1 6 from x 7 connect by rownum <= REGEXP_COUNT(a,'x}') 8 ) 9 select 10 substr(c1,2,instr(c1,'xxx')-2) z1, 11 substr(c1,instr(c1,'xxx')+3,instr(c1,'yyy')-instr(c1,'xxx')-3) z2, 12 rtrim(substr(c1,instr(c1,'yyy')+3),'x}') z3 13 from y; Z1 Z2 Z3 --------------- --------------- --------------- BASICINFO F 100 CONTACT 12345 20202
Here is another solution, which is derived from the place you left. Your query had already resulted into splitting of a row to 2 row. Below will make it in 3 columns: WITH x AS (SELECT '{BASICINFOxxxFyyy100x}{CONTACTxxx12345yyy20202x}' a FROM DUAL), -- Your query result here tbl AS ( SELECT REGEXP_SUBSTR (a, '({.*?x})', 1, ROWNUM, NULL, 1) Col FROM x CONNECT BY ROWNUM <= REGEXP_COUNT (a, 'x}')) --- Actual Query SELECT col, REGEXP_SUBSTR (col, '(.*?{)([^x]+)', 1, 1, '', 2) AS COL1, REGEXP_SUBSTR (REGEXP_SUBSTR (col, '(.*?)([^x]+)', 1, 2, '', 2), '[^y]+', 1, 1) AS COL2, REGEXP_SUBSTR (REGEXP_SUBSTR (col, '[^y]+x', 1, 2), '[^x]+', 1, 1) AS COL3 FROM tbl; Output: SQL> / COL COL1 COL2 COL3 ------------------------------------------------ ------------------------------------------------ ------------------------------------------------ ------------------------------------------------ {BASICINFOxxxFyyy100x} BASICINFO F 100 {CONTACTxxx12345yyy20202x} CONTACT 12345 20202
How to conditionally join a table function in Oracle SQL, which has a primary table row column as an argument, without excessive function calls?
I have the following issue: Every row in my base table have a flag column ('flg' in the listing below) and an function argument column ('arg'). If in a row #N the flag is 'Y', then function has to be called; after, it shall return, let's say, a column (actually a bunch of them, but i'll simplify as much as i can). And finally, the row #N should transform into a sub-table - full join of a row #N and the column, returned by the function. If the flag is 'N' then a result for the row #N shall be as the row itself plus NULL in that "function return column". So here's an example: create or replace package SIEBEL.TEST_PACKAGE as type ret_type is table of number; function testFunc(inp number) return ret_type pipelined; end TEST_PACKAGE; / create or replace package body SIEBEL.TEST_PACKAGE is function testFunc(inp number) return ret_type pipelined is i number; begin dbms_output.put_line('Function call, arg = ' || to_char(inp)); if (inp is null OR inp = 0) then pipe row (null); else for i in 1..inp loop pipe row (i); end loop; end if; end testFunc; end TEST_PACKAGE; / with base_table as ( select 'Shall invoke' col0, 'Y' flg, '2' arg from dual union select 'Shall not invoke' col0, 'N' flg, '0' arg from dual ) select * from base_table t0, table(siebel.test_package.testFunc(t0.arg)) t1; It will return what i actually want: COL0 | FLG | ARG | COLUMN_VALUE -------------------------------------------- Shall invoke | Y | 2 | 1 Shall invoke | Y | 2 | 2 Shall not invoke | N | 0 | The thing is, even for the 'N' flag the function is still called - the database output will show Function call, arg = 2 Function call, arg = 0 If a real world i have hundreds of records with 'Y' flag, ~100K with 'N'. Alse my actual function is much more complicated and have a lot of stuff in its namespace, so every function call is crucial in terms of perfomance. What i do want is the database output for the example: Function call, arg = 2 I could achieve it with with base_table as ( select 'Shall invoke' col0, 'Y' flg, '2' arg from dual union select 'Shall not invoke' col0, 'N' flg, '0' arg from dual ) select t.*, t1.column_value from base_table t, table(SIEBEL.TEST_PACKAGE.testFunc(t.arg)) t1 where t.flg = 'Y' union all select t.*, null as column_value from base_table t where t.flg = 'N'; but then all indexes became useless - every 'order by' instruction will take alot to complete. Please, help me to achieve the desired behaviour of function calls and still to save the primal rows order. Feel free to ask me if anything is not clear. Best Regards, Alexey
For flag = "N" the function is not called if you conditionally join set serveroutput on with base_table as ( select 'Shall invoke' col0, 'Y' flg, '2' arg from dual union select 'Shall not invoke' col0, 'N' flg, '0' arg from dual ) select * from base_table t0 left join table( test_package.testFunc(t0.arg) ) t1 on (t0.flg = 'Y'); Script Output Package created. Package body created. COL0 FLG ARG COLUMN_VALUE ---------------- --- --- ------------ Shall invoke Y 2 1 Shall invoke Y 2 2 Shall not invoke N 0 3 rows selected. Server Output: Function call, arg = 2
To avoid the function call for flag = 'N', filter out the row. Add where flg='Y' in the filter predicate. For better performance, create an index on flg column. SQL> set serveroutput on SQL> with base_table as 2 ( 3 select 'Shall invoke' col0, 'Y' flg, '2' arg from dual 4 union 5 select 'Shall not invoke' col0, 'N' flg, '0' arg from dual 6 ) 7 SELECT * FROM base_table t0, TABLE(test_package.testFunc(t0.arg)) t1 8 where flg='Y'; COL0 F A COLUMN_VALUE ---------------- - - ------------ Shall invoke Y 2 1 Shall invoke Y 2 2 Function call, arg = 2 SQL> Edit To have the rows for 'flg='N' also in the output, you could do an OUTER JOIN, no need of UNION. For example, SQL> CREATE table base_table AS 2 select * from( 3 ( SELECT 'Shall invoke' col0, 'Y' flg, '2' arg FROM dual 4 UNION 5 SELECT 'Shall not invoke' col0, 'N' flg, '0' arg FROM dual 6 ) 7 ); Table created. SQL> SQL> SELECT * 2 FROM base_table t0 3 LEFT JOIN TABLE( test_package.testFunc(t0.arg) ) t1 4 ON (t0.flg = 'Y'); COL0 F A COLUMN_VALUE ---------------- - - ------------ Shall invoke Y 2 1 Shall invoke Y 2 2 Shall not invoke N 0 SQL> You can check the explain plan to see how the function is called and how many times is it executed. SQL> EXPLAIN PLAN FOR 2 SELECT * 3 FROM base_table t0 4 LEFT JOIN TABLE( test_package.testFunc(t0.arg) ) t1 5 ON (t0.flg = 'Y'); Explained. SQL> SQL> SELECT * FROM TABLE(dbms_xplan.display); PLAN_TABLE_OUTPUT -------------------------------------------------------------------------------------------------------- Plan hash value: 2726614787 -------------------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 16336 | 510K| 61 (0)| 00:00:01 | | 1 | NESTED LOOPS OUTER | | 16336 | 510K| 61 (0)| 00:00:01 | | 2 | TABLE ACCESS FULL | BASE_TABLE | 2 | 38 | 3 (0)| 00:00:01 | | 3 | VIEW | VW_LAT_D4FD8C38 | 8168 | 103K| 29 (0)| 00:00:01 | |* 4 | FILTER | | | | | | | 5 | COLLECTION ITERATOR PICKLER FETCH| TESTFUNC | 8168 | 16336 | 29 (0)| 00:00:01 | -------------------------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 4 - filter("T0"."FLG"='Y') 17 rows selected. SQL>