Oracle Pivot Column in group query - oracle

I have the following working query:
WITH pivot_data AS (
select PSGROUP,
PSCOLUMN as PSCOLUMN
FROM LOG_PS_STATUS
)
SELECT *
FROM pivot_data
PIVOT (
MAX(NULL) --<-- pivot_clause
FOR PSCOLUMN--<-- pivot_for_clause
IN (&PS_COLUMNS.) --<-- pivot_in_clause
);
It shows results as expected:
Values:
PSGroup PSColumn
A 1
A 2
A 3
B 1
B 2
B 3
C 3
Result is giving like:
PSGroup(Column vertically) PSColoumn(Horizontally)
1 2 3
A
B
C
Now I want to make PSGroup column as group of PSColumn and output should be like:
A
1 2 3
B
1 2 3
C
3

You can use listagg:
WITH pivot_data(PSGroup,PSColumn) AS (
select 'A', 1 FROM dual UNION all
select 'A', 2 FROM dual UNION all
select 'A', 3 FROM dual UNION all
select 'B', 1 FROM dual UNION all
select 'B', 2 FROM dual UNION all
select 'B', 3 FROM dual UNION all
select 'C', 3 FROM DUAL),
res(PSGroup,PSColumns) as(
SELECT PSGroup, LISTAGG(PSColumn, ' ') WITHIN GROUP (order by PSColumn)
FROM pivot_data
GROUP BY PSGroup)
select DECODE(PSColumns,NULL,PSGroup,NULL) AS PSGroup, PSColumns from(
select PSGroup, NULL as PSColumns from res
union all
select PSGroup, PSColumns from res)t
ORDER BY t.PSGroup, t.PSColumns NULLS first
Also note that LISTAGG(PSColumn, ' ') WITHIN GROUP (order by PSColumn) is limited to 4000 characters

Related

Select statement with a dynamic group by clause

How possible to group records conditionally? For example I have records with FLOW_NUMBER column, values 1, 2, 3. When FLOW_NUMBER value is 1, I want to group this records, but when FLOW_NUMBER is 2 or 3, I don't want to group.
SELECT
LISTAGG(DISTINCT PRODORD, ',') PRODORD,
SUM(QTY) AS QTY
FROM (
SELECT
1 AS FLOW_NUMBER,
'A' AS PRODORD,
1 AS QTY
FROM DUAL
UNION ALL
SELECT
1 AS FLOW_NUMBER,
'B' AS PRODORD,
1 AS QTY
FROM DUAL
UNION ALL
SELECT
1 AS FLOW_NUMBER,
'C' AS PRODORD,
1 AS QTY
FROM DUAL
UNION ALL
SELECT
2 AS FLOW_NUMBER,
'A' AS PRODORD,
1 AS QTY
FROM DUAL
UNION ALL
SELECT
2 AS FLOW_NUMBER,
'B' AS PRODORD,
1 AS QTY
FROM DUAL
UNION ALL
SELECT
2 AS FLOW_NUMBER,
'C' AS PRODORD,
1 AS QTY
FROM DUAL
UNION ALL
SELECT
3 AS FLOW_NUMBER,
'A' AS PRODORD,
1 AS QTY
FROM DUAL
UNION ALL
SELECT
3 AS FLOW_NUMBER,
'B' AS PRODORD,
1 AS QTY
FROM DUAL
UNION ALL
SELECT
3 AS FLOW_NUMBER,
'C' AS PRODORD,
1 AS QTY
FROM DUAL
)
GROUP BY FLOW_NUMBER -- bad group by clause
There is example photo of group by which I need.
SELECT SUBSTR(flow,1,INSTR(flow,'/')-1) AS flow, LISTAGG(prodorder,',') WITHIN GROUP(ORDER BY prodorder) AS prodorders, SUM(qty) AS qty
FROM (
SELECT CASE WHEN flow=1 THEN '1/' ELSE to_CHAR(flow)||'/'||ROWNUM END AS flow,
prodorder, qty FROM DATA
)
GROUP BY flow;
I don't think there is anyway to achieve it except using 2 query combined with UNION ALL clause -
SELECT FLOW_NUMBER, LISTAGG(DISTINCT PRODORD, ',') PRODORD,
SUM(QTY) AS QTY
FROM temp
WHERE FLOW_NUMBER = 1
GROUP BY FLOW_NUMBER
UNION ALL
SELECT FLOW_NUMBER, PRODORD,
QTY
FROM temp
WHERE FLOW_NUMBER <> 1;
Demo.
You can try this:
select
substr(fn,1,decode(instr(fn,','),0,1000,instr(fn,','))-1) flow_number,
prodord, qty
from (SELECT
case when FLOW_NUMBER=1 then to_char(flow_number) else to_char(flow_number)||','||rownum end fn,
LISTAGG(DISTINCT PRODORD, ',') PRODORD,
SUM(QTY) AS QTY
FROM (
SELECT
1 AS FLOW_NUMBER,
'A' AS PRODORD,
1 AS QTY
FROM DUAL
UNION ALL
SELECT
1 AS FLOW_NUMBER,
'B' AS PRODORD,
1 AS QTY
FROM DUAL
UNION ALL
SELECT
1 AS FLOW_NUMBER,
'C' AS PRODORD,
1 AS QTY
FROM DUAL
UNION ALL
SELECT
2 AS FLOW_NUMBER,
'A' AS PRODORD,
1 AS QTY
FROM DUAL
UNION ALL
SELECT
2 AS FLOW_NUMBER,
'B' AS PRODORD,
1 AS QTY
FROM DUAL
UNION ALL
SELECT
2 AS FLOW_NUMBER,
'C' AS PRODORD,
1 AS QTY
FROM DUAL
UNION ALL
SELECT
3 AS FLOW_NUMBER,
'A' AS PRODORD,
1 AS QTY
FROM DUAL
UNION ALL
SELECT
3 AS FLOW_NUMBER,
'B' AS PRODORD,
1 AS QTY
FROM DUAL
UNION ALL
SELECT
3 AS FLOW_NUMBER,
'C' AS PRODORD,
1 AS QTY
FROM DUAL
)
GROUP BY case when FLOW_NUMBER=1 then to_char(flow_number) else to_char(flow_number)||','||rownum end);
I used rownum here, but you can probably replace it by other things (e.g. rowid, if the data is from a table, just be sure to have a unique value)

