Expanding Oracle rows with comma-delimited values into multiple rows - oracle

I have a table in Oracle like the following:
KEY,VALS
k1,"a,b"
I need it to look like:
KEY,VAL
k1,a
k1,b
I did this with CONNECT BY and LEVEL, following an example:
with t as (
select 'k1' as key, 'a,b' as vals
from dual
)
select key, regexp_substr(vals, '[^,]+', 1, level) as val
from t
connect by LEVEL <= length(vals) - length(replace(vals, ',')) + 1
But when I have multiple rows in the table, and the vals can be comma-delimited values of different lengths, like:
KEY,VALS
k1,"a,b"
k2,"c,d,e"
I'm looking for a result like:
KEY,VAL
k1,a
k1,b
k2,c
k2,d
k2,e
But the naive approach above doesn't work because every level is connected with the one above it, resulting in:
with t as (
select 'k1' as key, 'a,b' as vals
from dual
union
select 'k2' as key, 'c,d,e' as vals
from dual
)
select key, regexp_substr(vals, '[^,]+', 1, level) as val
from t
connect by LEVEL <= length(vals) - length(replace(vals, ',')) + 1
KEY,VAL
k1,a
k1,b
k2,e
k2,d
k2,e
k2,c
k1,b
k2,e
k2,d
k2,e
I suspect I need some kind of CONNECT BY PRIOR condition, but I'm not sure what. When trying to match by keys:
connect by prior key = key
and LEVEL <= length(vals) - length(replace(vals, ',')) + 1
I get an ORA-01436: CONNECT BY loop in user data error.
What's the right approach here?

Option 1: Simple, fast string functions and a recursive query:
with t (key, vals) as (
SELECT 'k1', 'a,b' FROM DUAL UNION ALL
SELECT 'k2', 'c,d,e' FROM DUAL
),
bounds (key, vals, spos, epos) AS (
SELECT key, vals, 1, INSTR(vals, ',', 1)
FROM t
UNION ALL
SELECT key, vals, epos + 1, INSTR(vals, ',', epos + 1)
FROM bounds
WHERE epos > 0
)
SEARCH DEPTH FIRST BY key SET key_order
SELECT key,
CASE epos
WHEN 0
THEN SUBSTR(vals, spos)
ELSE SUBSTR(vals, spos, epos - spos)
END AS val
FROM bounds;
Option 2: Slower regular expressions in a LATERAL joined hierarchical query
This option requires Oracle 12 or later.
with t (key, vals) as (
SELECT 'k1', 'a,b' FROM DUAL UNION ALL
SELECT 'k2', 'c,d,e' FROM DUAL
)
SELECT key, val
FROM t
LEFT OUTER JOIN LATERAL (
SELECT regexp_substr(vals, '[^,]+', 1, level) AS val
FROM DUAL
CONNECT BY LEVEL <= REGEXP_COUNT(vals, '[^,]+')
)
ON (1 = 1)
Option 3: Recursive query correlating to parent rows.
This option is the slowest of the options as it needs to correlate between levels of the hierarchy and generate a GUID at each step (which is seemingly useless but prevents unnecessary recursion).
with t (key, vals) as (
SELECT 'k1', 'a,b' FROM DUAL UNION ALL
SELECT 'k2', 'c,d,e' FROM DUAL
)
SELECT key,
regexp_substr(vals, '[^,]+', 1, level) AS val
FROM t
CONNECT BY LEVEL <= REGEXP_COUNT(vals, '[^,]+')
AND PRIOR key = key
AND PRIOR SYS_GUID() IS NOT NULL;
Which all output:
KEY
VAL
k1
a
k1
b
k2
c
k2
d
k2
e
db<>fiddle here

Related

oracle how to manage scientific notation a thousands separator in same column

i have an imported dataset with text column that i have to use as number and contains digit with many differents format.
letting oracle autoconvert text to number raise error if a thousand separator is found
i write a simple routine to test oracle conversion:
SELECT val, TRUNC(val, 0), MOD(val, 1) - 1
from (
select '8E4' val from dual union
select '8E-4' val from dual union
select '1,234.567' val from dual union
select '1.234' val from dual
);
is there a way to manage it? thanks
You can use a case expression to choose between two format models, for example:
to_number(val,
case when instr(val, 'E') > 0 then '9EEEE' else '999G999G999D99999' end,
'nls_numeric_characters=.,')
So for your example you could do:
select val, num, trunc(num, 0), mod(num, 1) - 1
from (
select val,
to_number(val,
case when instr(val, 'E') > 0 then '9EEEE' else '999G999G999D99999' end,
'nls_numeric_characters=.,') as num
from (
select '8E4' val from dual union
select '8E-4' val from dual union
select '1,234.567' val from dual union
select '1.234' val from dual
)
);
VAL
NUM
TRUNC(NUM,0)
MOD(NUM,1)-1
8E4
80000
80000
-1
8E-4
.0008
0
-.9992
1,234.567
1234.567
1234
-.433
1.234
1.234
1
-.766
fiddle

