Grouping multiple data in oracle sql - 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

Related

How to reformat data stored in a record and output it in oracle

Let's go about this with an example
Let's say my table column holds the following data
Col_ID Col_Name
------ ---------------------------------------------------
102 LOCATION_ID IN (7351,7550,76202,7350)
121 265,76700,76701,72701,74210)
111 ,76200,76201,76202,76203,76204,76205,76206,76207,7
The above data is stored in that manner in the tables already which cannot be changed. The output that I desire is as follows:-
Col_ID Col_Name
------ --------
102 7531
102 7550
102 76202
102 7350
121 265
121 76700
And So on.....
There are multiple ways of extracting data from a delimited string.
One of which is to use a recursive sub-query factoring clause:
WITH data ( col_id, col_name, lvl, max_lvl, value ) AS (
SELECT col_id,
col_name,
1, -- First level
REGEXP_COUNT( col_name, '\d+' ), -- Count the number of numbers
TO_NUMBER( REGEXP_SUBSTR( col_name, '\d+', 1, 1 ) ) -- Extract the first number
FROM your_table
WHERE 1 <= REGEXP_COUNT( col_name, '\d+' ) -- Check there is a number
UNION ALL -- Iterate over the previous rows
SELECT col_id,
col_name,
lvl + 1, -- Increase the level by one
max_lvl,
TO_NUMBER( REGEXP_SUBSTR( col_name, '\d+', 1, lvl + 1 ) )
-- Extract the (lvl+1)th number from the string.
FROM data
WHERE lvl < max_lvl -- Continue until all the numbers have been parsed
)
SELECT col_id,
value
FROM data;
The parenthesis is removed with some pattern, so try as below and make necessary modifications.
WITH outer_table
AS (SELECT col_id, col_name
FROM (SELECT col_id,
REGEXP_SUBSTR (col_name,
'[^)]+',
1,
1)
col_name
FROM t5
WHERE col_name LIKE '%)%'
UNION ALL
SELECT col_id,
REGEXP_SUBSTR (col_name,
'[^(]+',
1,
2)
col_name
FROM t5
WHERE col_name LIKE '%(%'
UNION ALL
SELECT col_id, (col_name) col_name
FROM t5
WHERE col_name NOT LIKE '%(%' AND col_name NOT LIKE '%)%'))
select col_id, regexp_substr(col_name,'[^,]+',1,column_value) col_name
from outer_table,
table(
cast(
multiset(
select level
from dual
connect by level <= length(regexp_replace(col_name,'[^,]')) + 1
)
as sys.OdciNumberList
)
)
ORDER BY col_id,col_name
Demo
WITH T AS
(SELECT '102' AS COL_ID, 'LOCATION_ID IN (7351,7550,76202,7350)' AS COL_NAME
FROM DUAL
UNION ALL
SELECT '121', '265,76700,76701,72701,74210)'
FROM DUAL
UNION ALL
SELECT '111', ',76200,76201,76202,76203,76204,76205,76206,76207,7'
FROM DUAL)
SELECT COL_ID, COL_NAME
FROM (SELECT T.COL_ID,
REGEXP_SUBSTR(REGEXP_SUBSTR(T.COL_NAME, '[^,]+', 1,
T1.COLUMN_VALUE), '[0-9]+', 1, 1) AS COL_NAME
FROM T,
TABLE(CAST(MULTISET
(SELECT LEVEL
FROM DUAL
CONNECT BY LEVEL <=
REGEXP_COUNT(T.COL_NAME, ',') + 1) AS
SYS.ODCINUMBERLIST)) T1)
WHERE COL_NAME IS NOT 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;

How to sort version numbers (like 5.3.60.8)

