oracle user defined delimiter in sql - sql

I have a sample table which looks like following:
WITH T(ID, SLOW, MEDIUM, FAST, SUPER_FAST) AS
(SELECT 1, 'Y', 'Y', 'N', 'Y' FROM DUAL
UNION ALL
SELECT 2, 'N','Y','N', 'Y' FROM DUAL
)
SELECT 'Identified in '
||CASE WHEN L_TEXT_A IS NOT NULL THEN L_TEXT_A END ||' , '
||CASE WHEN L_TEXT_B IS NOT NULL THEN L_TEXT_B END ||' , '
||CASE WHEN L_TEXT_C IS NOT NULL THEN L_TEXT_C END ||' & '
||CASE WHEN L_TEXT_D IS NOT NULL THEN L_TEXT_D END
FROM
(
SELECT CASE WHEN slow = 'Y' THEN 'slow' END L_TEXT_A,
CASE WHEN medium = 'Y' THEN 'medium' END L_TEXT_B,
CASE WHEN FAST = 'Y' THEN 'fast' END L_TEXT_C,
case when SUPER_FAST = 'Y' then 'super fast' END L_TEXT_D
FROM T
); --
Identified in slow , medium , & super fast
Identified in , medium , & super fast
I need to get a result which should return as like:
'Identified in slow, medium and super fast'
'Identified in medium and super fast'
The condition is no delimiter for single column which having Y , ampersand for two 'Y' values, and commas followed by ampersand which having more than 2 columns.

If you are using Oracle 11g2, a combination of unpivot and listagg can help, try this query
WITH T(ID, SLOW, MEDIUM, FAST, SUPER_FAST) AS
(SELECT 1, 'Y', 'Y', 'N', 'Y' FROM DUAL
UNION ALL
SELECT 2, 'N','Y','N', 'Y' FROM DUAL
union all
SELECT 3, 'N','N','N', 'Y' FROM DUAL
union all
SELECT 4, 'N','N','N', 'N' FROM DUAL
),
tlist as (select listagg(speed, ', ') within group (order by rownum) as list, id
from T
unpivot (pace for speed in (SLOW as 'Slow',MEDIUM as 'Medium', FAST as 'Fast', SUPER_FAST as 'Super Fast'))
where pace = 'Y'
group by id)
select case when instr(list, ', ', -1, 1) = 0 then
list
else
substr(list, 1, instr(list, ', ', -1, 1) - 1) || ' and ' || substr(list, instr(list, ', ', -1, 1) + 2)
end as list
from tlist;
output:
| LIST |
|-----------------------------|
| Slow, Medium and Super Fast |
| Medium and Super Fast |
| Super Fast |

