Converting rows into Column in Oracle without any relation - oracle

I have a query which will fetch two rows only and I want to bring second row data into columns with different column name.
Below is the original query result.
The expected result is like
Expected result.
Please help how shd I proceed, not able to figure out with PIVOT.

Here's one option; see comments within code.
SQL> with
2 your_query (column1, column2, column3) as
3 -- this is what your current query returns
4 (select 1, 'ABC', 123 from dual union all
5 select 2, 'XYZ', 456 from dual
6 ),
7 temp as
8 -- distinguish 1st from 2nd row
9 (select y.*,
10 row_number() over (order by column1) rn
11 from your_query y
12 )
13 -- finally, cross join two rows and conditionally display columns.
14 -- MAX is here to avoid empty "cells"
15 select max(case when a.rn = 1 then a.column1 end) as col1,
16 max(case when a.rn = 1 then a.column2 end) as col2,
17 max(case when a.rn = 1 then a.column3 end) as col3,
18 --
19 max(case when b.rn = 2 then b.column1 end) as col4,
20 max(case when b.rn = 2 then b.column2 end) as col5,
21 max(case when b.rn = 2 then b.column3 end) as col6
22 from temp a cross join temp b;
COL1 COL COL3 COL4 COL COL6
---------- --- ---------- ---------- --- ----------
1 ABC 123 2 XYZ 456
SQL>

Related

Requirement in Oracle PL/SQL to update labels

I have 2 tables - tab1 , tab2 with following data
tab1 data:
OID Label
1 MX1
1 MX2
1 MX3
2 MX4
2 MX5
tab2 data:
OID ID Label
1 5678
1 2347
1 9687
2 4567
2 3455
The join condition between these two tables is oid column.I need to create a process which will update Label column from tab1 to Label column of tab2.It doesn't matter which label gets assigned to which record of tab2 for a particular oid. The only check that needs to happen is that both the tables should have same number of records for an oid.The final outcome should be the following
tab2 data:
OID ID Label
1 5678 MX1
1 2347 MX2
1 9687 MX3
2 4567 MX4
2 3455 MX5
Again, it doesn't matter which label gets assigned to tab2 for a particular oid,but the same label cannot be repeated for a particular oid.What would be the best way to write a code for this requirement?
Here is a sql solution:
merge into tab2
using
(
select t2."id" as ide,t1."label" labela from
(select rownum n,"label","oid" from tab1 order by "oid")t1,
(select rownum n, a2.* from tab2 a2 order by "oid")t2
where t1.n=t2.n and
t1."oid"=t2."oid"
) tb4
on (tab2."id" = tb4.ide)
when matched then
update set tab2."label" = tb4.labela;
Result:
oid| id | label
-----------------
1 5678 mx1
1 2347 mx2
1 9687 mx3
2 4567 mx4
2 3455 mx5
Sample tables:
SQL> select * from tab1 order by oid, label;
OID LAB
---------- ---
1 mx1
1 mx2
1 mx3
2 mx4
2 mx5
SQL> select * from tab2 order by oid, id;
OID ID LAB
---------- ---------- ---
1 2347
1 5678
1 9687
2 3455
2 4567
SQL>
This is query that returns desired result:
SQL> with
2 t1 as (select oid, label, rowid rwid,
3 row_number() over (partition by oid order by label) rn
4 from tab1
5 ),
6 t2 as (select oid, id, rowid rwid,
7 row_number() over (partition by oid order by id) rn
8 from tab2
9 )
10 select b.oid, b.id, a.label
11 from t1 a join t2 b on a.oid = b.oid and a.rn = b.rn;
OID ID LAB
---------- ---------- ---
1 2347 mx1
1 5678 mx2
1 9687 mx3
2 3455 mx4
2 4567 mx5
SQL>
A few options I tried: correlated update won't work because of
ORA-01779: cannot modify a column which maps to a non key-preserved table
SQL> update (
2 with
3 t1 as (select oid, label, rowid rwid,
4 row_number() over (partition by oid order by label) rn
5 from tab1
6 ),
7 t2 as (select oid, id, rowid rwid, label,
8 row_number() over (partition by oid order by id) rn
9 from tab2
10 )
11 select b.oid, b.id, b.label b_label, a.label a_label
12 from t1 a join t2 b on a.oid = b.oid and a.rn = b.rn
13 )
14 set b_label = a_label;
set b_label = a_label
*
ERROR at line 14:
ORA-01779: cannot modify a column which maps to a non key-preserved table
SQL>
MERGE won't work because of
ORA-01732: data manipulation operation not legal on this view
SQL> merge into
2 (select oid, id, label, row_Number() over (partition by oid order by id ) rn from tab2) b
3 using (select oid, label, row_number() over (partition by oid order by label) rn from tab1) a
4 on (a.oid = b.oid and
5 a.rn = b.rn)
6 when matched then update set
7 b.label = a.label;
(select oid, id, label, row_Number() over (partition by oid order by id ) rn from tab2) b
*
ERROR at line 2:
ORA-01732: data manipulation operation not legal on this view
SQL>
Merge would accept a view (created with create view ...), but a view has to be updateable; this one can't be because it contains analytic function.
What's left is a PL/SQL procedure:
SQL> begin
2 for cur_r in (with
3 t1 as (select oid, label, rowid rwid,
4 row_number() over (partition by oid order by label) rn
5 from tab1
6 ),
7 t2 as (select oid, id, rowid rwid,
8 row_number() over (partition by oid order by id) rn
9 from tab2
10 )
11 select b.rwid, a.label
12 from t1 a join t2 b on a.oid = b.oid and a.rn = b.rn
13 )
14 loop
15 update tab2 b set
16 b.label = cur_r.label
17 where b.rowid = cur_r.rwid;
18 end loop;
19 end;
20 /
PL/SQL procedure successfully completed.
SQL> select * from tab2 order by oid, id;
OID ID LAB
---------- ---------- ---
1 2347 mx1
1 5678 mx2
1 9687 mx3
2 3455 mx4
2 4567 mx5
SQL>
Maybe someone has another idea; I'd like to see it & learn something new.