I have a Strings like:
5.3.60.8
6.0.5.94
3.3.4.1
How to sort these values in sorting order in Oracle SQL?
I want the order to be like this:
6.0.5.94
5.3.60.8
3.3.4.1
with
inputs ( str ) as (
select '6.0.5.94' from dual union all
select '5.3.60.8' from dual union all
select '3.3.4.1' from dual
)
select str from inputs
order by to_number(regexp_substr(str, '\d+', 1, 1)),
to_number(regexp_substr(str, '\d+', 1, 2)),
to_number(regexp_substr(str, '\d+', 1, 3)),
to_number(regexp_substr(str, '\d+', 1, 4))
;
STR
--------
3.3.4.1
5.3.60.8
6.0.5.94
You could pad numbers with zeroes on the left in the order by clause:
select version
from versions
order by regexp_replace(
regexp_replace(version, '(\d+)', lpad('\1', 11, '0')),
'\d+(\d{10})',
'\1'
) desc
This works for more number parts as well, up to about 200 of them.
If you expect to have numbers with more than 10 digits, increase the number passed as second argument to the lpad function, and also the braced number in the second regular expression. The first should be one more (because \1 is two characters but could represent only one digit).
Highest version
To get the highest version only, you can add the row number to the query above with the special Oracle rownum keyword. Then wrap all that in an another select with a condition on that row number:
select version
from (
select version, rownum as row_num
from versions
order by regexp_replace(
regexp_replace(version, '(\d+)', lpad('\1', 11, '0')),
'\d+(\d{10})',
'\1'
) desc)
where row_num <= 1;
See this Q&A for several alternatives, also depending on your Oracle version.
I will show here the answer from AskTom, which can be used with different version size :
WITH inputs
AS (SELECT 1 as id, '6.0.5.94' as col FROM DUAL
UNION ALL
SELECT 2,'5.3.30.8' FROM DUAL
UNION ALL
SELECT 3,'5.3.4.8' FROM DUAL
UNION ALL
SELECT 4,'3' FROM DUAL
UNION ALL
SELECT 5,'3.3.40' FROM DUAL
UNION ALL
SELECT 6,'3.3.4.1.5' FROM DUAL
UNION ALL
SELECT 7,'3.3.4.1' FROM DUAL)
SELECT col, MAX (SYS_CONNECT_BY_PATH (v, '.')) p
FROM (SELECT t.col, TO_NUMBER (SUBSTR (x.COLUMN_VALUE, 1, 5)) r, SUBSTR (x.COLUMN_VALUE, 6) v, id rid
FROM inputs t,
TABLE (
CAST (
MULTISET (
SELECT TO_CHAR (LEVEL, 'fm00000')
|| TO_CHAR (TO_NUMBER (SUBSTR ('.' || col || '.', INSTR ('.' || col || '.', '.', 1, ROWNUM) + 1, INSTR ('.' || col || '.', '.', 1, ROWNUM + 1) - INSTR ('.' || col || '.', '.', 1, ROWNUM) - 1)), 'fm0000000000')
FROM DUAL
CONNECT BY LEVEL <= LENGTH (col) - LENGTH (REPLACE (col, '.', '')) + 1) AS SYS.odciVarchar2List)) x)
START WITH r = 1
CONNECT BY PRIOR rid = rid AND PRIOR r + 1 = r
GROUP BY col
ORDER BY p

SQL Concatenate strings across multiple columns with corresponding values

