Oracle Group and Pivot - Dynamic Pivot in Oracle - sql

I have a table like this:
+-----+------+------------+
| SN | CASE | CASE_VALUE |
+-----+------+------------+
| A | AA | 1 |
| A | AB | 5 |
| A | AC | 3 |
| A | AD | 4 |
| B | BA | 5 |
| B | BB | 7 |
| B | BC | 5 |
| B | BD | 1 |
+-----+------+------------+
Not sure if there is any way to get
+-----+--------+--------------+--------+--------------+--------+--------------+--------+--------------+
| SN | CASE_1 | CASE_1_VALUE | CASE_2 | CASE_2_VALUE | CASE_3 | CASE_3_VALUE | CASE_4 | CASE_4_VALUE |
+-----+--------+--------------+--------+--------------+--------+--------------+--------+--------------+
| A | AA | 1 | AB | 5 | AC | 3 | AD | 4 |
| B | BA | 5 | BB | 7 | BC | 5 | BD | 1 |
+-----+--------+--------------+--------+--------------+--------+--------------+--------+--------------+
There is no order required for those four cases
28/02/2021 Edit
If there's no order between case name?
Like blow
+-----+------+------------+
| SN | CASE | CASE_VALUE |
+-----+------+------------+
| A | AB | 1 |
| A | CD | 5 |
| A | IJ | 3 |
| A | GH | 4 |
| B | OP | 5 |
| B | EF | 7 |
| B | MN | 5 |
| B | KJ | 1 |
+-----+------+------------+