Retrieving based on specific condition

I have a little complex requirement on couple of tables which I am finding hard to crack.
There are 2 tables. TableA and TableB
TableA has a structure like:
-------------------------------------
ID COL1 COL2 CAT
-------------------------------------
1 RecAA RecAB 3
2 RecBA RecBB 3
3 RecCA RecCB 2
4 RecDA RecDB 2
5 RecEA RecEB 1
-------------------------------------
TableB has a structure like:
-----------------
COL3 TYPE
-----------------
RecAA 10
RecAA 11
RecAA 12
RecAB 10
RecAB 11
RecAB 12
RecAB 13
RecAB 14
RecBA 10
RecBA 11
RecBA 14
RecBA 15
RecBB 10
-----------------
Requirements:
Records in TableA should have CAT = 3.
Either COL1 or COL2 of TableA should be available in COL3 of TableB.
COL3 should definitely have TYPE in 10,11,12 and should have only that TYPE.
i.e As per the above requirements,
Of the records available in TableA, records with ID 1 and 2 have CAT = 3 in TableA
Both the records have atleast only value in COL3 of TableB. (Record with ID 1 in TableA has both COL1 and COL2 in TableB and record with ID 2 in TableA has COL1 in TableB)
RecAA record has Type 10,11,12 and only 10,11,12. So doesnt matter if RecAB has 10,11,12 or not. But RecBA and RecBB both does not have 10,11,12 types.
Therefore the result should be:
-------------------------------------
ID COL1 COL2 CAT
-------------------------------------
1 RecAA RecAB 3
-------------------------------------
What I tried:
WITH TEMP AS (SELECT COL3 FROM TableB GROUP BY COL3 HAVING SUM(CASE WHEN TYPE IN ('10','11','12') THEN 1 ELSE 0 END) = 0)
SELECT S.ID, S.COL1, S.COL2, S.CAT FROM TableA S
INNER JOIN TEMP T ON S.COL1 = T.COL3
WHERE S.CAT = 3;
Can someone please help on achieving this?
I think you're almost there, it's just your row selection in the CTE that seems problematic, and I think you need an OR:
WITH TEMP AS (
SELECT COL3
FROM TableB
GROUP BY COL3
HAVING SUM(POWER(2, TYPE - 10)) = 7 AND COUNT(*) = 3
)
SELECT
S.ID, S.COL1, S.COL2, S.CAT
FROM
TableA S
INNER JOIN TEMP T ON S.COL1 = T.COL3 OR S.COL2 = T.COL3
WHERE
S.CAT = 3;
I've subtracted 10 from each of your TYPEs to turn your 10,11,12 into 0,1,2 and then used POWER to turn them into 1, 2 and 4 which uniquely sum to 7 - (in other words your 10,11,12 became 2^(10-10), 2^(11-10) and 2^(12-10) which are 1, 2 and 4.. Which must then sum to 7).
I also mandate that there be a count of 3; the only way to get to 7 with three numbers that are powers of 2 is to have 1+2+4 which guarantees that 10,11,12 are present initially. If anything was missing, extra or repeated it wouldn't be 3 numbers that sum to 7
I think RecAB is excluded because even though it has 10,11,12 it also has 13,14 which cause it to be excluded..
You also seemed to be saying that COL3 should be present in either COL1 or COL2 of table A
You can use listagg analytic version to turn TYPE column into type_in_list column like below :
With temp_TableB (COL3, type_in_list) as (
SELECT distinct COL3, listagg(TYPE, ',') within group (order by TYPE)over(partition by COL3)
FROM TableB
)
select tA.*
--, tb.*
from tableA tA
INNER JOIN temp_TableB tB on (tA.COL1 = tB.COL3 or tA.COL2 = tB.COL3)
Where tA.CAT = 3
AND tB.TYPE_IN_LIST = '10,11,12'
;

