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

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

Related

Converting rows into Column in Oracle without any relation

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>

SQL | SPLIT COLUMNS INTO ROWS

How can I split the column data into rows with basic SQL.
COL1 COL2
1 A-B
2 C-D
3 AAA-BB
Result
COL1 Col2
1 A
1 B
2 C
2 D
3 AAA
3 BB
From Oracle 12, if it is always two delimited values then you can use:
SELECT t.col1,
l.col2
FROM table_name t
CROSS JOIN LATERAL (
SELECT SUBSTR(col2, 1, INSTR(col2, '-') - 1) AS col2 FROM DUAL
UNION ALL
SELECT SUBSTR(col2, INSTR(col2, '-') + 1) FROM DUAL
) l
Which, for the sample data:
CREATE TABLE table_name (COL1, COL2) AS
SELECT 1, 'A-B' FROM DUAL UNION ALL
SELECT 2, 'C-D' FROM DUAL UNION ALL
SELECT 3, 'AAA-BB' FROM DUAL;
Outputs:
COL1
COL2
1
A
1
B
2
C
2
D
3
AAA
3
BB
db<>fiddle here
Snowflake is tagged, so here's the snowflake way of doing this:
WITH TEST (col1, col2) as
(select 1, 'A-B' from dual union all
select 2, 'C-D' from dual union all
select 3, 'AAA-BB' from dual
)
SELECT test.col1, table1.value
FROM test, LATERAL strtok_split_to_table(test.col2, '-') as table1
ORDER BY test.col1, table1.value;
As of Oracle:
SQL> with test (col1, col2) as
2 (select 1, 'A-B' from dual union all
3 select 2, 'C-D' from dual union all
4 select 3, 'AAA-BB' from dual
5 )
6 select col1,
7 regexp_substr(col2, '[^-]+', 1, column_value) col2
8 from test cross join
9 table(cast(multiset(select level from dual
10 connect by level <= regexp_count(col2, '-') + 1
11 ) as sys.odcinumberlist))
12 order by col1, col2;
COL1 COL2
---------- ------------------------
1 A
1 B
2 C
2 D
3 AAA
3 BB
6 rows selected.
SQL>
For MS-SQL 2016 and higher you can use:
SELECT Col1, x.value
FROM t CROSS APPLY STRING_SPLIT(t.Col2, '-') as x;
BTW: If Col2 contains null, it does not appear in the result.

converting single column to equal rows in Oracle

I have a requirement to display one column in a matrix table.
ex:
Select SlotNumber from Parking_lot_table;
out:
SL01
SL02
SL03
SL04
SL05
SL06
SL07
SL08
SL09
SL10
My Output must be like :
Col1 Col2 Col3 Col4 Col5
SL01 SL02 SL03 SL04 SL05
SL06 SL07 SL08 SL09 SL10
even Its fine to fix the number of columns ...
Kindly suggest ... How to do this in Oracle SQL
select * from
( select trunc((rownum -1) / 5 ) gr , mod(rownum, 5) rn, slot
from
( select * from
parking_lot_table order by to_number(REGEXP_REPLACE(slot, '[^0-9]+', ''))
)
)
pivot
( max(slot)
for rn in ( 1 as Col1, 2 as Col2, 3 as Col3, 4 as Col4, 0 as Col5) )
order by 1

how to find the value which is common in a column for all the values in other column?

