converting multiple comma separated columns into rows - oracle

I have an Oracle table which holds comma separated values in many columns. For example :
Id Column1 Column2
1 A,B,C H
2 D,E J,K
3 F L,M,N
I want to split all the columns into rows and the output should be this :
ID Column1 Column2
1 A H
1 B H
1 C H
2 D J
2 D K
2 E J
2 E K
3 F L
3 F M
3 F N
I found some suggestions which uses regexp_substr and connect by but it deals with only one column that has comma separated values. I have tried sub-query method also where I will be dealing with one column at a time in inner query and send the inner query output as input it outer query, this takes more time and the columns that hold comma separated values are more. So I cannot use the sub-query method.

For one column you can CROSS JOIN a TABLE() collection expression containing a correlated sub-query that uses a hierarchical query to split the column value up into separate strings. For two columns, you just do the same thing for the second column and the CROSS JOIN takes care of ensuring that each delimited value in column1 is paired with each delimited value in column2.
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE table_name ( Id, Column1, Column2 ) AS
SELECT 1, 'A,B,C', 'H' FROM DUAL UNION ALL
SELECT 2, 'D,E', 'J,K' FROM DUAL UNION ALL
SELECT 3, 'F', 'L,M,N' FROM DUAL;
Query 1:
SELECT t.id,
c1.COLUMN_VALUE AS c1,
c2.COLUMN_VALUE AS c2
FROM table_name t
CROSS JOIN
TABLE(
CAST(
MULTISET(
SELECT REGEXP_SUBSTR( t.Column1, '[^,]+', 1, LEVEL )
FROM DUAL
CONNECT BY LEVEL <= REGEXP_COUNT( t.Column1, '[^,]+' )
) AS SYS.ODCIVARCHAR2LIST
)
) c1
CROSS JOIN
TABLE(
CAST(
MULTISET(
SELECT REGEXP_SUBSTR( t.Column2, '[^,]+', 1, LEVEL )
FROM DUAL
CONNECT BY LEVEL <= REGEXP_COUNT( t.Column2, '[^,]+' )
) AS SYS.ODCIVARCHAR2LIST
)
) c2
Results:
| ID | C1 | C2 |
|----|----|----|
| 1 | A | H |
| 1 | B | H |
| 1 | C | H |
| 2 | D | J |
| 2 | D | K |
| 2 | E | J |
| 2 | E | K |
| 3 | F | L |
| 3 | F | M |
| 3 | F | N |

Below will give you an idea about to how to convert the comma separated string into rows. You can use this logic to satisfy your need.
select regexp_substr('a,b,c,v,f', '[^,]+',1,level)
from dual
connect by level <= regexp_count('a,b,c,v,f', ',') + 1;

Related

Oracle add group function over result rows

I'm trying to add an aggregate function column to an existing result set. I've tried variations of OVER(), UNION, but cannot find a solution.
Example current result set:
ID ATTR VALUE
1 score 5
1 score 7
1 score 9
Example desired result set:
ID ATTR VALUE STDDEV (score)
1 score 5 2
1 score 7 2
1 score 9 2
Thank you
Seems like you're after:
stddev(value) over (partition by attr)
stddev(value) over (partition by id, attr)
It just depend on what you need to partition by. Based on sample data the attr should be enough; but I could see possibly the ID and attr.
Example:
With CTE (ID, Attr, Value) as (
SELECT 1, 'score', 5 from dual union all
SELECT 1, 'score', 7 from dual union all
SELECT 1, 'score', 9 from dual union all
SELECT 1, 'Z', 1 from dual union all
SELECT 1, 'Z', 5 from dual union all
SELECT 1, 'Z', 8 from dual)
SELECT A.*, stddev(value) over (partition by attr)
FROM cte A
ORDER BY attr, value
DOCS show that by adding an order by to the analytic, one can acquire the cumulative standard deviation per record.
Giving us:
+----+-------+-------+------------------------------------------+
| ID | attr | value | stdev |
+----+-------+-------+------------------------------------------+
| 1 | Z | 1 | 3.51188458428424628280046822063322249225 |
| 1 | Z | 5 | 3.51188458428424628280046822063322249225 |
| 1 | Z | 8 | 3.51188458428424628280046822063322249225 |
| 1 | score | 5 | 2 |
| 1 | score | 7 | 2 |
| 1 | score | 9 | 2 |
+----+-------+-------+------------------------------------------+