Oracle sort values into column

I have a table like this:
time length name
00:01:00 2 a
00:11:22 2 a
01:01:00 45 a
00:23:00 3 b
and I want to retrieve data from the table in the form:
a b
time length time length
00:01:00 2 00:23:00 3
00:11:22 2
01:01:00 2
so it is a simple task of rearranging data, atm I am doing this in a bash script, but I wonder if there is an easy way to do it in Oracle?
You can use analytical function ROW_NUMBER and full outer join as follows:
WITH CTE1 AS
(SELECT T.*, ROW_NUMBER() OVER (ORDER BY LENGTH, TIME) AS RN FROM YOUR_TABLE T WHERE NAME = 'a'),
CTE2 AS
(SELECT T.*, ROW_NUMBER() OVER (ORDER BY LENGTH, TIME) AS RN FROM YOUR_TABLE T WHERE NAME = 'b')
SELECT A.TIME, A.LENGTH, B.TIME, B.LENGTH
FROM CTE1 A FULL OUTER JOIN CTE2 B
ON A.RN = B.RN
Note: You need to use proper order by to order the records as per your requirement. I have used LENGTH, TIME
You can use a multi-column pivot, by adding an extra column that links the related A and B values; presumably by time order, something like:
select time_col, length_col, name_col,
dense_rank() over (partition by name_col order by time_col) as rnk
from your_table;
TIME_COL LENGTH_COL N RNK
-------- ---------- - ----------
00:01:00 2 a 1
00:11:22 2 a 2
01:01:00 45 a 3
00:23:00 3 b 1
and then pivot based on that:
select *
from (
select time_col, length_col, name_col,
dense_rank() over (partition by name_col order by time_col) as rnk
from your_table
)
pivot (
max(time_col) as time_col, max(length_col) as length_col
for name_col in ('a' as a, 'b' as b)
);
RNK A_TIME_C A_LENGTH_COL B_TIME_C B_LENGTH_COL
---------- -------- ------------ -------- ------------
1 00:01:00 2 00:23:00 3
2 00:11:22 2
3 01:01:00 45
I've left the rnk value in the output; if you don't want that you can list the columns in the select list:
select a_time_col, a_length_col, b_time_col, b_length_col
from ...
Or you could do the same thing with conditional aggregation (which is what pivot uses under the hood anyway):
select
max(case when name_col = 'a' then time_col end) as time_col_a,
max(case when name_col = 'a' then length_col end) as length_col_a,
max(case when name_col = 'b' then time_col end) as time_col_b,
max(case when name_col = 'b' then length_col end) as length_col_b
from (
select time_col, length_col, name_col,
dense_rank() over (partition by name_col order by time_col) as rnk
from your_table
)
group by rnk
order by rnk;
TIME_COL LENGTH_COL_A TIME_COL LENGTH_COL_B
-------- ------------ -------- ------------
00:01:00 2 00:23:00 3
00:11:22 2
01:01:00 45
db<>fiddle

PL/SQL Oracle :- Dynamically UNPIVOT ORACLE TABLE on passing a value

I have a table as below with data:-
Item COL1 COL2 COL3 COL4 COL5 COL6 .... COL 30
A 1 1 2 3 4 2 5 2
B 2 6 4 3 5 2 5 1
C 3 4 5 2 2 2 4 2
D 4 5 2 23 45 3 3 3
F 5 3 1 11 23 34 34 1
and need to UNPIVOT depending on the value I give... If I give 4, the table is unpivoted to COL4, If I give 7 the table is unpivoted till 7, making it dynamic. I have written a simple SQL but cant get a way to make it dynamic
SELECT * FROM (
WITH
WIDE AS (
SELECT
/*+ PARALLEL(128) */
ITEM, COL1, COL2, COL3, COL4, COL5, COL6, COL7
FROM TAB
WHERE ITEM = 'A'
)
SELECT
/*+ PARALLEL(128) */
ITEM
FROM WIDE
UNPIVOT INCLUDE NULLS
(QTY FOR SCOL IN
(COL1, COL2, COL3, COL4, COL5, COL6,
COL7
)
)
);
Why don't you unpivot all possible columns and then restrict the dataset with the where clause:
SELECT ITEM, SCOL, QTY
FROM WIDE
UNPIVOT INCLUDE NULLS
(QTY FOR SCOL IN (COL1, ..., COL 30))
WHERE TO_NUMBER(SUBSTR(SCOL,4)) <= 7 -- 7 Should be replaced with your parameter