If i'm having a table like this.
col1 col2 col3
1 2 3
1 3 2
1 2 1
1 2 2
1 2 3
I want only the col2 values which appears for all three values(1,2,3) in col3
col2
2
How to get result like this guys?
select col2
from your_table
group by col2
having count(distinct col3) = (select count(distinct col3) from your_table)
Assuming that 1, 2 and 3 are the only acceptable values for col3 (and that the column is not null), then you could do it like this:
with sample_data as (select 1 col1, 2 col2, 3 col3 from dual union all
select 1 col1, 3 col2, 2 col3 from dual union all
select 1 col1, 2 col2, 1 col3 from dual union all
select 1 col1, 2 col2, 2 col3 from dual union all
select 1 col1, 2 col2, 3 col3 from dual),
res as (select col1,
col2,
col3,
count(distinct col3) over (partition by col1, col2) cnt_distinct_col3
from sample_data)
select col1,
col2,
col3
from res
where cnt_distinct_col3 = 3;
COL1 COL2 COL3
---------- ---------- ----------
1 2 1
1 2 2
1 2 3
1 2 3
I wasn't sure from your sample_data if you were just going from col2, or whether it was col1 + col2; eg, if the sample data was:
COL1 COL2 COL3
---------- ---------- ----------
1 2 3
1 3 2
1 2 1
1 2 2
1 2 3
2 3 1
2 3 3
Would you expect to see col2 = 2 and col2 = 3 rows returned, or would you only expect to see (col1, col2) = (1, 2) rows returned?
I have assumed the latter, but if it's the former, it's just a case of taking col1 out of the partition clause in the analytic function. And then, of course, changing the select to "select distinct col2".
So, depending on how you want the answers displayed, I think my query works for the situations where you want to show col1 in the results (ie. unfiltered) and when you want to filter by a specific col1 value, e.g;
with sample_data as (select 1 col1, 2 col2, 3 col3 from dual union all
select 1 col1, 3 col2, 2 col3 from dual union all
select 1 col1, 2 col2, 1 col3 from dual union all
select 1 col1, 2 col2, 2 col3 from dual union all
select 1 col1, 2 col2, 3 col3 from dual union all
select 2 col1, 3 col2, 1 col3 from dual union all
select 2 col1, 3 col2, 2 col3 from dual union all
select 2 col1, 3 col2, 3 col3 from dual union all
select 2 col1, 2 col2, 1 col3 from dual union all
select 3 col1, 1 col2, 2 col3 from dual union all
select 3 col1, 1 col2, 3 col3 from dual),
res as (select col1,
col2,
col3,
count(distinct col3) over (partition by col1, col2) cnt_distinct_col3
from sample_data)
select distinct col1,
col2
from res
where cnt_distinct_col3 = 3;
COL1 COL2
---------- ----------
2 3
1 2
with sample_data as (select 1 col1, 2 col2, 3 col3 from dual union all
select 1 col1, 3 col2, 2 col3 from dual union all
select 1 col1, 2 col2, 1 col3 from dual union all
select 1 col1, 2 col2, 2 col3 from dual union all
select 1 col1, 2 col2, 3 col3 from dual union all
select 2 col1, 3 col2, 1 col3 from dual union all
select 2 col1, 3 col2, 2 col3 from dual union all
select 2 col1, 3 col2, 3 col3 from dual union all
select 2 col1, 2 col2, 1 col3 from dual union all
select 3 col1, 1 col2, 2 col3 from dual union all
select 3 col1, 1 col2, 3 col3 from dual),
res as (select col1,
col2,
col3,
count(distinct col3) over (partition by col1, col2) cnt_distinct_col3
from sample_data)
select distinct col2
from res
where cnt_distinct_col3 = 3
and col1 = 1;
COL2
----------
2
Ok, I think this is what you're after:
with sample_data as (select 1 col1, 2 col2, 3 col3 from dual union all
select 1 col1, 3 col2, 2 col3 from dual union all
select 1 col1, 2 col2, 1 col3 from dual union all
select 1 col1, 2 col2, 2 col3 from dual union all
select 1 col1, 2 col2, 3 col3 from dual union all
select 2 col1, 3 col2, 1 col3 from dual union all
select 2 col1, 3 col2, 2 col3 from dual union all
select 2 col1, 3 col2, 3 col3 from dual union all
select 2 col1, 2 col2, 1 col3 from dual union all
select 3 col1, 1 col2, 2 col3 from dual union all
select 3 col1, 1 col2, 3 col3 from dual),
res as (select col1,
col2,
col3,
count(distinct col3) over (partition by col1, col2) cnt_distinct_col3_per_col1col2,
count(distinct col3) over (partition by col1) cnt_distinct_col3_per_col1
from sample_data)
select distinct col2
from res
where cnt_distinct_col3_per_col1col2 = cnt_distinct_col3_per_col1
and col1 = 1;
COL2
----------
2
The way this works is:
find the number of distinct col3 values for each col1 value. (So in the example above, for col1 = 1, there are 3 different col3 values.)
find the number of distinct col3 values for each (col1, col2) pairing. (So in the example above, for (col1, col2) = (1, 2) there are 3 different col3 values, and for (col1, col2) = (1, 3) there is only 1)
select the rows where these two numbers match. (So from the above example, only (col1, col2) = (1, 2) has the same number of distinct values (3) as col1 = 1 does (3).

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