Oracle 8g - How can i create a dynamic table without pivot?

i need to make a dynamic table in Oracle 8g, but this version doesn't have the PIVOT property. I want to create a table like this.
Date | Code | count
12/04/2016 | a1 | 8
12/05/2016 | a2 | 10
10/06/2016 | a3 | 4
24/10/2016 | a2 | 6
Date | a1 | a2 | a3
12/04/2016 | 8 | |
12/05/2016 | | 10 |
10/06/2016 | | | 4
24/10/2016 | | 6 |
The numbers of codes is undefined. That would be the reason why i cant create a static table.
Use a "plain" pivot query:
SELECT Date,
max( CASE code WHEN 'a1' THEN count END ) As a1,
max( CASE code WHEN 'a2' THEN count END ) As a2,
max( CASE code WHEN 'a3' THEN count END ) As a3
FROM table
GROUP BY Date
PIVOT clause is only a syntactic sugar to make it easier to express the above query
The below query using PIVOT clause is the same as the above one.
SELECT *
FROM (SELECT date, code, count FROM table )
PIVOT (
max( count ) FOR code IN ( 'a1' as a1, 'a2' as a2, 'a3' as a3 )
)

Select an included values

I'm using Oracle SQL and i need help with a query. Hope it's not too much easy one. I did't find an answer for it in Google.
I have a table that need to be aggregated by ID column and then to select only the records that two values are included in a certain table (and both of them).
Table for example
ID | Value
1 | Y
1 | N
2 | N
2 | N
2 | Y
3 | Y
3 | Y
4 | Y
5 | Y
5 | N
5 | Y
5 | N
The output table need to include only the IDs that both Y and N are included in Value table. Output:
ID
1
2
5
Another solution that groups by the ID and uses HAVING to return only those with > 1 DISTINCT values:
with v_data(id, value) as (
select 1, 'Y' from dual union all
select 1, 'N' from dual union all
select 2, 'Y' from dual)
select id
from v_data
group by id
having count(distinct value) > 1
select distinct a.id
from your_table a inner join your_table b on a.id = b.id and a.value != b.value;

update row without considering space between two words

I am looking to replace text in column Text with format(A,B) where text contains A and B only and ignoring spaces between A and B.
Here is test data
Id Text
1 A B //should be replaced with format(A,B).
2 A B //should be replaced with format(A,B).
3 A B //should be replaced with format(A,B).
4 A 1 B //shouldn't be replaced.
5 A B 1 //should be replaced with format(A,B) 1.
I think I have to do something like
UPDATE test SET text = REPLACE(text, 'A[wild char for space]B', 'format(A,B)');
but how should I compare only for space? like % will compare everything.
You can use Oracle Regex fro this
UPDATE test SET text = REGEXP_REPLACE(testcol, '(A[:blank:]B)', 'format(A,B)')
Oracle Regex
Just a hint:
SQL> with t as (
2 select 'A B' col from dual union all
3 select 'A B' from dual union all
4 select 'A B' from dual union all
5 select 'AB' from dual union all
6 select 'A 1 B' from dual union all
7 select 'A B 1' from dual
8 )
9 select regexp_replace(t.col, 'A[[:space:]]*B', 'format(A,B)') from t
10 /
REGEXP_REPLACE(T.COL,'A[[:SPACE:]]*B','FORMAT(A,B)')
--------------------------------------------------------------------------------
format(A,B)
format(A,B)
format(A,B)
format(A,B)
A 1 B
format(A,B) 1
Sql for your scenario:
with tab(Id,Text) as
(select 1,'A B' from dual union all -- //should be replaced with format(A,B).
select 2,'B C' from dual union all -- //should be replaced with format(A,B).
select 3,'A B' from dual union all -- //should be replaced with format(A,B).
select 4,'A 2 B' from dual union all -- //shouldn't be replaced.
select 5,'Y Z 1' from dual) -- //should be replaced with format(A,B) 1.
select id,
text,
regexp_replace(text, '^\D\s+\D', 'format('||regexp_substr(text, '\D')||','||trim(regexp_substr(text, '\D+', 2))||')') format
from tab;
output:
| ID | TEXT | FORMAT |
|----|-------------|---------------|
| 1 | A B | format(A,B) |
| 2 | B C | format(B,C) |
| 3 | A B | format(A,B) |
| 4 | A 2 B | A 2 B |
| 5 | Y Z 1 | format(Y,Z) 1 |
And the update statement becomes
UPDATE test SET text = regexp_replace(text, '^\D\s+\D', 'format('||regexp_substr(text, '\D')||','||trim(regexp_substr(text, '\D+', 2))||')');

