SQL Concatenate strings across multiple columns with corresponding values - sql

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;

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

split column value of a table and skip some words

Hil All,
I have a table , count is about 200M. It has a column which contains data separated by '~'. I want to parse it.
e.g:
Column1
A~B~C~D~E~F
Result :
Column_new1
A~C~E
I just want to skip 2,4,6,n th. words. I don't want plsql. I need sql query. And table is very big,I also need performance.
I use substr,instr functions and I can parse. But it runs really slowly..
Thanks for help.
If you are after performance then use the INSTR and SUBSTR simple string functions:
SELECT SUBSTR(column1, 1, p1 - 1 ) || '~' ||
SUBSTR(column1, p2 + 1, p3 - p2 - 1) || '~' ||
SUBSTR(column1, p4 + 1, p5 - p4 - 1) AS column1_new
FROM (
SELECT column1,
INSTR(column1, '~', 1, 1) AS p1,
INSTR(column1, '~', 1, 2) AS p2,
INSTR(column1, '~', 1, 3) AS p3,
INSTR(column1, '~', 1, 4) AS p4,
INSTR(column1, '~', 1, 5) AS p5
FROM table_name
);
Which, for the sample data:
CREATE TABLE table_name (column1) AS
SELECT 'A~B~C~D~E~F' FROM DUAL;
Outputs:
COLUMN1_NEW
A~C~E
If you want a shorter query then you can use regular expressions:
SELECT REGEXP_REPLACE(column1, '([^~]+)~[^~]+~([^~]+)~[^~]+~([^~]+).*', '\1~\2~\3' )
AS column1_new
FROM table_name;
However, you will find that performance is likely to be an order of magnitude worse than simple string functions.
Another alternative would be to generate a materialized view.
db<>fiddle here
This is a regular expressions option. Looks nice, isn't PL/SQL, works OK (for 2 rows). I'm afraid that anything will run slow for 200 million rows.
SQL> with test (id, col) as
2 (select 1, 'A~B~C~D~E~F' from dual union all
3 select 2, 'M~N~O~P~Q-R' from dual
4 )
5 select id,
6 regexp_substr(col, '\w+', 1, 1) || '~' ||
7 regexp_substr(col, '\w+', 1, 3) || '~' ||
8 regexp_substr(col, '\w+', 1, 5) result
9 from test;
ID RESULT
---------- -----------------------------------
1 A~C~E
2 M~O~Q
SQL>

SQL: parse a column delimited (shorten the script)