One option is using conditional aggregation in order to pivot as desired such as
SELECT sn,
MAX(CASE WHEN SUBSTR(case,2,1) = 'A' THEN case END) AS case_1,
MAX(CASE WHEN SUBSTR(case,2,1) = 'A' THEN case_value END) AS case_1_value,
MAX(CASE WHEN SUBSTR(case,2,1) = 'B' THEN case END) AS case_2,
MAX(CASE WHEN SUBSTR(case,2,1) = 'B' THEN case_value END) AS case_2_value,
MAX(CASE WHEN SUBSTR(case,2,1) = 'C' THEN case END) AS case_3,
MAX(CASE WHEN SUBSTR(case,2,1) = 'C' THEN case_value END) AS case_3_value,
MAX(CASE WHEN SUBSTR(case,2,1) = 'D' THEN case END) AS case_4,
MAX(CASE WHEN SUBSTR(case,2,1) = 'D' THEN case_value END) AS case_4_value
FROM t
GROUP BY sn
Demo
The pivot is static in the above case. You can create a function returning of SYS_REFCURSOR type as below one in order to have a dynamic pivot
CREATE OR REPLACE FUNCTION Get_Pivoted_Cols RETURN SYS_REFCURSOR IS
v_recordset SYS_REFCURSOR;
v_sql VARCHAR2(32767);
v_cols VARCHAR2(32767);
BEGIN
SELECT LISTAGG( 'MAX(CASE WHEN SUBSTR(case,2,1) = '''||cs||''' THEN case END ) AS "case_'||rn||'",
MAX(CASE WHEN SUBSTR(case,2,1) = '''||cs||''' THEN case_value END ) AS "case_'||rn||'_value"', ',')
WITHIN GROUP ( ORDER BY rn )
INTO v_cols
FROM ( SELECT DISTINCT SUBSTR(case,2,1) AS cs, ROW_NUMBER() OVER (PARTITION BY sn ORDER BY case) AS rn
FROM t );
v_sql :='SELECT sn,'|| v_cols ||' FROM t GROUP BY sn';
OPEN v_recordset FOR v_sql;
RETURN v_recordset;
END;
/
and call from SQL Developer's Command Line in order to see the result set
VAR rc REFCURSOR
EXEC :rc := Get_Pivoted_Cols;
PRINT rc

You need to pivot rows, then to use decode function to map your output like below. (I hope CASE is not the real name of your column).
with your_data (SN, "CASE", CASE_VALUE ) as (
select 'A', 'AA', 1 from dual union all
select 'A', 'AB', 5 from dual union all
select 'A', 'AC', 3 from dual union all
select 'A', 'AD', 4 from dual union all
select 'B', 'BA', 5 from dual union all
select 'B', 'BB', 7 from dual union all
select 'B', 'BC', 5 from dual union all
select 'B', 'BD', 1 from dual
)
select SN
, decode(SN, 'A', CASE_1, CASE_5)CASE_1
, decode(SN, 'A', CASE_1_VALUE, CASE_5_VALUE)CASE_1_VALUE
, decode(SN, 'A', CASE_2, CASE_6)CASE_2
, decode(SN, 'A', CASE_2_VALUE, CASE_6_VALUE)CASE_2_VALUE
, decode(SN, 'A', CASE_3, CASE_7)CASE_3
, decode(SN, 'A', CASE_3_VALUE, CASE_7_VALUE)CASE_3_VALUE
, decode(SN, 'A', CASE_4, CASE_8)CASE_4
, decode(SN, 'A', CASE_4_VALUE, CASE_8_VALUE)CASE_4_VALUE
from your_data t
pivot (
max(case_value) as value, max("CASE") FOR "CASE" in (
'AA' CASE_1
,'AB' CASE_2
,'AC' CASE_3
,'AD' CASE_4
,'BA' CASE_5
,'BB' CASE_6
,'BC' CASE_7
,'BD' CASE_8
)
)
;

Closest I could get, assuming (based on your data) that the first character in case will always match with sn we can pivot on that second character
select *
from (
select
data.*
, substr(case, 2, 1) case_t
from data
)
pivot (
max(case) type
, max(case_value) value
for case_t in (
'A' case_1
, 'B' case_2
, 'C' case_3
, 'D' case_4
)
)

Related

Filtering a table via another table's values

I have 2 tables:
Value
+----+-------+
| id | name |
+----+-------+
| 1 | Peter |
| 2 | Jane |
| 3 | Joe |
+----+-------+
Filter
+----+---------+------+
| id | valueid | type |
+----+---------+------+
| 1 | 1 | A |
| 2 | 1 | B |
| 3 | 1 | C |
| 4 | 1 | D |
| 5 | 2 | A |
| 6 | 2 | C |
| 7 | 2 | E |
| 8 | 3 | A |
| 9 | 3 | D |
+----+---------+------+
I need to retrieve the values from the Value table where the related Filter table does not contain the type 'B' or 'C'
So in this quick example this would be only Joe.
Please note this is a DB2 DB and i have limited permissions to run selects only.
Or also a NOT IN (<*fullselect*) predicate:
Only that my result is 'Joe', not 'Jane' - and the data constellation would point to that ...
WITH
-- your input, sans reserved words
val(id,nam) AS (
SELECT 1,'Peter' FROM sysibm.sysdummy1
UNION ALL SELECT 2,'Jane' FROM sysibm.sysdummy1
UNION ALL SELECT 3,'Joe' FROM sysibm.sysdummy1
)
,
filtr(id,valueid,typ) AS (
SELECT 1,1,'A' FROM sysibm.sysdummy1
UNION ALL SELECT 2,1,'B' FROM sysibm.sysdummy1
UNION ALL SELECT 3,1,'C' FROM sysibm.sysdummy1
UNION ALL SELECT 4,1,'D' FROM sysibm.sysdummy1
UNION ALL SELECT 5,2,'A' FROM sysibm.sysdummy1
UNION ALL SELECT 6,2,'C' FROM sysibm.sysdummy1
UNION ALL SELECT 7,2,'E' FROM sysibm.sysdummy1
UNION ALL SELECT 8,3,'A' FROM sysibm.sysdummy1
UNION ALL SELECT 9,3,'D' FROM sysibm.sysdummy1
)
-- real query starts here
SELECT
*
FROM val
WHERE id NOT IN (
SELECT valueid FROM filtr WHERE typ IN ('B','C')
)
;
-- out id | nam
-- out ----+-------
-- out 3 | Joe
Or also, a failing left join:
SELECT
val.*
FROM val
LEFT JOIN (
SELECT valueid FROM filtr WHERE typ IN ('B','C')
) filtr
ON filtr.valueid = val.id
WHERE valueid IS NULL
You can use EXISTS, as in:
select *
from value v
where not exists (
select null from filter f
where f.valueid = v.id and f.type in ('B', 'C')
);
Result:
ID NAME
--- -----
3 Joe
See running example at db<>fiddle.

Conditional operations without using SWITCH CASE

I have a couple of complex tables. But their mapping is something like:
TABLE_A:
_________________________________
| LINK_ID | TYPE_ID |
_________________________________
| adfasdnf23454 | TYPE 1 |
| 43fsdfsdcxsh7 | TYPE 1 |
| dfkng037sdfbd | TYPE 1 |
| sd09734fdfhsf | TYPE 2 |
| khsadf94u5dfc | TYPE 2 |
| piukvih66wfa8 | TYPE 3 |
_________________________________
TABLE_B:
_____________________________________________
| LINK_ID | CODE_ID | CODE_VALUE |
_____________________________________________
| adfasdnf23454 | 7 | test 1 |
| fgubk65esdfj7 | 6 | test 2 |
| ooogfsg354fds | 7 | test 3 |
| sd09734fdfhsf | 5 | test 4 |
_____________________________________________
The LINK_ID column links these two tables.
My requirement is to have all the records from TABLE_A checked whether they have a specific CODE_ID or not.
If the record has CODE_ID as 7 - populate CODE_VALUE in a column.
If the record has CODE_ID as 6 - populate CODE_VALUE in another column.
If the record doesn't have a CODE_ID show CODE_VALUE as null.
The catch is, TABLE_B may have records that TABLE_A don't. But the final result should contain the records of TABLE_A alone.
PS: SWITCH CASE not suggested since I would require the fields in the same row. (Please see the multiple rows for same LINK_ID in OBTAINED RESULT on using SWITCH CASE)
OBTAINED RESULT:
_______________________________________________
| LINK_ID | CODE_VALUE_1 | CODE_VALUE_1 |
_______________________________________________
| adfasdnf23454 | test 1 | null |
| adfasdnf23454 | null | test 4 |
| sd09734fdfhsf | test 6 | null |
_______________________________________________
EXPECTED RESULT:
__________________________________________________
| LINK_ID | CODE_VALUE_1 | CODE_VALUE_2 |
__________________________________________________
| adfasdnf23454 | test 1 | test 4 |
| 43fsdfsdcxsh7 | null | null |
| dfkng037sdfbd | null | null |
| sd09734fdfhsf | test 6 | null |
| khsadf94u5dfc | null | null |
| piukvih66wfa8 | null | null |
__________________________________________________
Can someone help on this?
One option uses two correlated subqueries:
select
a.link_id,
(select code_value from table_b b where b.link_id = a.link_id and b.code_id = 7) code_value_1,
(select code_value from table_b b where b.link_id = a.link_id and b.code_id = 6) code_value_2
from table_a a
Note that this assumes no duplicate (link_id, code_id) in table_b. You could also write this with two left joins - which is quite the same logic.
Another solution is a single left join, then conditional aggregation:
select
a.link_id,
max(case when b.code_id = 7 then b.code_value end) code_value_1,
max(case when b.code_id = 6 then b.code_value end) code_value_2
from table_a a
left join table_b b on b.link_id = a.link_id and b.code_id in (6, 7)
group by a.link_id
Problematic part of your question is what to do if two entries in B have same link_id and type_id. You can use min, max, last entry (but for that you need ordering column in B). Or you can list them all:
select *
from a left join b using (link_id)
pivot (listagg(code_value, ', ') within group (order by code_value)
for code_id in (6 as code6, 7 as code7))
Data:
create table a (link_id, type_id) as (
select 'a', 'TYPE 1' from dual union all
select 'b', 'TYPE 1' from dual union all
select 'c', 'TYPE 1' from dual union all
select 'd', 'TYPE 2' from dual );
create table b(LINK_ID, CODE_ID, CODE_VALUE) as (
select 'a', 6, 'test 1' from dual union all
select 'a', 7, 'test 2' from dual union all
select 'a', 7, 'test 3' from dual union all
select 'b', 7, 'test 4' from dual union all
select 'd', 6, 'test 5' from dual );
Result:
LINK_ID TYPE_ID CODE6 CODE7
a TYPE 1 test 1 test 2, test 3
b TYPE 1 test 4
c TYPE 1
d TYPE 2 test 5
dbfiddle

SQL oracle : Group and Count data

I have a table like this:
+-----+------+
| acc | CASE |
+-----+------+
| 001 | a |
| 001 | b |
| 001 | c |
| 002 | a |
| 002 | b |
| 003 | b |
| 003 | c |
| 004 | a |
| 005 | b |
| 006 | b |
| 007 | a |
| 007 | b |
| 007 | c |
| 008 | a |
| 008 | b |
| n | x |
+-----+------+
I have no idea how to group and count data with
+-----------+-----------+
| case | count_acc |
+-----------+-----------+
| a | 1 |
| b | 2 |
| c | 0 |
| a+b | 2 |
| b+c | 1 |
| a+b+c | 2 |
| a+b+c+…+x | n |
+-----------+-----------+
in case a+b,b+c ... a+b+c+…+x I can't group case and count acc. Do you have any idea to group and count?
select b.case,count(distinct(a.acc)) as account from
test a , (select acc , rtrim(case,'+') case
from ( select acc , case , rn from test
model
partition by (acc)
dimension by (row_number() over (partition by acc order by case) rn )
measures (cast(case as varchar2(10)) case)
rules
(case[any] order by rn desc = case[cv()]||'+'||case[cv()+1])
)
where rn = 1) b
where a.acc = b.acc
group by b.case
You can achieve the same using LISTAGG in oracle
with test as ( select 001 acc , 'a' case FROM DUAL UNION
SELECT 001 , 'b' FROM DUAL UNION
SELECT 001 , 'c' FROM DUAL UNION
SELECT 002 , 'a' FROM DUAL UNION
SELECT 002 , 'b' FROM DUAL UNION
SELECT 003 , 'b' FROM DUAL UNION
SELECT 003 , 'c' FROM DUAL UNION
SELECT 004 , 'a' FROM DUAL UNION
SELECT 005 , 'b' FROM DUAL UNION
SELECT 006 , 'b' FROM DUAL UNION
SELECT 007 , 'a' FROM DUAL UNION
SELECT 007 , 'b' FROM DUAL UNION
SELECT 007 , 'c' FROM DUAL UNION
SELECT 008 , 'a' FROM DUAL UNION
SELECT 008 , 'b' FROM DUAL )
select case,count(1) from
(
SELECT count(1),acc, LISTAGG(case, '+') WITHIN GROUP (ORDER BY acc) AS case
FROM test
GROUP BY acc) GROUP BY case order by 1;
SQL fiddle here
First you have to organize your data, grouping the ACC and Aggregating the CASE
SELECT
LISTAGG (case,'+') WITHIN GROUP (ORDER BY case) case,
ACC
FROM TEST
GROUP BY ACC
Them you will be able to count:
SELECT case, count(*) FROM (
SELECT
LISTAGG (case,'+') WITHIN GROUP (ORDER BY case) case,
ACC
FROM TEST
GROUP BY ACC
) GROUP BY case
ORDER BY CASE;
In case you don't have function LISTAGG, below 11g for example, refer to this website:
http://oracle-base.com/articles/misc/string-aggregation-techniques

group by conditional on two columns in hibernate

I want to group by on two columns. I want to get total of c group by a and b if b is not null and group by a if b is null
I wrote this query but it does not work in case b is null!the result of query is all rows that b is not null
select m.s.a ,
case when (m.l is not null)
then m.l.code end , coalesce(sum(m.c),0 )
from material m where m.Item.id =:itemId
group by m.s.a, case
when (m.l is not null)
then m.l.code end
+--+----+-------+---+
| | s | l | c |
+--+----+-------+---+
| | a | d | 1 |
| | a | d | 9 |
| | a | e | 3 |
| | a | f | 4 |
| | c | g | 5 |
| | c | g | 6 |
| | c | h | 20 |
| | d | null | 7 |
| | d | null | 8 |
result expected:
+--+----+-------+---+
| | s | l | c |
+--+----+-------+---+
| | a | d | 10 |
| | a | e | 3 |
| | a | f | 4 |
| | c | g | 11 |
| | c | h | 20 |
| | d | | 15 |
By default, oracle/postgres/mysql will produces the expected output.
SELECT s,l,sum(c)
FROM temp
GROUP BY s,l;
If you don't want to group by NULL values you can use UNION
SELECT s,l,sum(c)
FROM temp
WHERE l is NOT NULL
GROUP BY s,l
UNION
SELECT s,l,sum(c)
FROM temp
WHERE l is NULL;
with data (col1, col2, val) as
(
select 'a', 'd', 1 from dual union all
select 'a', 'd', 9 from dual union all
select 'a', 'e', 3 from dual union all
select 'a', 'f', 4 from dual union all
select 'c', 'g', 5 from dual union all
select 'c', 'g', 6 from dual union all
select 'c', 'h', 20 from dual union all
select 'd', null, 7 from dual union all
select 'd', null, 8 from dual union all
select 'e', 'g', null from dual -- additional check if val is null
)
,
prs (col1, col2, col1n2) as
(
select distinct col1, col2, col1||'-'||col2 from data
)
,
rs (col, val) as
(
-- concatenate the columns that need to be grouped by
-- to act as one single column (col1 and col2)
select col1||'-'||col2, sum(nvl(val,0)) from data group by col1||'-'||col2
)
select
prs.col1, prs.col2, rs.val
from
rs join prs
on (prs.col1n2 = rs.col)
order by 1
;

What does select 2 mean in a union sql statement?

I have the following code which unions two select statements but the second select starts off as select 2. What does this do?
select tax_type, sum(amount)
from bill_tax_tbl a (index multi), bill_summ_tbl b
where a.customer_no = #customer_no
and a.invoice_month = convert(tinyint,datepart(mm,dateadd(mm,-1,convert(datetime,#date))))
and a.job = b.job and a.billing_sub_job = b.billing_sub_job
and b.job_group is null
group by tax_type
union
select 2, sum(amount)
from bill_tax_tbl a (index multi), bill_summ_tbl b
where a.customer_no = #customer_no
and tax_type = 1
and a.invoice_month = convert(tinyint,datepart(mm,dateadd(mm,-1,convert(datetime,#date))))
and a.job = b.job and a.billing_sub_job = b.billing_sub_job
and b.job_group is null
2 is a value constant that will end up in the tax_type field.
Example
Given the table:
+-----+-----+
| a | b |
+===========+
| 'a' | 'b' |
| 'c' | 'd' |
+------------
Queried By:
SELECT a, b from table
UNION
SELECT 'y','z'
Would return:
+-----+-----+
| a | b |
+===========+
| 'a' | 'b' |
| 'c' | 'd' |
| 'y' | 'z' |
+------------