Oracle 10: Incomprehensible behaviour on INSERT into a view?

we have a strange problem here, we can't explain to ourselves.
We have a view in an Oracle DB Version 10.2.0.5.8. The view uses an INSTEAD OF trigger.
This is the code for the trigger:
CREATE OR REPLACE TRIGGER V1_T1_BIUD
INSTEAD OF INSERT OR UPDATE OR DELETE
ON V1_T1
FOR EACH ROW
DECLARE
AnyId NUMBER;
BEGIN
IF INSERTING THEN
INSERT INTO Table T1 (
F1, F2, F3, F4, F5
) VALUES (
:new.F1, :new.F2, :new.F3, :new.F4, :new.F5
);
ELSIF UPDATING THEN
UPDATE T1 SET F1 = :new.F1,
F2 = :new.F2,
F3 = :new.F3,
F4 = :new.F4,
F5 = :new.F5
WHERE F1 = :old.F1;
ELSIF DELETING THEN
DELETE FROM T1
WHERE F1 = :old.F1;
END IF;
END;
/
This is an example INSERT statement:
INSERT INTO V_T1 (
F1, F2, F3, F4, F5
)
SELECT A.V, A.S, A.F, A.T, A.Z
FROM (
SELECT 'E' V, 'N' S, 'ABC' F, 'E' T, 'E' Z FROM DUAL UNION ALL
SELECT 'E', 'Y', 'QWE', 'O', 'E' FROM DUAL UNION ALL
SELECT 'I', 'Y', 'GHJ', 'I', 'I' FROM DUAL
) A
ORDER BY 1, 2, 3;
COMMIT;
Pay attention to the ORDER BY clause at the end of the select. The result of this INSERT statement is something like this:
F1 F2 F3 F4 F5
---------------
E N ABC I I
E Y QWE I I
I Y GHJ I I
As you can see, the 4th and 5th column are incorrectly filled with the values of the last datarow in all other datarows.
If we change the INSERT statement like this:
INSERT INTO V_T1 (
F1, F2, F3, F4, F5
)
SELECT A.V, A.S, A.F, A.T, A.Z
FROM (
SELECT 'E' V, 'N' S, 'ABC' F, 'E' T, 'E' Z FROM DUAL UNION ALL
SELECT 'E', 'Y', 'QWE', 'O', 'E' FROM DUAL UNION ALL
SELECT 'I', 'Y', 'GHJ', 'I', 'I' FROM DUAL
) A
ORDER BY 1, 2, 3, 4, 5;
COMMIT;
the result is this:
F1 F2 F3 F4 F5
---------------
E N ABC E E
E Y QWE O E
I Y GHJ I I
Again, pay attention to the ORDER BY clause, which now orders all rows instead of the first three in the first insert statement.
edit: If you omit the ORDER BY clause the result is also as expected (e. g. like in example 2).
Can someone explain this behaviour to me?
P. S. Concerning the comments:
I have not time to investigate or deliver any more infos on this topic today. I will create a complete example on our database and publish it here in the next few days. Thank you for your patience!
This does look like a bug, but I can't find an obvious match in the bug database (a few look possible, like 5842445, but are vague or don't quite line up). I can only make it happen with the trigger (so I assume your inserts being against T1 rather than V1_T1 are a transcription error); and only if F4 and F5 are CHAR not VARCHAR2:
create table t1 (f1 varchar2(2), f2 varchar2(2), f3 varchar2(3),
f4 char(2), f5 char(2));
create view v1_t1 as select * from t1;
... and the instead of trigger exactly as shown in the question.
The :NEW values inside the trigger are wrong, according to DBMS_OUTPUT, but how that's affected by the column data type is something only Oracle would be able to figure out I think.
It also still happens in 11.2.0.3 (Linux). Interestingly if I change the UNION ALL to just UNION I get slightly different results; in 10g the two columns end up null, in 11g they have x:
insert into v1_t1 (
F1, F2, F3, F4, F5
)
SELECT A.V, A.S, A.F, A.T, A.Z
FROM (
SELECT 'E' V, 'N' S, 'ABC' F, 'E' T, 'E' Z FROM DUAL UNION
SELECT 'E', 'Y', 'QWE', 'O', 'E' FROM DUAL UNION
SELECT 'I', 'Y', 'GHJ', 'I', 'I' FROM DUAL
) A
ORDER BY 1, 2, 3;
3 rows created.
select * from v1_t1;
F1 F2 F3 F4 F5
-- -- --- -- --
E N ABC x x
E Y QWE x x
I Y GHJ x x
... which is even stranger - looks like maybe a fix to some other bug has slightly affected this one.
So not really an answer; you'd need to rase a service request with Oracle, and I'm fairly sure they'd just tell you to remove the order by since it doesn't have any value, as you already know.
For Thilo; plan without any order by (11g):
----------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------
| 0 | INSERT STATEMENT | | 3 | 51 | 9 (34)| 00:00:01 |
| 1 | LOAD TABLE CONVENTIONAL | V1_T1 | | | | |
| 2 | VIEW | | 3 | 51 | 9 (34)| 00:00:01 |
| 3 | SORT UNIQUE | | 3 | | 9 (78)| 00:00:01 |
| 4 | UNION-ALL | | | | | |
| 5 | FAST DUAL | | 1 | | 2 (0)| 00:00:01 |
| 6 | FAST DUAL | | 1 | | 2 (0)| 00:00:01 |
| 7 | FAST DUAL | | 1 | | 2 (0)| 00:00:01 |
----------------------------------------------------------------------------------
And plan with order by 1,2,3 or 1,2,3,4,5 - same plan hash value (11g):
----------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------
| 0 | INSERT STATEMENT | | 3 | 51 | 10 (40)| 00:00:01 |
| 1 | LOAD TABLE CONVENTIONAL | V1_T1 | | | | |
| 2 | SORT ORDER BY | | 3 | 51 | 10 (40)| 00:00:01 |
| 3 | VIEW | | 3 | 51 | 9 (34)| 00:00:01 |
| 4 | SORT UNIQUE | | 3 | | 9 (78)| 00:00:01 |
| 5 | UNION-ALL | | | | | |
| 6 | FAST DUAL | | 1 | | 2 (0)| 00:00:01 |
| 7 | FAST DUAL | | 1 | | 2 (0)| 00:00:01 |
| 8 | FAST DUAL | | 1 | | 2 (0)| 00:00:01 |
----------------------------------------------------------------------------------
And I see the same sort of corruption selecting from other tables, but only if the results in the subquery are unioned before being ordered; though then I get nulls rather than x. (I briefly wondered if the x was coming from dual itself, but dummy is upper-case X, and this shows lower-case x).
Following #Annjawn's comment, changing the insert from V1_T1 to a direct insert in T1 works fine (i.e. correct values inserted), and curiously has the same plan hash even though it shows the table name instead of the view in the Name column. Work with either UNION or UNION ALL, too, and in both 10gR2 and 11gR2. Seems to be the trigger that's confused by the union, I guess.
Further to the datatype point... the view has to have char columns, the table does not necessarily, which isn't really a surprise since the trigger on the view seems to be the problem. If I set the table up with char columns but cast them to varchar2 in the view then I don't see the problem:
create table t1 (f1 varchar2(2), f2 varchar2(2), f3 varchar2(3),
f4 char(2), f5 char(2));
create view v1_t1 as select f1, f2, f3, cast(f4 as varchar(2)) f4,
cast(f5 as varchar(2)) f5
from t1;
But If I do it the other way around it does exhibit the problem:
create table t1 (f1 varchar2(2), f2 varchar2(2), f3 varchar2(3),
f4 varchar(2), f5 varchar(2));
create view v1_t1 as select f1, f2, f3, cast(f4 as char(2)) f4,
cast(f5 as char(2)) f5
from t1;
Unless I am missing something, this entire ordeal works fine and inserts the rows into table T1 as expected in both Oracle 10g and 11g.
create table t1 (f1 varchar2(10),
f2 varchar2(10),
f3 varchar2(10),
f4 varchar2(10),
f5 varchar2(10));
create or replace view v_t1 as select * from t1;
CREATE OR REPLACE TRIGGER V1_T1_BIUD
INSTEAD OF INSERT OR UPDATE OR DELETE
ON v_t1
FOR EACH ROW
DECLARE
AnyId NUMBER;
BEGIN
IF INSERTING THEN
INSERT INTO t1 (
F1, F2, F3, F4, F5
) VALUES (
:new.F1, :new.F2, :new.F3, :new.F4, :new.F5
);
ELSIF UPDATING THEN
UPDATE t1 SET F1 = :new.F1,
F2 = :new.F2,
F3 = :new.F3,
F4 = :new.F4,
F5 = :new.F5
WHERE F1 = :old.F1;
ELSIF DELETING THEN
DELETE FROM t1
WHERE F1 = :old.F1;
END IF;
END;
--With UNION ALL
INSERT INTO V_T1 (
F1, F2, F3, F4, F5
)
SELECT A.V, A.S, A.F, A.T, A.Z
FROM (
SELECT 'E' V, 'N' S, 'ABC' F, 'E' T, 'E' Z FROM DUAL UNION ALL
SELECT 'E', 'Y', 'QWE', 'O', 'E' FROM DUAL UNION ALL
SELECT 'I', 'Y', 'GHJ', 'I', 'I' FROM DUAL
) A
ORDER BY 1, 2, 3;
commit;
select * from t1;
F1 F2 F3 F4 F5
---------- ---------- ---------- ---------- ----------
E N ABC E E
E Y QWE O E
I Y GHJ I I
delete from t1;
commit;
--With UNION
INSERT INTO V_T1 (
F1, F2, F3, F4, F5
)
SELECT A.V, A.S, A.F, A.T, A.Z
FROM (
SELECT 'E' V, 'N' S, 'ABC' F, 'E' T, 'E' Z FROM DUAL UNION
SELECT 'E', 'Y', 'QWE', 'O', 'E' FROM DUAL UNION
SELECT 'I', 'Y', 'GHJ', 'I', 'I' FROM DUAL
) A
ORDER BY 1, 2, 3;
commit;
select * from t1;
F1 F2 F3 F4 F5
---------- ---------- ---------- ---------- ----------
E N ABC E E
E Y QWE O E
I Y GHJ I I
Sure enough, when I change F4 and F5 to char(10) from varchar2(10) (as noted by Alex Poole) I can re-create your issue exactly in both 10g and 11g.

Resources