How to Find specific values in oracle

i Have some values in my table like this,
column1
50,52,53,54,56,57,58,60,61,63,75 50,53,54 54,75
i want find values containing
53,54
returns
50,52,53,54,56,57,58,60,61,63,75 50,53,54
rows
or 50,52,53
returns
50,52,53,54,56,57,58,60,61,63,75
i tried;
WHERE Regexp_like(DIST_GRP,('53,54]'))
You can search for each number in the column and use AND to combine the filters:
SELECT *
FROM table_name
WHERE ','||column1||',' LIKE '%,54,%'
AND ','||column1||',' LIKE '%,75,%'
or
SELECT *
FROM table_name
WHERE ','||column1||',' LIKE '%,50,%'
AND ','||column1||',' LIKE '%,52,%'
AND ','||column1||',' LIKE '%,53,%'
or you can split the string and count to make sure you have all the matches:
SELECT t.*
FROM table_name t
CROSS APPLY (
SELECT 1
FROM DUAL
WHERE REGEXP_SUBSTR(t.column1, '\d+', 1, LEVEL) IN (50, 52, 53)
CONNECT BY LEVEL <= REGEXP_COUNT(t.column1, '\d+')
HAVING COUNT(DISTINCT REGEXP_SUBSTR(t.column1, '\d+', 1, LEVEL)) = 3
)
or you can create a collection data type and split the string and re-aggregate into that:
CREATE TYPE int_list AS TABLE OF INT;
then
SELECT t.*
FROM table_name t
CROSS APPLY (
SELECT CAST(
COLLECT(
TO_NUMBER(REGEXP_SUBSTR(t.column1, '\d+', 1, LEVEL))
)
AS INT_LIST
) AS list
FROM DUAL
CONNECT BY LEVEL <= REGEXP_COUNT(t.column1, '\d+')
) l
WHERE INT_LIST(50,52,53) SUBMULTISET OF l.list;
db<>fiddle here

sql placeholder rows

I have an apex item P_USERS which can have a value higher than the amount of rows returning from the query below.
I have a classic report which has the following query:
select
first_name,
last_name
from accounts
where account_role = 'Author'
order by account_nr;
I want placeholder rows to be added to the query (first_name = null, last_name = null etc.), if the total rows from the query is lesser than the value in the apex_item P_USERS.
Any tips on how to achieve this? Maybe with a LEFT join?
If you have more result than the minima you defined, you must add the rest with union.
Here is what you could try to adapt to your case:
SELECT i,c FROM (
select rownum i, c from (
select 'a' c from dual union all select 'b' from dual union all select 'd' from dual union all select 'be' from dual
)), (Select Rownum r From dual Connect By Rownum <= 3)
where (i(+)= r)
union select i,c from (select rownum i, c from (
select 'a' c from dual union all select 'b' from dual union all select 'd' from dual union all select 'be' from dual
)) where i>3
You may try to use a LEFT JOIN.
First, create a list of number until the limit you want like suggested here:
-- let's say you want 300 records
Select Rownum r From dual Connect By Rownum <= 300
Then you can use this to left join and have empty records:
SELECT C, R FROM
( select rownum i, c from (select 'a' c from dual union all select 'b' from dual) )
, ( Select Rownum r From dual Connect By Rownum <= 300)
where i(+)= r order by r
The above gives you an ordered list starting with 'a', 'b', then null until the end.
So you could adapt it to your case so:
SELECT F,L FROM
( select rownum i, f, l from (
select first_name f, last_name l
from accounts where account_role = 'Author'
order by account_nr) )
, ( Select Rownum r From dual Connect By Rownum <= 300)
where i(+)= r

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;

Result set different from query than from stored procedure in Oracle