Oracle - Is it possible to "set" values inside case statement during update as below?

Is it possible to "set" values inside case statement during update as below ?
UPDATE TABLE1
CASE WHEN COL1 = 'A' THEN SET COL2 = 10, COL3 = 20, COL4 = 30
WHEN COL1 IN ('B','N') THEN SET COL2 = 1, COL3 = 5, COL4 = 7
WHEN COL1 = 'D' THEN SET COL2 = 11, COL3 = 13, COL4 = 17
ELSE SET COL2 = 0, COL3 = 0, COL4 = 0
END;
The corresponding valid syntax would be like this.
UPDATE TABLE1 SET
COL2 = (CASE WHEN COL1 = 'A' THEN 10
WHEN COL1 IN ('B','N') THEN 1
WHEN COL1 = 'D' THEN 11
ELSE 0
END),
COL3 = (CASE WHEN COL1 = 'A' THEN 20
WHEN COL1 IN ('B','N') THEN 5
WHEN COL1 = 'D' THEN 13
ELSE 0
END),
COL4 = (CASE WHEN COL1 = 'A' THEN 30
WHEN COL1 IN ('B','N') THEN 7
WHEN COL1 = 'D' THEN 17
ELSE 0
END);
Looks like you're trying to do a MERGE, with one exception. You can update the table in a single merge statement as follows, except your logic to update all non-matching rows to 0's.
SQL> create table tab1
(
col1 varchar2(10),
col2 number,
col3 number,
col4 number,
merge_flag char(1)
)
Table created.
SQL> insert into tab1 values ('A', 10,11,12,null)
1 row created.
SQL> insert into tab1 values ('B', 20,21,22,null)
1 row created.
SQL> insert into tab1 values ('C', 30,31,32,null)
1 row created.
SQL> commit
Commit complete.
SQL> select * from tab1
COL1 COL2 COL3 COL4 MERGE_FLAG
---------- ---------- ---------- ---------- ----------
A 10 11 12
B 20 21 22
C 30 31 32
3 rows selected.
SQL> merge into tab1 t
using (
select 'A' as col1, 10 as col2, 20 as col3, 30 as col4 from dual
union
select 'B' as col1, 1 as col2, 5 as col3, 7 as col4 from dual
union
select 'N' as col1, 1 as col2, 5 as col3, 7 as col4 from dual
union
select 'D' as col1, 11 as col2, 13 as col3, 17 as col4 from dual
) x
on (t.col1 = x.col1)
when matched then
update set t.col2 = x.col2, t.col3 = x.col3, t.col4 = x.col4, t.merge_flag = 'X'
Merge successfully completed.
SQL> commit
Commit complete.
SQL> select * from tab1
COL1 COL2 COL3 COL4 MERGE_FLAG
---------- ---------- ---------- ---------- ----------
A 10 20 30 X
B 1 5 7 X
C 30 31 32
3 rows selected.
You could run a single update after the merge to change all non matching rows with 0.
In order to do this via case, you would have to repeat the case for each field (as demonstrated by #MaheswaranRavisankar). That's just the way case works. An alternative would be to fabricate a sub-query that provides the same results. While this is longer, it does group related values together, which may be more readable/easier to maintain.
Given the need to set all non-matching values to zero, I would solve that by setting all values to zero first, then updating the matches the appropriate value.
UPDATE table1
SET col2 = 0, col3 = 0, col4 = 0;
UPDATE table1
SET (col2, col3, col4) =
(SELECT a.col2, a.col3, a.col4
FROM (SELECT 'A' AS col1,
10 AS col2,
20 AS col3,
30 AS col4
FROM DUAL
UNION ALL
SELECT 'B' AS col1,
5 AS col2,
5 AS col3,
7 AS col4
FROM DUAL
UNION ALL
SELECT 'N' AS col1,
5 AS col2,
5 AS col3,
7 AS col4
FROM DUAL
UNION ALL
SELECT 'D' AS col1,
13 AS col2,
7 AS col3,
17 AS col4
FROM DUAL) a
WHERE a.col1 = table1.col1);
This also hints at another option: perhaps you should consider creating a table with these values, rather than embedding them in the SQL.

Resources