I created a query and it's properly working.. but I'm not yet satisfied because my code is too long, is there way that I can simplify or shorten my select statement ?
select
/*GenInfo*/
id ,name name,
replace(regexp_substr(properties, 'EntityID=[^;]*'), 'EntityID=', '') as EntityID,
replace(regexp_substr(properties, 'deployed=[^;]*'), 'deployed=', '') as deployed,
replace(regexp_substr(properties, 'type=[^;]*'), 'type=', '') as type,
replace(regexp_substr(properties, 'level=[^;]*'), 'level=', '') as "LEVEL",
replace(regexp_substr(properties, 'description=[^;]*'), 'description=', '') as description,
replace(regexp_substr(properties, 'indicator=[^;]*'), 'indicator=', '') as indicator,
replace(regexp_substr(properties, 'Agreement=[^;]*'), 'Agreement=', '') as Agreement,
replace(regexp_substr(properties, 'Activation date to charge=[^;]*'), 'Activation date to charge=', '') as Activationdatetocharge,
replace(regexp_substr(properties, 'id=[^;]*'), 'id=', '') as id,
replace(regexp_substr(properties, 'name=[^;]*'), 'name=', '') as name,
replace(regexp_substr(properties, 'currencyCode=[^;]*'), 'currencyCode=', '') as currencyCode,
replace(regexp_substr(properties, 'saleExpirationDate=[^;]*'), 'saleExpirationDate=', '') as saleExpirationDate,
replace(regexp_substr(properties, 'Product type=[^;]*'), 'Product type=', '') as Producttype,
replace(regexp_substr(properties, 'saleEffectiveDate=[^;]*'), 'saleEffectiveDate=', '') as saleEffectiveDate,
replace(regexp_substr(properties, 'Deactivation date to charge=[^;]*'), 'Deactivation date to charge=', '') as Deactivationdatetocharge
.
.
.
.
.
.
from OFFER
where name = 'PLAN 599'
;
You have to double-split and generate a query:
Split Row into "ColName=Value"-fields
Split fields into two seperate vars
Create a from-dual-query
DECLARE
inputstring VARCHAR2 (2000) := 'EntityID=1;deployed=2018-01-01;type=app';
myquery VARCHAR2 (2000) := 'SELECT'; -- result-var
tmpValue VARCHAR2 (2000);
tmpName VARCHAR2 (2000);
BEGIN
FOR i IN
(
SELECT TRIM (REGEXP_SUBSTR (inputstring, -- Split input and loop through
'[^;]+',
1,
LEVEL))
l
FROM DUAL
CONNECT BY LEVEL <= REGEXP_COUNT (inputstring, ';') + 1
)
LOOP
tmpName := REGEXP_SUBSTR (i.l, '[^=]+', 1, 1); -- Split column into value and name
tmpValue := REGEXP_SUBSTR (i.l, '[^=]+', 1, 2);
myquery := myquery || ' ''' || tmpValue || ''' as ' || tmpName || ','; -- build some query
END LOOP;
myQuery := SUBSTR (myQuery, 0, LENGTH (myQuery) - 1) || ' FROM DUAL'; -- complete query
DBMS_OUTPUT.put_line (myQuery); --output result
-- Result: SELECT '1' as EntityID, '2018-01-01' as deployed, 'app' as type FROM DUAL
END;
Anyway you'll get a problem with this query if you want to read out the values by code.
Perhaps you could tell us, what you want to do with the data.
I hope, you only want to convert some data from a file. If so you can add another split-loop to split rows.
Example-Function
CREATE OR REPLACE FUNCTION MakeSQL (inputstring VARCHAR2)
RETURN VARCHAR2
IS
myquery VARCHAR2 (2000); -- result-var
tmpValue VARCHAR2 (2000);
tmpName VARCHAR2 (2000);
BEGIN
FOR x IN ( SELECT TRIM (REGEXP_SUBSTR (inputstring, -- Split input and loop through
'[^' || CHR (10) || ']+',
1,
LEVEL))
tmpRow
FROM DUAL
CONNECT BY LEVEL <= REGEXP_COUNT (inputstring, CHR (10)) + 1)
LOOP
myquery := myquery || 'SELECT';
FOR i IN ( SELECT TRIM (REGEXP_SUBSTR (x.tmpRow, -- Split input and loop through
'[^;]+',
1,
LEVEL))
l
FROM DUAL
CONNECT BY LEVEL <= REGEXP_COUNT (x.tmpRow, ';') + 1)
LOOP
tmpName :=
REGEXP_SUBSTR (i.l,
'[^=]+',
1,
1); -- Split column into value and name
tmpValue :=
REGEXP_SUBSTR (i.l,
'[^=]+',
1,
2);
myquery :=
myquery || ' ''' || tmpValue || ''' as ' || tmpName || ','; -- build some query
END LOOP;
myQuery :=
SUBSTR (myQuery, 0, LENGTH (myQuery) - 1)
|| ' FROM DUAL UNION ALL'
|| CHR (10); -- complete row-select
END LOOP;
myQuery := SUBSTR (myQuery, 0, LENGTH (myQuery) - 11); -- complete query
DBMS_OUTPUT.put_line (myQuery); --output result
RETURN myQuery;
END MakeSQL;
Example call
SELECT MakeSQL('EntityID=1;deployed=2018-01-01;type=app
EntityID=2;deployed=2018-02-02;type=app') FROM DUAL;
Example-Result
SELECT '1' as EntityID, '2018-01-01' as deployed, 'app' as type FROM DUAL UNION ALL
SELECT '2' as EntityID, '2018-02-02' as deployed, 'app' as type FROM DUAL

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

Count sequential matching words in two strings oracle

I want a query that returns the number of sequential match of words in two strings
example:
Table
Id column1 column2 result
1 'foo bar live' 'foo bar' 2
2 'foo live tele' 'foo tele' 1
3 'bar foo live' 'foo bar live' 0
to get total number of occurrence I am using:
select id, column1,column2,
extractvalue(dbms_xmlgen.getxmltype('select cardinality (
sys.dbms_debug_vc2coll(''' || replace(lower(column1), ' ', ''',''' ) || ''') multiset intersect
sys.dbms_debug_vc2coll('''||replace(lower(column2), ' ', ''',''' )||''')) x from dual'), '//text()') cnt
from table.
Can anyone please suggest a query on similar lines for sequential match also as I want number of sequential matches and number of occurrences shown together.
Personally, in this situation, I would choose PL/SQL code over plain SQL. Something like:
Package specification:
create or replace package PKG is
function NumOfSeqWords(
p_str1 in varchar2,
p_str2 in varchar2
) return number;
end;
Package body:
create or replace package body PKG is
function NumOfSeqWords(
p_str1 in varchar2,
p_str2 in varchar2
) return number
is
l_str1 varchar2(4000) := p_str1;
l_str2 varchar2(4000) := p_str2;
l_res number default 0;
l_del_pos1 number;
l_del_pos2 number;
l_word1 varchar2(1000);
l_word2 varchar2(1000);
begin
loop
l_del_pos1 := instr(l_str1, ' ');
l_del_pos2 := instr(l_str2, ' ');
case l_del_pos1
when 0
then l_word1 := l_str1;
l_str1 := '';
else l_word1 := substr(l_str1, 1, l_del_pos1 - 1);
end case;
case l_del_pos2
when 0
then l_word2 := l_str2;
l_str2 := '';
else l_word2 := substr(l_str2, 1, l_del_pos2 - 1);
end case;
exit when (l_word1 <> l_word2) or
((l_word1 is null) or (l_word2 is null));
l_res := l_res + 1;
l_str1 := substr(l_str1, l_del_pos1 + 1);
l_str2 := substr(l_str2, l_del_pos2 + 1);
end loop;
return l_res;
end;
end;
Test case:
with t1(Id1, col1, col2) as(
select 1, 'foo bar live' ,'foo bar' from dual union all
select 2, 'foo live tele' ,'foo tele' from dual union all
select 3, 'bar foo live' ,'foo bar live'from dual
)
select id1
, col1
, col2
, pkg.NumOfSeqWords(col1, col2) as res
from t1
;
Result:
ID1 COL1 COL2 RES
---------- ------------- ------------ ----------
1 foo bar live foo bar 2
2 foo live tele foo tele 1
3 bar foo live foo bar live 0
Why to give up on the query approach. I know it's a bit complicated and I hope someone can work on it to improve it, but working on this during my spare time I was able to survive a an afternoon of calls...
Here on SQLFidlle
SELECT Table1.id,
Table1.column1,
Table1.column2,
max(nvl(t.l,0)) RESULT
FROM (
SELECT id,
column1,
column2,
LEVEL l,
decode(LEVEL,
1,
substr(column1, 1, instr(column1,' ', 1, LEVEL) -1),
substr(column1, 1, (instr(column1,' ', 1, LEVEL )))
) sub1,
decode(LEVEL,
1,
substr(column2, 1, instr(column2,' ', 1, LEVEL) -1),
substr(column2, 1, (instr(column2,' ', 1, LEVEL )))
) sub2
FROM (SELECT id,
column1 || ' ' column1,
column2 || ' ' column2
FROM Table1)
WHERE decode(LEVEL,
1,
substr(column1, 1, instr(column1,' ', 1, LEVEL) -1),
substr(column1, 1, (instr(column1,' ', 1, LEVEL )))
) =
decode(LEVEL,
1,
substr(column2, 1, instr(column2,' ', 1, LEVEL) -1),
substr(column2, 1, (instr(column2,' ', 1, LEVEL )))
)
START WITH column1 IS NOT NULL
CONNECT BY instr(column1,' ', 1, LEVEL) > 0
) t
RIGHT OUTER JOIN Table1 ON trim(t.column1) = Table1.column1
AND trim(t.column2) = Table1.column2
AND t.id = Table1.id
GROUP BY Table1.id,
Table1.column1,
Table1.column2
ORDER BY max(nvl(t.l,0)) DESC
I know this issue is old but I found a good solution:
You can test from here https://rextester.com/l/oracle_online_compiler
select
id1,
col1,
col2,
(
Select Count(*)
From
(Select Upper(To_Char(Trim(Substr(Column_Value,0,Length(Column_Value))))) w1
From xmltable(('"' || Replace(Replace(col1,' ', ','), ',', '","') || '"'))
Where Upper(To_Char(Trim(Substr(Column_Value,0,Length(Column_Value))))) Is Not Null) c1,
(Select Upper(To_Char(Trim(Substr(Column_Value,0,Length(Column_Value))))) w2
From xmltable(('"' || Replace(Replace(col2,' ', ','), ',', '","') || '"'))
Where Upper(To_Char(Trim(Substr(Column_Value,0,Length(Column_Value))))) Is Not Null) c2
Where c1.w1 = c2.w2
) Test
From
(select 1 Id1, 'foo bar live' col1, 'foo bar' col2 from dual union all
select 2, 'foo live tele pepe gato coche' ,'bar foo live tele perro gato' from dual union all
select 3, 'bar foo live tele perro gato' ,'foo bar live'from dual) t1