Here is some code that uses simple instr, substr and replace functions built in functions which seems to work for all combinations. Note, if you have the option of doing this via a PL/SQL function you could probably simplify the code a bit:
with t(id, slow, medium, fast, super_fast) as (
select 1, 'n', 'n', 'n', 'n' from dual
union all
select 2, 'y', 'n', 'n', 'n' from dual
union all
select 3, 'n', 'y', 'n', 'n' from dual
union all
select 4, 'n', 'n', 'y', 'n' from dual
union all
select 5, 'n', 'n', 'n', 'y' from dual
union all
select 6, 'y', 'y', 'n', 'n' from dual
union all
select 7, 'y', 'n', 'y', 'n' from dual
union all
select 8, 'y', 'n', 'n', 'y' from dual
union all
select 9, 'n', 'y', 'y', 'n' from dual
union all
select 10, 'n', 'y', 'n', 'y' from dual
union all
select 11, 'n', 'n', 'y', 'y' from dual
union all
select 12, 'y', 'y', 'y', 'n' from dual
union all
select 13, 'y', 'y', 'n', 'y' from dual
union all
select 14, 'y', 'n', 'y', 'y' from dual
union all
select 15, 'n', 'y', 'y', 'y' from dual
union all
select 16, 'y', 'y', 'y', 'y' from dual
),
step1 as (
select 'identified in ' ||
decode(slow,'y', 'slow' || ', ', '') ||
decode(medium,'y', 'medium' || ', ', '') ||
decode(fast,'y', 'fast' || ', ', '') ||
decode(super_fast,'y', 'super fast' || ', ', '') str
from t
),
step2 as (
select length(str) - length(replace(str, ',', null)) as vals, -- count values (using commas)
substr(str, 1, length(str)-2) as str -- strip final comma
from step1
),
step3 as (
select str,
decode(vals, 0, 0, 1, 0, instr(str, ',', 1, vals -1)) as final_comma_pos,
vals
from step2
),
step4 as (
select decode(vals, 0, null,
1, str,
substr(str, 1, final_comma_pos - 1) || ' &' ||
substr(str, final_comma_pos + 1)
) as str
from step3
)
select * from step4;
Output:
identified in slow
identified in medium
identified in fast
identified in super fast
identified in slow & medium
identified in slow & fast
identified in slow & super fast
identified in medium & fast
identified in medium & super fast
identified in fast & super fast
identified in slow, medium & fast
identified in slow, medium & super fast
identified in slow, fast & super fast
identified in medium, fast & super fast
identified in slow, medium, fast & super fast