how to make into an array and also count

I have this data:
app
asscs
mod_asscs
a
56
cb-56
a
67
cb-67
b
38
cb-38
a
12
12
I want to group by column 'app' and count the cases where 'mod_asscs' value is equal to concat('cb-', asscs). I also want to output the array in a separate column 'mod_asscs_array' so that the output is the following:
app
mod_asscs_array
scs_count
a
cb-56, cb-67
2
b
cb-38
1
So far this is what I have:
SELECT DISTINCT
app,
( CASE WHEN concat('cb-', asscs) = mod_asscs THEN mod_asscs || ',') AS mod_asscs_array,
COUNT( CASE WHEN concat('cb-', asscs) = mod_asscs THEN mod_asscs || ',') AS scs_count
FROM data_table
GROUP BY
app
Looks like aggregation.
Sample data:
SQL> with test (app, asscs, mod_asscs) as
2 (select 'a', 56, 'cb-56' from dual union all
3 select 'a', 67, 'cb-67' from dual union all
4 select 'b', 38, 'cb-38' from dual union all
5 select 'a', 12, '12' from dual
6 )
Query:
7 select app,
8 listagg(mod_asscs, ', ') within group (order by mod_asscs) array,
9 count(*) cnt
10 from test
11 where mod_asscs = 'cb-'|| asscs
12 group by app;
A ARRAY CNT
- -------------------- ----------
a cb-56, cb-67 2
b cb-38 1
SQL>
If you need to show all the "apps", even those with a "count" of zero, then you need conditional aggregation, something like this:
with
test (app, asscs, mod_asscs) as (
select 'a', 56, 'cb-56' from dual union all
select 'a', 67, 'cb-67' from dual union all
select 'b', 38, 'cb-38' from dual union all
select 'a', 12, '12' from dual union all
select 'c', 33, 'cb-23' from dual
)
select app,
listagg(case when mod_asscs = 'cb-' || asscs
then mod_asscs end, ', ')
within group (order by asscs) as mod_asscs_array,
count(case when mod_asscs = 'cb-' || asscs
then mod_asscs end) as scs_count
from test
group by app
order by app -- if needed
;
APP MOD_ASSCS_ARRAY SCS_COUNT
--- -------------------- ----------
a cb-56, cb-67 2
b cb-38 1
c 0
if there are duplicates in the array, do I do listagg(distinct.. and count(distinct case when ...?
You cannot use LISTAGG(DISTINCT ... as the LISTAGG function does not currently support the DISTINCT keyword; instead you need to use DISTINCT first in a sub-query and then use LISTAGG:
SELECT app,
LISTAGG(mod_asscs, ',') WITHIN GROUP (ORDER BY mod_asscs)
AS mod_asscs_array,
COUNT(*) AS scs_count
FROM (
SELECT DISTINCT
app,
mod_asscs
FROM data_table
WHERE 'cb-' || asscs = mod_asscs
)
GROUP BY app
Which, for the sample data:
CREATE TABLE data_table (app, asscs, mod_asscs) AS
SELECT 'a', 56, 'cb-56' FROM DUAL UNION ALL
SELECT 'a', 56, 'cb-56' FROM DUAL UNION ALL
SELECT 'a', 67, 'cb-67' FROM DUAL UNION ALL
SELECT 'b', 38, 'cb-38' FROM DUAL UNION ALL
SELECT 'a', 12, '12' FROM DUAL;
Outputs:
APP
MOD_ASSCS_ARRAY
SCS_COUNT
a
cb-56,cb-67
2
b
cb-38
1
db<>fiddle here

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.

Oracle 9.2 pivot distinct value

The pivot function is available from Oracle 11 and i will need similar result using Oracle 9.2.
The main argument is that i need to pivot some values with a distinct result in a table like this:
id col3
1 a
1 b
--
2 a
2 a
2 b
--
3 a
3 b
3 c
My result sould be something like this
id a b c
1 1 1 0
2 1 1 0
3 1 1 1
To create a "manual" pivot i'm using a case/when but I am not able to understand how to get distinct value.
Right now the query is this:
with t as
( select 1 as id, 'a' as col1 from dual union all
select 1 as id, 'b' from dual union all
select 2 as id, 'a' from dual union all
select 2 as id, 'a' from dual union all
select 2 as id, 'b' from dual union all
select 3 as id, 'a' from dual union all
select 3 as id, 'b' from dual union all
select 3 as id, 'c' from dual
)
select t.id,
count(case when t.col1 = 'a' then 1 end) a,
count(case when t.col1 = 'b' then 1 end) b,
count(case when t.col1 = 'c' then 1 end) c
This produce correct value but obviously it just "count" the total a/b/c value and not the distinct.
thanks for the support
If I correctly understand your need, you can try something like the following; it aggregates by id and counts the distinct values of col3:
with t as
( select 1 as id, 'a' as col1 from dual union all
select 1 as id, 'b' from dual union all
select 2 as id, 'a' from dual union all
select 2 as id, 'a' from dual union all
select 2 as id, 'b' from dual union all
select 3 as id, 'a' from dual union all
select 3 as id, 'b' from dual union all
select 3 as id, 'c' from dual
)
select id,
count(distinct decode (col1, 'a', id, null)) a,
count(distinct decode (col1, 'b', id, null)) b,
count(distinct decode (col1, 'c', id, null)) c
from t
group by id
Of course the query depends on the number of different values of col3, but this is the same problem than pivot.

Oracle - How to return multi records from CASE

How to return multiple records from "CASE DEFAULT".
eg:
Master.COLUMN1 IN (CASE '#InputString'
WHEN 'One' THEN 1
WHEN 'Two' THEN 2
WHEN 'THREE' THEN 3
ELSE (SELECT NUM_BER FROM TABLE1 WHERE COLUMN2 LIKE '%#InputString%')
END)
I tried with passing One and it returns 1. But when I pass 'four' it showed error like ORA-01427 single-row sub query returns more than one row. How can i solve this??
you can try it like this:
column1 in (CASE '#InputString'
WHEN 'One' THEN 1
WHEN 'Two' THEN 2
WHEN 'THREE' THEN 3
END)
OR (column1 in (SELECT NUM_BER FROM TABLE1 WHERE COLUMN2 LIKE '%#InputString%')
and '#InputString' not in ('One', 'Two', 'THREE'));
Here is a sqlfiddle demo
You're getting the ORA-01427 error, because in select query in your else parts returns more than one row.
The below queries will help you:
WITH t AS
(SELECT 1 string FROM dual
UNION
SELECT 2 string FROM dual
UNION
SELECT 1234 string FROM dual
)
SELECT * FROM t
where t.string in ( case 'one' when 'one' then 1 else (select 1234 from dual) end);
output
_____
1
WITH t AS
(SELECT 1 string FROM dual
UNION
SELECT 2 string FROM dual
UNION
SELECT 1234 string FROM dual
)
SELECT * FROM t
where t.string in ( case 'two' when 'one' then 1 else (select 1234 from dual) end);
output
------
1234
You can use decode
SQL> ed
Wrote file afiedt.buf
1 with x as (
2 select 'A' as string from dual union all
3 select 'B' from dual union all
4 select 'C' from dual union all
5 select 'D' from dual
6 )
7 select string , decode(string, 'A',1,'B',2,3) as string_out
8* from x
SQL> /
S STRING_OUT
- ----------
A 1
B 2
C 3
D 3

Resources