Oracle PL/SQL Tokenize String with empty position - oracle

I've a String like this:
AAA,BBB,,DDD
And i would like to tokenize it using comma and retrieve a table like this:
VALUE LEVEL
AAA 1
BBB 2
(null) 3
DDD 4
I need to know the String and in witch position i found it, without missing null String.
I've tried a code like this but i miss the empty position:
SELECT regexp_substr ('AAA,BBB,,DDD', '[^,]+', 1, level), level
FROM dual
CONNECT BY LEVEL <= LENGTH(regexp_replace ('AAA,BBB,,DDD', '[^,]+'));
The output is this:
VALUE LEVEL
AAA 1
BBB 2
DDD 3

Another simple answer is that replacing comma(,) by space with comma(, ) like below
SELECT trim(regexp_substr (replace('AAA,BBB,,DDD',',',', '), '[^,]+', 1, level)), level
FROM dual
CONNECT BY LEVEL <= REGEXP_COUNT (replace('AAA,BBB,,DDD',',',', '), '[^,]+');
this also works http://sqlfiddle.com/#!4/b255d/26

SELECT token, lvl FROM (
SELECT regexp_substr ('AAA,BBB,,DDD', '[^,]*', 1, LEVEL) token, LEVEL lvl,
lag(regexp_substr ('AAA,BBB,,DDD', '[^,]*', 1, LEVEL)) over(order by level) prev_token
FROM dual
CONNECT BY LEVEL <= LENGTH(regexp_replace ('AAA,BBB,,DDD', '[^,]+'))*2
) WHERE prev_token is null;

in oracle 11g and upper you can do something like this query:
with
tab1(pointer,test,split_test) as
(select
1 as pointer,test,substr(test,0,case when instr(test,',',1,1) = 0 then LENGTH(test)
else instr(test,',',1,1)-1 end) split_test from table1
union all
select
pointer + 1 as pointer,test,
substr(test,instr(test,',',1,pointer) + 1,case when instr(test,',',1,pointer + 1) = 0 then LENGTH(test) else
instr(test,',',1,pointer + 1) - instr(test,',',1,pointer) - 1 end) split_test
from tab1 where pointer - 1 < LENGTH(test)-LENGTH(REPLACE(test,',','')))
select split_test as "value",pointer as "level" from tab1;
SQL Fiddle

Related

Regular Expression with Connect by

I have this query:
select regexp_substr('1,2,3,4,5','[^,]+',1,level)
from test1
connect by instr('1,2,3,4,5',',',1,level-1)>0;
Please help me to understand this query especially the use of a level and connect by and level-1.
LEVEL and CONNECT BY are used to generate sequences, see this simple example:
select level
from dual
connect by level < 10;
LEVEL
=======
1
2
3
4
5
6
7
8
9
instr('1,2,3,4,5', ',' , 1, level-1) > 0 counts the number of elements separated by comma maybe this version is easier to understand, it does the same:
select regexp_substr('1,2,3,4,5','[^,]+',1,level)
from dual
connect by LEVEL <= REGEXP_COUNT('1,2,3,4,5', ',')+1;
LEVEL <= REGEXP_COUNT('1,2,3,4,5', ',')+1 or instr('1,2,3,4,5', ',' , 1, level-1) > 0 means "run five times".
So, your select is basically a shortcut for
select regexp_substr('1,2,3,4,5','[^,]+', 1, 1) from dual union all
select regexp_substr('1,2,3,4,5','[^,]+', 1, 2) from dual union all
select regexp_substr('1,2,3,4,5','[^,]+', 1, 3) from dual union all
select regexp_substr('1,2,3,4,5','[^,]+', 1, 4) from dual union all
select regexp_substr('1,2,3,4,5','[^,]+', 1, 5) from dual;
#Wernfried's answer is excellent, but you should know there is a big risk with the regex of the format '[^,]+' which one sees often as an example of how to parse delimited strings.
This only works when all elements of the list are present. For an eye-opener, try it with the 2nd element as NULL:
select regexp_substr('1,,3,4,5', '[^,]+', 1, 3) from dual;
REGEXP_SUBSTR('1,,3,4,5','[^,]+',1,3)
-------------------------------------
4
1 row selected.
WHAT? '4' is most definitely NOT the 3rd element of that list! As you can see the NULL is not handled.
Please use this format of REGEXP_SUBSTR which does handle NULL list elements:
select regexp_substr('1,,3,4,5','(.*?)(,|$)', 1, 3, NULL, 1) from dual;
REGEXP_SUBSTR('1,,3,4,5','(.*?)(,|$)',1,3,NULL,1)
-------------------------------------------------
3
1 row selected.
The regex defines defines 2 groups, an optional set of any characters followed by a comma or the end of the line. The arguments to REGEXP_SUBSTR
say to return the 1st group of the 3rd instance of this match.