I have a sp that is giving me wrong results. That sp returns a cursor which is missing some rows. Now
When we fire the query within the sp directly in a query window, correct results come up. The only difference in this query is that instead of a cursor, the query directly outputs the result set; i.e. no cursor is opened when we fire the query directly.
When we recompile the sp, correct results come up for some time, and then it is back to missing rows after some time.
Have not been able to make much progress on this. Any pointers on what to look for, how to make progress etc. are much appreciated. I can paste the sp in here if needed, and probably the sp can be rewritten in some other way to make the problem go away, but I am more interested in knowing root cause - what could be causing this strange problem?
SP
CREATE OR REPLACE PROCEDURE "SP_GETTAXSETTINGCHANNELINFO" (v_bid varchar2,
p_rdTaxSetting out SYS_REFCURSOR) as
vT_bid varchar2(100);
begin
vT_bid := v_bid;
Open p_rdTaxSetting for
Select taxmappingid, CHANNELNAME,PROPERTYNAME, PROPERTYID, TAXTYPE, ISPARTIALLYINCLUSIVE, PARTIALLYINCLUSIVEVALUE
FROM (
WITH ChannelData AS (
SELECT ONDEMAND_SELECTED_SOURCES AS OnDemandChannels,
SCHEDULED_SELECTED_SOURCES AS ScheduledChannels
FROM SUB_ORDER S
INNER JOIN CONTACT C ON S.Supplier_ID = C.Contact_ID
WHERE C.BID = vT_bid
AND S.STATUS='Complete' and current_ind = 1
),
Property_Data As (
SELECT
SC.PROPERTY_NUM AS PropertyID
,SC.Company_Name as PropertyName
,SC.competitor_number as PropertyOrder
FROM SUB_ORDER S
INNER JOIN CONTACT C ON S.Supplier_ID = C.Contact_ID
INNER JOIN SUB_ORDER_COMPETITOR SC ON S.Order_Id = SC.Order_ID
WHERE C.BID = vT_bid
AND S.STATUS='Complete' and current_ind = 1
),
ODChannel as (
select regexp_substr (OnDemandChannels, '[^,]+', 1, rn) as Channel
from (Select OnDemandChannels From ChannelData)
cross join
(select rownum rn
from (select max (length (regexp_replace (OnDemandChannels, '[^,]+'))) + 1 max_value
from (Select OnDemandChannels From ChannelData))
connect by level <= max_value)
where regexp_substr (OnDemandChannels, '[^,]+', 1, rn) is not null
),
SCDChannel As (
select regexp_substr (ScheduledChannels, '[^,]+', 1, rn) as Channel
from (Select ScheduledChannels From ChannelData)
cross join
(select rownum rn
from (select max (length (regexp_replace (ScheduledChannels, '[^,]+'))) + 1 max_value
from (Select ScheduledChannels From ChannelData))
connect by level <= max_value)
where regexp_substr (ScheduledChannels, '[^,]+', 1, rn) is not null
),
ChaData1 As (
Select
CASE lower(Channel)
WHEN 'galileo' then 'GDS'
else Channel
end AS Channel
from ODChannel
),
ChaData2 As (
Select CASE lower(Channel)
WHEN 'galileo' then 'GDS'
else Channel
end AS Channel FROM SCDChannel
),
PropData As (
Select Distinct PropertyID,PropertyName from Property_Data
order by PropertyID
),
AllChannel AS (
Select Channel From ChaData1
union
Select Channel From ChaData2
),
Prop_Cha_Data As (
Select Distinct Channel,PropertyID,PropertyName from AllChannel,Property_Data
order by Channel,PropertyID
),
Tax_Channel_Prop_Exists AS ( select
CASE lower(CHANNELNAME)
WHEN 'galileo' then 'GDS'
else CHANNELNAME
end AS CHANNELNAME,
PropertyName As PROPERTYNAME, TCP.PROPERTYID As PROPERTYID,TAXTYPE,
ISPARTIALLYINCLUSIVE, PARTIALLYINCLUSIVEVALUE,taxmappingid
from TAXSETTING_CHANNEL_PROPERTY TCP
INNER JOIN AllChannel PC On TRIM (CASE lower(TCP.CHANNELNAME)
WHEN 'galileo' then 'GDS'else TCP.CHANNELNAME end) = TRIM(PC.Channel)
INNER JOIN PropData CP On TCP.PROPERTYID = CP.PropertyID
where TCP.BID=vT_bid
)
Select Distinct taxmappingid, CHANNELNAME,PROPERTYNAME, PROPERTYID, TAXTYPE, ISPARTIALLYINCLUSIVE, PARTIALLYINCLUSIVEVALUE from Tax_Channel_Prop_Exists
UNION ALL
select DISTINCT 0 As taxmappingid, PCD.Channel As CHANNELNAME,
PCD.PropertyName As PROPERTYNAME ,
PCD.propertyid As PROPERTYID,
-1 As TAXTYPE,
0 As ISPARTIALLYINCLUSIVE,
'' As PARTIALLYINCLUSIVEVALUE
FROM Prop_Cha_Data PCD
WHERE NOT EXISTS (
Select taxmappingid FROM Tax_Channel_Prop_Exists E
WHERE E.channelname = PCD.Channel AND
E.propertyid = PCD.propertyid )
)
Order by PROPERTYNAME,CHANNELNAME ;
end SP_GetTaxSettingChannelInfo;

Resources