The below query delimits as you expected..
The Key is last occurrence of comma is replaced by AND.
regexp_count is used to find the last occurrence.
SQL> select regexp_replace('Identified in slow , medium , super fast',
',' ,' and ',1,
regexp_count('Identified in slow , medium , super fast',',')) from dual; 2 3
REGEXP_REPLACE('IDENTIFIEDINSLOW,MEDIUM,SUPE
--------------------------------------------
Identified in slow , medium and super fast
Full Version:
WITH T(ID, SLOW, MEDIUM, FAST, SUPER_FAST) AS
(SELECT 1, 'Y', 'Y', 'N', 'Y' FROM DUAL
UNION ALL
SELECT 2, 'N','Y','N', 'Y' FROM DUAL
),
mytext as
(SELECT trim(trailing ',' FROM 'Identified in '
||NVL2(L_TEXT_A,L_TEXT_A||',',NULL)
||NVL2(L_TEXT_B,L_TEXT_B||',',NULL)
||NVL2(L_TEXT_C,L_TEXT_C||',',NULL)
||(L_TEXT_D)) as text
FROM
(
SELECT CASE WHEN slow = 'Y' THEN 'slow' END L_TEXT_A,
CASE WHEN medium = 'Y' THEN 'medium' END L_TEXT_B,
CASE WHEN FAST = 'Y' THEN 'fast' END L_TEXT_C,
case when SUPER_FAST = 'Y' then 'super fast' END L_TEXT_D
FROM T
)
)
SELECT regexp_replace(text,
',' ,
' and ',
1,
regexp_count(text,',')) FROM mytext;

Related

Grouping multiple data in oracle sql

i have this table in my oracle db
enter image description here
is there some way I can perform the grouping in SQL? so that the returned data is something like
enter image description here
Thank you
In later versions of Oracle you can use:
SELECT product_id,
LISTAGG(DISTINCT qc, ',') WITHIN GROUP (ORDER BY qc) AS qc,
LISTAGG(DISTINCT packing, ',') WITHIN GROUP (ORDER BY packing)
AS packing,
LISTAGG(DISTINCT delivering, ',') WITHIN GROUP (ORDER BY delivering)
AS delivering
FROM table_name
GROUP BY product_id
However, in Oracle 11, LISTAGG does not support the DISTINCT keyword and you would either need to pre-process the input to remove duplicates or to post-process the aggregated string:
SELECT product_id,
REPLACE(
TRIM(
BOTH ',' FROM
REGEXP_REPLACE(
',' || LISTAGG(qc, ',,') WITHIN GROUP (ORDER BY qc) || ',',
'(,.*?,)\1+',
'\1'
)
),
',,',
','
)AS qc,
REPLACE(
TRIM(
BOTH ',' FROM
REGEXP_REPLACE(
',' || LISTAGG(packing, ',,') WITHIN GROUP (ORDER BY packing) || ',',
'(,.*?,)\1+',
'\1'
)
),
',,',
','
)AS packing,
REPLACE(
TRIM(
BOTH ',' FROM
REGEXP_REPLACE(
',' || LISTAGG(delivering, ',,') WITHIN GROUP (ORDER BY delivering) || ',',
'(,.*?,)\1+',
'\1'
)
),
',,',
','
)AS delivering
FROM table_name
GROUP BY product_id
Which, for the sample data:
CREATE TABLE table_name ( product_id, qc, packing, delivering ) AS
SELECT 1, 'A', 'A', 'C' FROM DUAL UNION ALL
SELECT 1, 'A', 'B', 'C' FROM DUAL UNION ALL
SELECT 2, 'A', 'C', 'C' FROM DUAL UNION ALL
SELECT 3, 'A', 'B', 'D' FROM DUAL UNION ALL
SELECT 3, 'A', 'D', 'D' FROM DUAL;
Both output (for compliant versions):
PRODUCT_ID
QC
PACKING
DELIVERING
1
A
A,B
C
2
A
C
C
3
A
B,D
D
fiddle

Replace character that occurs right after nth occurrence of character in oracle sql

For Eg : DS/Home/INT01/int_4/123045_PDF_test 12/06/2022 checklist
Here i want to replace / into '_' after 4th occurrence of /
Expected output: DS/Home/INT01/int_4/123045_PDF_TEST 12_06_2022 checklist
Something like this (using only standard string functions):
with
test_strings (str) as (
select 'DS/Home/INT01/int_4/123045_PDF_test 12/06/2022 checklist'
from dual union all
select null from dual union all
select 'text/with/four/slashes/' from dual union all
select 'only/two/slashes' from dual union all
select 'no slashes' from dual union all
select '////////' from dual
)
select case instr(str, '/', 1, 4) when 0 then str
else substr(str, 1, instr(str, '/', 1, 4)) ||
translate(substr(str, instr(str, '/', 1, 4) + 1), '/', '_')
end as new_str
from test_strings
;
NEW_STR
------------------------------------------------------------
DS/Home/INT01/int_4/123045_PDF_test 12_06_2022 checklist
text/with/four/slashes/
only/two/slashes
no slashes
////____
The same can easily be made into a user-defined function if you need the occurrence specifier (4 in your example), the "from-character" and the "to-character" to be generic.

Cross Join query in Oracle

I am trying to refractor a query that has in its Select concatenation of five fields, thus
SELECT
Id
'-'
Field_2
'-'
Field_3
'-'
Field_4
AS id
FROM some_table
Example result would be something like this
Id
1-a-b-c-d
2-e-f-g-h
#null#
#null#
My problem is whilst this query works, some or all of these fields could be null. It has been suggested that a cross join could be to allow a display of null when all or any of these fields are null. Unfortunately, I am not sure how to do this. The cross join has been suggested for performance due to the large number of rows.
Can someone please show me how
You don't give a lot of detail but I expect what you want is the following.
SELECT COALESCE(Id,'') || '-' ||
COALESCE(Field_2,'') || '-' ||
COALESCE(Field_3,'') || '-' ||
COALESCE(Field_4,'')
AS id
FROM some_table
try this
select nvl(concat(concat(concat(concat(a,b),c),d),e),'NULL') from some_table
when any of the fields we show the key as null with the word null
What could be joined here? Use simple case:
with sample_data(id, field1, field2, field3, field4) as (
select 1, 'a', 'b', 'c', 'd' from dual union all
select 2, 'e', 'f', 'g', 'h' from dual union all
select 3, 'p', 'q', null, 's' from dual union all
select 4, null, 'x', 'y', 'z' from dual )
select case when id is null or field1 is null or field2 is null or field3 is null or field4 is null
then '#null#'
else id || '-' || field1 || '-' || field2 || '-' || field3 || '-' || field4
end as id
from sample_data
Result:
ID
---------
1-a-b-c-d
2-e-f-g-h
#null#
#null#

Oracle rows To Column

Old Result
Column_Name
A
B
C
D
New Required Result
Column_Name
'A', 'B', 'C', 'D'
Restrictions, (Count is unknown. Could be A, B, C or A, B, C, D, E and so on.
Considering your question literally, you have a query returning a single column with a variable number of rows, say
select 'A' column_name from dual union all
select 'B' column_name from dual union all
select 'C' column_name from dual union all
select 'D' column_name from dual
and you need to have the result in a single row with a single column containing the concatenation of the values returned by the original query, wrapped by ''; in your example, you need:
Column_Name
'A', 'B', 'C', 'D'
If this is correct, you may simply need listagg:
select listagg('''' || column_name || '''', ', ') within group ( order by column_name)
from (
select 'A' column_name from dual union all
select 'B' column_name from dual union all
select 'C' column_name from dual union all
select 'D' column_name from dual
) yourQuery
If you are looking to make a list of all the values in a certain column behind each other you can use LISTAGG.
For example:
SELECT LISTAGG(Column_Name, ', ') WITHIN GROUP (ORDER BY Column_Name) "Column_Listing"
FROM TableA;

Oracle - string combinatorial permutation

I think I have a complex requirement.
It's a combinatorial permutation using Oracle 10.2, I'was able to solve it using cartesian joins, but I think that it need some improvements to made it simplest and more flexible.
Main behaviour.
input string: 'one two'
output:
'one'
'two'
'one two'
'two one'
For my solution I've restricted the number of strings to 5 (note that the output is a number near the factorial)
SQL:
with My_Input_String as (select 1 as str_id, 'alpha beta omega gama' as str from dual )
--------logic-------
, String_Parse as (
SELECT REGEXP_SUBSTR(str, '[^ ]+', 1, ROWNUM) str
FROM My_Input_String
where rownum < 6 -- string limitation --
CONNECT BY level <= LENGTH(REGEXP_REPLACE(str, '([^ ])+|.', '\1') )
)
--------CRAP select need refactoring-------
select str from String_Parse
union
select REGEXP_REPLACE(trim(s1.str||' '||s2.str||' '||s3.str||' '||s4.str||' '||s5.str), '( ){2,}', ' ') as str
from
(select str from String_Parse union select ' ' from dual) s1,
(select str from String_Parse union select ' ' from dual) s2,
(select str from String_Parse union select ' ' from dual) s3,
(select str from String_Parse union select ' ' from dual) s4,
(select str from String_Parse union select ' ' from dual) s5
where
--
s1.str <> s2.str and s1.str <> s3.str and s1.str <> s4.str and s1.str <> s5.str
--
and s2.str <> s3.str and s2.str <> s4.str and s2.str <> s5.str
--
and s3.str <> s4.str and s3.str <> s5.str
--
and s4.str <> s5.str
Edit: Got the generic one. Really simple in the end (but took me a while to get there)
WITH words AS
( SELECT REGEXP_SUBSTR( '&txt', '\S+', 1, LEVEL ) AS word
, LEVEL AS num
FROM DUAL
CONNECT BY LEVEL <= LENGTH( REGEXP_REPLACE( '&txt', '\S+\s*', 'X' ) )
)
SELECT SYS_CONNECT_BY_PATH( W.word, ' ' )
FROM words W
CONNECT BY NOCYCLE PRIOR W.num != W.num
Edit2: Removed redundant maxnum stuff. Left over from previous attempts