Show multiple display values on interactive report column with multiple values based on LOV (checklist)

I want to show the display values in the column, but the column is a string with entries like '1:3', '2', '1:2:4', which are the return values based on a LOV with static values.
If I set the column as "Display text (Based on LOV)" it will only work for those entries with one number only, like '3' or '1'.
I thought of normalizing the table, but then I would have a multi table form.
This should be simple, yet I cannot figure out how to do this?
select p.COD,
UNIDADE_PROPONENTE,
CAUSA_FUND,
CAUSAS_RELAC,
TITULO,
DESC_SOLUC,
listagg(a.acao, ';<br><br>') within group (order by null) as "a.acao",
OUTRA_ACAO,
SUGESTAO,
SITUACAO
from TB_PROPOSTA p, TB_ACAO a,
table(cast(multiset(select level from dual
connect by level <=regexp_count(p.acao,':') + 1)
as sys.odcinumberlist)) x
where
a.cod = regexp_substr(p.acao, '[^:]+', 1, x.column_value)
and
(:P2_XUNIDADE_RESPONSAVEL = UNIDADE_PROPONENTE OR :P2_XUNIDADE_RESPONSAVEL IS NULL)
and
(INSTR(':'|| p.acao ||':', ':' || :P2_XACAO || ':') > 0 OR :P2_XACAO IS NULL)
group by p.cod, p.acao, unidade_proponente, causa_fund, causas_relac, titulo, desc_soluc, outra_acao, sugestao, situacao
order by p.cod, p.acao;
edit:
Now I either get missing keyword error or sql command not properly ended.
This one gives SQL COMMAND NOT PROPERLY ENDED
select p.COD,
UNIDADE_PROPONENTE,
CAUSA_FUND,
CAUSAS_RELAC,
TITULO,
DESC_SOLUC,
listagg(a.acao, ';<br><br>') within group (order by null) as "a.acao",
OUTRA_ACAO,
SUGESTAO,
SITUACAO
from TB_PROPOSTA p
cross join table(cast(multiset(select level from dual
connect by level <=regexp_count(p.acao,':') + 1)
as sys.odcinumberlist)) x
left join TB_ACAO.ACAO
on TB_ACAO.COD = regexp_substr(TB_PROPOSTA.ACAO, '[^:]+', 1, x.column_value)
where
(:P2_XUNIDADE_RESPONSAVEL )= UNIDADE_PROPONENTE OR :P2_XUNIDADE_RESPONSAVEL IS NULL)
AND
(:P2_XACAO IN (p.ACAO) OR :P2_XACAO IS NULL)
group by p.cod, p.acao, unidade_proponente, causa_fund, causas_relac, titulo, desc_soluc, outra_acao, sugestao, situacao
order by p.cod, p.acao;
And if I change the "cross join" to only "join" I get the "Missing keyword (ORA-06550: line 17, column 1: ORA-00905: missing keyword) right before the "where"
Thank you for additional explanation.
Would something like this help? My TEST table acts as your report query, while the rest of it splits colon separated values. It is a SQL*Plus example, just to show you what's going on.
SQL> with test (situacao, tipo_acao) as
2 (select 'Aguardando resposta', '1:2:3' from dual union
3 select 'Aguardando resposta', '5' from dual
4 )
5 select
6 situacao,
7 'This is value ' || regexp_substr(tipo_acao, '[^:]+', 1, column_value) tipo_acao
8 from test,
9 table(cast(multiset(select level from dual
10 connect by level <= regexp_count(tipo_acao, ':') + 1)
11 as sys.odcinumberlist));
SITUACAO TIPO_ACAO
------------------- ----------------------------------
Aguardando resposta This is value 1
Aguardando resposta This is value 2
Aguardando resposta This is value 3
Aguardando resposta This is value 5
SQL>
[EDIT, after creating Page 5 in your Apex application]
I think I got it - have a look at Page 5 (created as copy of your page 2 so that I wouldn't spoil it). Its column list is reduced, but you'll get the general idea. Query looks like this; I hope it's OK:
select p.cod,
s.situacao,
p.tipo_acao,
listagg(t.acao, ', ') within group (order by null) acao
from tb_proposta p,
tipo_situacao s,
vw_unid v,
tipo_acao t,
table(cast(multiset(select level from dual
connect by level <= regexp_count(p.tipo_acao, ':') + 1)
as sys.odcinumberlist)) x
where s.cod = p.proposta_status
and p.cod_vw_unid = v.cod
and t.cod= regexp_substr(p.tipo_acao, '[^:]+', 1, x.column_value)
group by p.cod, s.situacao, p.tipo_acao
order by p.cod, p.tipo_acao;
[EDIT #2, ANSI outer join]
This is how you should try to do it. Also, CROSS JOIN and its place in query.
select p.cod,
s.situacao,
p.tipo_acao,
listagg(t.acao, ', ') within group (order by null) acao
from tb_proposta p
cross join table(cast(multiset(select level from dual
connect by level <= regexp_count(p.tipo_acao, ':') + 1)
as sys.odcinumberlist)) x
join tipo_situacao s on s.cod = p.proposta_status
join vw_unid v on v.cod = p.cod_vw_unid
left join tipo_acao t on t.cod = regexp_substr(p.tipo_acao, '[^:]+', 1, x.column_value)
group by p.cod, s.situacao, p.tipo_acao
order by p.cod, p.tipo_acao;

Parse Values with Delimiters Using REGEXP_SUPSTR in Oracle 10g

I have a table called TVL_DETAIL that contains column TVL_CD_LIST. Column TVL_CD_LIST contains three records:
TVL_CD_LIST:
M1180_Z6827
K5900_Z6828
I2510
I've used the following code in an attempt to return the values only(so excluding the underscore):
SELECT
TVL_CD_LIST
FROM TVL_DETAIL
WHERE TVL_CD_LIST IN (SELECT regexp_substr(TVL_CD_LIST,'[^_]+', 1, level) FROM DUAL
CONNECT BY regexp_substr(TVL_CD_LIST,'[^_]+', 1, level) IS NOT NULL)
What I was expecting to see returned in separate rows was:
M1180
Z6827
K5900
Z6828
I2510
But it only returns I2510(which is the original value that doesn't contain an underscore).
What am I doing wrong? Any help is appreciated. Thanks!
To answer your question, you are querying for the list where it matches a sub-element and that will only happen where the list is comprised of one element. What you really wanted to select are the sub-elements themselves.
Note: Explanation of why parsing strings using the regex form '[^_]+' is bad here: https://stackoverflow.com/a/31464699/2543416
You want to parse the list, selecting the elements:
SQL> with TVL_DETAIL(TVL_CD_LIST) as (
select 'M1180_Z6827' from dual union
select 'K5900_Z6828' from dual union
select 'I2510' from dual
)
SELECT distinct regexp_substr(TVL_CD_LIST, '(.*?)(_|$)', 1, level, NULL, 1) element
FROM TVL_DETAIL
CONNECT BY level <= LENGTH(regexp_replace(TVL_CD_LIST, '[^_]', '')) + 1;
-- 11g CONNECT BY level <= regexp_count(TVL_CD_LIST, '_') + 1;
ELEMENT
-----------
Z6827
K5900
M1180
I2510
Z6828
SQL>
And this is cool if you want to track by row and element within row:
SQL> with TVL_DETAIL(row_nbr, TVL_CD_LIST) as (
select 1, 'M1180_Z6827' from dual union
select 2, 'K5900_Z6828' from dual union
select 3, 'I2510' from dual
)
SELECT row_nbr, column_value substring_nbr,
regexp_substr(TVL_CD_LIST, '(.*?)(_|$)', 1, column_value, NULL, 1) element
FROM TVL_DETAIL,
TABLE(
CAST(
MULTISET(SELECT LEVEL
FROM dual
CONNECT BY level <= LENGTH(regexp_replace(TVL_CD_LIST, '[^_]', '')) + 1
-- 11g CONNECT BY LEVEL <= REGEXP_COUNT(TVL_CD_LIST, '_')+1
) AS sys.OdciNumberList
)
)
order by row_nbr, substring_nbr;
ROW_NBR SUBSTRING_NBR ELEMENT
---------- ------------- -----------
1 1 M1180
1 2 Z6827
2 1 K5900
2 2 Z6828
3 1 I2510
SQL>
EDIT: Oops, edited to work with 10g as REGEXP_COUNT is not available until 11g.
The query you have used creates the list but you are comparing the list of record with column it self using the in clause, as such M1180 or Z6827 cannot be equal to M1180_Z6827 and so for K5900_Z6828. I2510 has only one value so it gets matched.
You can use below query if your requirement is exactly what you have mentioned in your desired output.
SQL> WITH tvl_detail AS
2 (SELECT 'M1180_Z6827' tvl_cd_list FROM dual
3 UNION ALL
4 SELECT 'K5900_Z6828' FROM dual
5 UNION ALL
6 SELECT 'I2510' FROM dual)
7 ---------------------------
8 --- End of data preparation
9 ---------------------------
10 SELECT regexp_substr(tvl_cd_list, '[^_]+', 1, LEVEL) AS tvl_cd_list
11 FROM tvl_detail
12 CONNECT BY regexp_substr(tvl_cd_list, '[^_]+', 1, LEVEL) IS NOT NULL
13 AND PRIOR tvl_cd_list = tvl_cd_list
14 AND PRIOR sys_guid() IS NOT NULL;
OUTPUT:
TVL_CD_LIST
--------------------------------------------
I2510
K5900
Z6828
M1180
Z6827

How to reverse a string in Oracle (11g) SQL without using REVERSE() function

I am trying to reverse a string without using REVERSE function. I came across one example which is something like:
select listagg(letter) within group(order by lvl)
from
(SELECT LEVEL lvl, SUBSTR ('hello', LEVEL*-1, 1) letter
FROM dual
CONNECT BY LEVEL <= length('hello'));
Apart from this approach,is there any other better approach to do this?
If you're trying to avoid the undocumented reverse() function you could use the utl_raw.reverse() function instead, with appropriate conversion too and from RAW:
select utl_i18n.raw_to_char(
utl_raw.reverse(
utl_i18n.string_to_raw('Some string', 'AL32UTF8')), 'AL32UTF8')
from dual;
UTL_I18N.RAW_TO_CHAR(UTL_RAW.REVERSE(UTL_I18N.STRING_TO_RAW('SOMESTRING','AL32UT
--------------------------------------------------------------------------------
gnirts emoS
So that is taking an original value; doing utl_i18n.string_to_raw() on that; then passing that to utl_raw.reverse(); then passing the result of that back through utl_i18n.raw_to_char().
Not entirely sure how that will cope with multibyte characters, or what you'd want to happen to those anyway...
Or a variation from the discussion #RahulTripathi linked to, without the character set handling:
select utl_raw.cast_to_varchar2(utl_raw.reverse(utl_raw.cast_to_raw('Some string')))
from dual;
UTL_RAW.CAST_TO_VARCHAR2(UTL_RAW.REVERSE(UTL_RAW.CAST_TO_RAW('SOMESTRING')))
--------------------------------------------------------------------------------
gnirts emoS
But that thread also notes it only works for single-byte characters.
You could do it like this:
with strings as (select 'hello' str from dual union all
select 'fred' str from dual union all
select 'this is a sentance.' from dual)
select str,
replace(sys_connect_by_path(substr (str, level*-1, 1), '~|'), '~|') rev_str
from strings
where connect_by_isleaf = 1
connect by prior str = str --added because of running against several strings at once
and prior sys_guid() is not null --added because of running against several strings at once
and level <= length(str);
STR REV_STR
------------------- --------------------
fred derf
hello olleh
this is a sentance. .ecnatnes a si siht
N.B. I used a delimiter of ~| simply because that's something unlikely to be part of your string. You need to supply a non-null delimiter to the sys_connect_by_path, hence why I didn't just leave it blank!
SELECT LISTAGG(STR) WITHIN GROUP (ORDER BY RN DESC)
FROM
(
SELECT ROWNUM RN, SUBSTR('ORACLE',ROWNUM,1) STR FROM DUAL
CONNECT BY LEVEL <= LENGTH('ORACLE')
);
You can try using this function:
SQL> ed
Wrote file afiedt.buf
1 with t as (select 'Reverse' as txt from dual)
2 select replace(sys_connect_by_path(ch,'|'),'|') as reversed_string
3 from (
4 select length(txt)-rownum as rn, substr(txt,rownum,1) ch
5 from t
6 connect by rownum <= length(txt)
7 )
8 where connect_by_isleaf = 1
9 connect by rn = prior rn + 1
10* start with rn = 0
SQL> /
Source
select listagg(rev)within group(order by rownum)
from
(select substr('Oracle',level*-1,1)rev from dual
connect by level<=length('Oracle'));

Oracle Count how much values in varchar

I'm testing with a query:
SELECT '-1,-2,-4,-6' "Verdunning"
FROM DUAL
Now I need to know how much values are in the varchar: '-1,-2,-4,-6'. I want to have 4 back.
and when it is '-1,-2,-4,-6,-8' i need to get 5 back. How to do this in an oracle select statement?
Fiddle
SELECT LENGTH(Verdunning) - LENGTH(REPLACE(Verdunning, ',', '')) + 1
FROM (SELECT '-1,-2,-4,-6' AS Verdunning FROM DUAL) T
find number of , then add 1 to get count
Select LEN(column) – LEN(REPLACE(column, ',', ''))+1 as comma_separted_values_count
From table
Try this:
SELECT
REGEXP_COUNT ( REGEXP_REPLACE ( '-1,-2,-4,-6', '".*?"' ), ',' ) + 1
FROM
DUAL;
Using regexp_substr.
select count(regexp_substr( '-1,-2,-4,-6','[^,]+', 1, level)) from dual
connect by regexp_substr( '-1,-2,-4,-6', '[^,]+', 1, level) is not null;

Resources