group by conditional on two columns in hibernate - sql

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
;

Related

How to multiply rows in oracle query by count column?

I have example table:
OVERALL_COUNT | NAME | OTHER_ATTRIBUTE
1 | ABC | q
5 | ACB | w
2 | CBA | e
........
[many rows, like thousands]
Update: the rows in overall_count column are unknown, they are calculated by other query
I need to get specific result where names and other attributes duplicates by overall_count.
How I can write query to get result like this?
ABC | q
ACB | w
ACB | w
ACB | w
ACB | w
ACB | w
CBA | e
CBA | e
For 12c and above you may use lateral join:
with a(OVERALL_COUNT, NAME, OTHER_ATTRIBUTE) as (
select 1, 'ABC', 'q' from dual union all
select 5, 'ACB', 'w' from dual union all
select 2, 'CBA', 'e' from dual
)
select a.*
from a
cross join lateral (
select 1
from dual
connect by level <= a.overall_count
)
order by name
OVERALL_COUNT | NAME | OTHER_ATTRIBUTE
------------: | :--- | :--------------
1 | ABC | q
5 | ACB | w
5 | ACB | w
5 | ACB | w
5 | ACB | w
5 | ACB | w
2 | CBA | e
2 | CBA | e
Or for 11 version:
with b(
overall_count
, name
, other_attribute
, l
) as (
select a.*, 1
from a
union all
select
overall_count
, name
, other_attribute
, l + 1
from b
where l < overall_count
)
select *
from b
order by name
OVERALL_COUNT | NAME | OTHER_ATTRIBUTE | L
------------: | :--- | :-------------- | -:
1 | ABC | q | 1
5 | ACB | w | 1
5 | ACB | w | 2
5 | ACB | w | 4
5 | ACB | w | 5
5 | ACB | w | 3
2 | CBA | e | 2
2 | CBA | e | 1
db<>fiddle here
You could use a join approach with the help of a sequence table:
WITH seq AS (
SELECT 1 AS cnt FROM dual UNION ALL
SELECT 2 FROM dual UNION ALL
SELECT 3 FROM dual UNION ALL
SELECT 4 FROM dual UNION ALL
SELECT 5 FROM dual
)
SELECT t1.NAME, t1.OTHER_ATTRIBUTE
FROM yourTable t1
INNER JOIN seq t2
ON t2.cnt <= t1.OVERALL_COUNT
ORDER BY t1.NAME;
Demo
Note that I hard-coded a trivial sequence table with only 5 values (which happens to cover all your sample data). Check this SO question and answers for some more ideas on how to work with sequence tables in Oracle.

Oracle SQL start with/prior

I've successfully mananaged to understand how the connect by level works with the below example:
SELECT
level,
t.*
FROM
(
SELECT
'a' AS col1,
'b' AS col2
FROM
dual
UNION ALL
SELECT
'c',
'd'
FROM
dual
) t
CONNECT BY
level <= 3
However, I'm struggling to understand the 'start with' and 'prior' concepts and what use cases do they have in real life. Could someone please walk me through using the provided example?
If you have a parent/child relationship:
CREATE TABLE t ( parent, child ) AS
SELECT 'a', 'b' FROM dual UNION ALL
SELECT 'b', 'c' FROM dual UNION ALL
SELECT 'c', 'd' FROM dual UNION ALL
SELECT 'd', 'e' FROM dual;
And you want to get the family tree starting from b and get all of the descendants then you can:
SELECT level,
t.*
FROM t
START WITH parent = 'b'
CONNECT BY PRIOR child = parent
Which outputs:
LEVEL | PARENT | CHILD
----: | :----- | :----
1 | b | c
2 | c | d
3 | d | e
Level 1 starts with b then level 2 has b's child c then level 3 has b's child's child (grandchild) d and they are all connected by the relationship that the PRIOR child is the (current) parent.
More examples of how to get different relationships can be found in this answer.
As an aside, your example in the question is a little confusing as it is finding all paths to a depth of 3 recursions. If you show the paths it has taken through the data using SYS_CONNECT_BY_PATH then you get a better idea:
SELECT level,
t.*,
SYS_CONNECT_BY_PATH( '('||col1||','||col2||')', '->' ) AS path
FROM (
SELECT 'a' AS col1, 'b' AS col2 FROM dual UNION ALL
SELECT 'c', 'd' FROM dual
) t
CONNECT BY level <= 3
Which outputs:
LEVEL | COL1 | COL2 | PATH
----: | :--- | :--- | :--------------------
1 | a | b | ->(a,b)
2 | a | b | ->(a,b)->(a,b)
3 | a | b | ->(a,b)->(a,b)->(a,b)
3 | c | d | ->(a,b)->(a,b)->(c,d)
2 | c | d | ->(a,b)->(c,d)
3 | a | b | ->(a,b)->(c,d)->(a,b)
3 | c | d | ->(a,b)->(c,d)->(c,d)
1 | c | d | ->(c,d)
2 | a | b | ->(c,d)->(a,b)
3 | a | b | ->(c,d)->(a,b)->(a,b)
3 | c | d | ->(c,d)->(a,b)->(c,d)
2 | c | d | ->(c,d)->(c,d)
3 | a | b | ->(c,d)->(c,d)->(a,b)
3 | c | d | ->(c,d)->(c,d)->(c,d)
You get 14 rows because you get 2 rows at level 1 (one for each combination of input row) and then 4 rows at level 2 (one for each input row for each level 1 row) and then 8 rows at level 2 (one for each input row for each level 2 row) and your output is growing exponentially.
db<>fiddle here

Oracle Group and Pivot - Dynamic Pivot in Oracle

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
)
)

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