Oracle Database 7.3 was the first to support inline views – using the select * from (select * from) syntax. Oracle Database 9i added the with clause for subquery factoring, using a syntax like with vw as (select * from) , vw2 as (select * from vw) select * from vw2 join vw join tbl. Oracle Database release 12c (12.1) added a new feature into this mix: Inline PL/SQL functions, using a syntax like: with function x (p_in varchar2) return varchar2 is begin return ‘X’;end; select x(‘ahaha’) from dual. This feature is described in various articles, such as this one: https://technology.amis.nl/2013/06/25/oracle-database-12c-in-line-plsql-functions-in-sql-queries/.
Today I was wondering how to combine in a single query both an inline function and an inline query. I was not sure what the exact syntax had to be – and to be honest, I was not even sure if the combination was supported at all. The good news: the combination is supported, and the syntax is very straightforward. There is just a comma less than you (I) perhaps would expect.
This next query uses a PL/SQL string tokenizer to split a string into its constituents (using a comma as a delimiter).
with
function string_tokenizer( p_string in varchar2, p_token in varchar2 default ',')
return string_table
is
l_token VARCHAR2(50) ;
i PLS_INTEGER := 1 ;
l_tokens string_table := string_table();
FUNCTION get_token(
p_input_string IN VARCHAR2, -- input string
p_token_number IN PLS_INTEGER, -- token number
p_delimiter IN VARCHAR2 DEFAULT ',' -- separator character
)
RETURN VARCHAR2
IS
l_temp_string VARCHAR2(500) := p_delimiter || p_input_string ;
l_pos1 number ;
l_pos2 number ;
BEGIN
l_pos1 := INSTR( l_temp_string, p_delimiter, 1, p_token_number ) ;
IF l_pos1 > 0
THEN
l_pos2 := INSTR( l_temp_string, p_delimiter, 1, p_token_number + 1) ;
IF l_pos2 = 0
THEN
l_pos2 := LENGTH( l_temp_string ) + 1 ;
END IF ;
RETURN( SUBSTR( l_temp_string, l_pos1+1, l_pos2 - l_pos1-1 ) ) ;
ELSE
RETURN NULL ;
END IF ;
END get_token;
BEGIN
LOOP
l_token := get_token( p_string, i , ',') ;
EXIT WHEN l_token IS NULL ;
l_tokens.extend;
l_tokens(l_tokens.last) := l_token;
i := i + 1 ;
END LOOP ;
return l_tokens;
end string_tokenizer;
tag_filter as
( select 'recent,comeback,early' selected_tags
from dual
)
, filter_tags as
( select string_tokenizer(selected_tags, ',') filter_tag
from tag_filter
)
select column_value tag
from table( (select filter_tag from filter_tags))
The result is simply
With a little bit help of a Scalar Subquery and the Table function – see this next query –
... ( select string_tokenizer(selected_tags, ',') filter_tag from tag_filter ) select column_value tag from table( (select filter_tag from filter_tags))
I can extract the elements from this nested table as if they are rows in a relation:
Note: a SQL query can contain multiple Inline PL/SQL Functions – with one invoking the other; I have rewritten the first query as:
with
FUNCTION get_token(
p_input_string IN VARCHAR2, -- input string
p_token_number IN PLS_INTEGER, -- token number
p_delimiter IN VARCHAR2 DEFAULT ',' -- separator character
)
RETURN VARCHAR2
IS
l_temp_string VARCHAR2(500) := p_delimiter || p_input_string ;
l_pos1 number ;
l_pos2 number ;
BEGIN
l_pos1 := INSTR( l_temp_string, p_delimiter, 1, p_token_number ) ;
IF l_pos1 > 0
THEN
l_pos2 := INSTR( l_temp_string, p_delimiter, 1, p_token_number + 1) ;
IF l_pos2 = 0
THEN
l_pos2 := LENGTH( l_temp_string ) + 1 ;
END IF ;
RETURN( SUBSTR( l_temp_string, l_pos1+1, l_pos2 - l_pos1-1 ) ) ;
ELSE
RETURN NULL ;
END IF ;
END get_token;
function string_tokenizer( p_string in varchar2, p_token in varchar2 default ',')
return string_table
is
l_token VARCHAR2(50) ;
i PLS_INTEGER := 1 ;
l_tokens string_table := string_table();
BEGIN
LOOP
l_token := get_token( p_string, i , ',') ;
EXIT WHEN l_token IS NULL ;
l_tokens.extend;
l_tokens(l_tokens.last) := l_token;
i := i + 1 ;
END LOOP ;
return l_tokens;
end string_tokenizer;
tag_filter as
( select 'recent,comeback,early' selected_tags
from dual
)
, filter_tags as
( select string_tokenizer(selected_tags, ',') filter_tag
from tag_filter
)
select column_value tag
from table( (select filter_tag from filter_tags))