I'm looking for a way to achieve this in a SELECT statement.
FROM
Column1 Column2 Column3
A,B,C 1,2,3 x,y,z
TO
Result
A|1|x,B|2|y,C|3|z
The delimiters don't matter. I'm just trying to to get all the data in one single column. Ideally I am looking to do this in DB2. But I'd like to know if there's an easier way to get this done in Oracle.
Thanks
You can do it like this using INSTR and SUBSTR:
select
substr(column1,1,instr(column1,',',1)-1) || '|' ||
substr(column2,1,instr(column2,',',1)-1) || '|' ||
substr(column3,1,instr(column3,',',1)-1) || '|' ||
',' ||
substr(column1 ,instr(column1 ,',',1,1)+1,instr(column1 ,',',1,2) - instr(column1 ,',',1)-1) || '|' ||
substr(column2 ,instr(column2 ,',',1,1)+1,instr(column2 ,',',1,2) - instr(column2 ,',',1)-1) || '|' ||
substr(column3 ,instr(column3 ,',',1,1)+1,instr(column3 ,',',1,2) - instr(column3 ,',',1)-1) || '|' ||
',' ||
substr(column1 ,instr(column1 ,',',1,2)+1) || '|' ||
substr(column2 ,instr(column2 ,',',1,2)+1) || '|' ||
substr(column3 ,instr(column3 ,',',1,2)+1)
from yourtable
i tried some thing. just look into link
first i created a table called t_ask_test and inserted the data based on the above question. Achieved the result by using the string functions
sample table
create table t_ask_test(column1 varchar(10), column2 varchar(10),column3 varchar(10));
inserted a row
insert into T_ASK_TEST values ('A,B,C','1,2,3','x,y,z');
the following query will be in dynamic way
select substr(column1,1,instr(column1,',',1,1)-1)||'|'||substr(column2,1,instr(column1,',',1,1)-1)||'|'||substr(column3,1,instr(column1,',',1,1)-1) ||','||
substr(column1,instr(column1,',',1,1)+1,instr(column1,',',1,2)-instr(column1,',',1,1)-1)||'|'||substr(column2,instr(column2,',',1,1)+1,instr(column2,',',1,2)-instr(column2,',',1,1)-1)||'|'||substr(column3,instr(column3,',',1,1)+1,instr(column3,',',1,2)-instr(column3,',',1,1)-1) ||','||
substr(column1,instr(column1,',',1,2)+1,length(column1)-instr(column1,',',1,2))||'|'||substr(column2,instr(column2,',',1,2)+1,length(column2)-instr(column2,',',1,2))||'|'||substr(column3,instr(column3,',',1,2)+1,length(column3)-instr(column3,',',1,2)) as test from t_ask_test;
output will be as follows
TEST
---------------
A|1|x,B|2|y,C|3|z
If you have a dynamic number of entries for each row then:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE TEST ( Column1, Column2, Column3 ) AS
SELECT 'A,B,C', '1,2,3', 'x,y,z' FROM DUAL
UNION ALL SELECT 'D,E', '4,5', 'v,w' FROM DUAL;
Query 1:
WITH ids AS (
SELECT t.*, ROWNUM AS id
FROM TEST t
)
SELECT LISTAGG(
REGEXP_SUBSTR( i.Column1, '[^,]+', 1, n.COLUMN_VALUE )
|| '|' || REGEXP_SUBSTR( i.Column2, '[^,]+', 1, n.COLUMN_VALUE )
|| '|' || REGEXP_SUBSTR( i.Column3, '[^,]+', 1, n.COLUMN_VALUE )
, ','
) WITHIN GROUP ( ORDER BY n.COLUMN_VALUE ) AS value
FROM ids i,
TABLE(
CAST(
MULTISET(
SELECT LEVEL
FROM DUAL
CONNECT BY LEVEL <= GREATEST(
REGEXP_COUNT( i.COLUMN1, '[^,]+' ),
REGEXP_COUNT( i.COLUMN2, '[^,]+' ),
REGEXP_COUNT( i.COLUMN3, '[^,]+' )
)
)
AS SYS.ODCINUMBERLIST
)
) n
GROUP BY i.ID
Results:
| VALUE |
|-------------------|
| A|1|x,B|2|y,C|3|z |
| D|4|v,E|5|w |
You need to use:
SUBSTR
INSTR
|| concatenation operator
It would be easy if you break your output, and then understand how it works.
SQL> WITH t AS
2 ( SELECT 'A,B,C' Column1, '1,2,3' Column2, 'x,y,z' Column3 FROM dual
3 )
4 SELECT SUBSTR(column1, 1, instr(column1, ',', 1) -1)
5 ||'|'
6 || SUBSTR(column2, 1, instr(column2, ',', 1) -1)
7 ||'|'
8 || SUBSTR(column3, 1, instr(column1, ',', 1) -1)
9 ||','
10 || SUBSTR(column1, instr(column1, ',', 1, 2) +1 - instr(column1, ',', 1),
11 instr(column1, ',', 1) -1)
12 ||'|'
13 || SUBSTR(column2, instr(column2, ',', 1, 2) +1 - instr(column2, ',', 1),
14 instr(column2, ',', 1) -1)
15 ||'|'
16 || SUBSTR(column3, instr(column3, ',', 1, 2) +1 - instr(column3, ',', 1),
17 instr(column3, ',', 1) -1)
18 ||','
19 || SUBSTR(column1, instr(column1, ',', 1, 3) +1 - instr(column1, ',', 1),
20 instr(column1, ',', 2) -1)
21 as "new_column"
22 FROM t;
new_column
-------------
A|1|x,B|2|y,C
On a side note, you should avoid storing delimited values in a single column. Consider normalizing the data.
From Oracle 11g and above, you could create a VIRTUAL COLUMN using the above expression and use it instead of executing the SQL frequently.
Its very simple in oracle. just use the concatenation operatort ||.
In the below solution, I have used underscore as the delimiter
select Column1 ||'_'||Column2||'_'||Column3 from table_name;

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