Why when using grouping_id with rollup the total will be level 3 instead of 2? - oracle

Is there any logic behind the reason to why the total grouping when using rollup will be "lvl" 3?..
example:
col1
col2
id1
1
id1
2
id2
1
id2
2
id3
1
id3
2
In cube method this is understandable as
level 0 is the "basic values" (my own term)such as "col1-ID1","col2-id1", "col3-id1" etc..,
level 1 will be the subtotal row of each basic value which means subtotal of ID1(1)+ID1(2) --> id1(3) for example.
level 2 will be the total of each combination which means subtotal of 1's and subtotal of 2's , in this example: 1- subtotal will be 3 and 2-subtotal will be 6
and level 3 will be the grand total of them all, in this example: 9
my explanation might not make any sense:) .. sorry
Is there any reason behind that skipping from lvl 0/1 straight to 3? or its just the way it is?

Summary: The grand total will be in the grouping set with the id the maximum value of the grouping set which is equal to 2(number of columns in the grouping set) - 1. Therefore, with 2 columns being cubed, the grand total is in 22-1 = 3 and, with 3 columns being cubed, the grand total is in 23-1 = 7.
Given the query:
SELECT LISTAGG(col1, ',') WITHIN GROUP (ORDER BY col1) AS col1,
LISTAGG(col2, ',') WITHIN GROUP (ORDER BY col2) AS col2,
GROUPING_ID(col1, col2) AS grp
FROM table_name
GROUP BY CUBE(col1, col2)
ORDER BY grp, col1, col2
Which, for the sample data:
CREATE TABLE table_name (col1, col2) AS
SELECT 'id1', 1 FROM DUAL UNION ALL
SELECT 'id1', 2 FROM DUAL UNION ALL
SELECT 'id2', 1 FROM DUAL UNION ALL
SELECT 'id2', 2 FROM DUAL UNION ALL
SELECT 'id3', 1 FROM DUAL UNION ALL
SELECT 'id3', 2 FROM DUAL;
Outputs:
COL1
COL2
GRP
id1
1
0
id1
2
0
id2
1
0
id2
2
0
id3
1
0
id3
2
0
id1,id1
1,2
1
id2,id2
1,2
1
id3,id3
1,2
1
id1,id2,id3
1,1,1
2
id1,id2,id3
2,2,2
2
id1,id1,id2,id2,id3,id3
1,1,1,2,2,2
3
You can see that when GROUPING_ID(col1, col2) is:
The un-grouped value.
The value grouped by the first column in the grouping set (and there is one value for the first column and N values for the second column).
The value grouped by the second column in the grouping set (and there are M values for the first column and one value for the second column).
The value grouped by the both columns in the grouping set (and there are M values for the first column and N values for the second column giving N*M total values); which will give you the grand total.
If you had the sample data with 3 columns:
CREATE TABLE table2 (col1, col2, col3) AS
SELECT t1.COLUMN_VALUE,
t2.COLUMN_VALUE,
t3.COLUMN_VALUE
FROM TABLE(SYS.ODCIVARCHAR2LIST('id1', 'id2', 'id3')) t1
CROSS JOIN TABLE(SYS.ODCINUMBERLIST(1, 2)) t2
CROSS JOIN TABLE(SYS.ODCINUMBERLIST(3, 4)) t3;
Then using CUBE across 3 columns:
SELECT LISTAGG(col1, ',') WITHIN GROUP (ORDER BY col1) AS col1,
LISTAGG(col2, ',') WITHIN GROUP (ORDER BY col2) AS col2,
LISTAGG(col3, ',') WITHIN GROUP (ORDER BY col3) AS col3,
GROUPING_ID(col1, col2, col3) AS grp
FROM table2
GROUP BY CUBE(col1, col2, col3)
ORDER BY grp, col1, col2, col3
Outputs:
COL1
COL2
COL3
GRP
id1
1
3
0
id1
1
4
0
id1
2
3
0
id1
2
4
0
id2
1
3
0
id2
1
4
0
id2
2
3
0
id2
2
4
0
id3
1
3
0
id3
1
4
0
id3
2
3
0
id3
2
4
0
id1,id1
1,1
3,4
1
id1,id1
2,2
3,4
1
id2,id2
1,1
3,4
1
id2,id2
2,2
3,4
1
id3,id3
1,1
3,4
1
id3,id3
2,2
3,4
1
id1,id1
1,2
3,3
2
id1,id1
1,2
4,4
2
id2,id2
1,2
3,3
2
id2,id2
1,2
4,4
2
id3,id3
1,2
3,3
2
id3,id3
1,2
4,4
2
id1,id1,id1,id1
1,1,2,2
3,3,4,4
3
id2,id2,id2,id2
1,1,2,2
3,3,4,4
3
id3,id3,id3,id3
1,1,2,2
3,3,4,4
3
id1,id2,id3
1,1,1
3,3,3
4
id1,id2,id3
1,1,1
4,4,4
4
id1,id2,id3
2,2,2
3,3,3
4
id1,id2,id3
2,2,2
4,4,4
4
id1,id1,id2,id2,id3,id3
1,1,1,1,1,1
3,3,3,4,4,4
5
id1,id1,id2,id2,id3,id3
2,2,2,2,2,2
3,3,3,4,4,4
5
id1,id1,id2,id2,id3,id3
1,1,1,2,2,2
3,3,3,3,3,3
6
id1,id1,id2,id2,id3,id3
1,1,1,2,2,2
4,4,4,4,4,4
6
id1,id1,id1,id1,id2,id2,id2,id2,id3,id3,id3,id3
1,1,1,1,1,1,2,2,2,2,2,2
3,3,3,3,3,3,4,4,4,4,4,4
7
And will generate 23 = 8 levels (from 0 to 7) since there are all the possible combinations of grouping 3 columns and the grand-total will be in level 7; compared to 22 levels (0 to 3) when you are cubing 2 columns and the grand total is in level 3.
fiddle
Update
What I don't understand is why roll up is skipping level 2 straight to 3?
From the SELECT documentation:
ROLLUP
The ROLLUP operation in the simple_grouping_clause groups the selected rows based on the values of the first n, n-1, n-2, ... 0 expressions in the GROUP BY specification, and returns a single row of summary for each group.
[...]
CUBE
The CUBE operation in the simple_grouping_clause groups the selected rows based on the values of all possible combinations of expressions in the specification. It returns a single row of summary information for each group.
CUBE generates all possible grouping sets; ROLLUP generates groups of the first 1 column then with the first 2 columns, 3 columns, ..., up to n columns which is the same as the CUBE when the grouping sets are restricted to the 20-1, 21-1, 22-1, ..., 2n-1 (or more simply 0, 1, 3, 7, ... 2n-1).
This means that ROLLUP will skip the grouping set with id 2 as that is grouping only by the 2nd column and that is not "one of the first n, n-1, n-2, ... 0 expressions" in the GROUP BY specification.
fiddle

Related

Combination of all values (Oracle)

I would like to get all possible combinations from a given data set, but even with empty elements (i.e. with a variable number of elements)
Example we have data and a simple query that displays them:
with t as ( select 1 as COL1, 2 as COL2, 3 as COL3 from dual )
select * from t;
or better example: (perhaps simple to solve)
select 1 as col from dual
union all
select 2 from dual
union all
select 3 from dual
Is it possible to create a query that displays the following result:
1 - -
1 2 -
1 2 3
1 3 -
1 3 2
2 - -
2 1 -
2 1 3
2 3 -
2 3 1
3 - -
3 1 -
3 1 2
3 2 -
3 2 1
One difficulty here is that you have the inputs in three columns and you want the results in three columns. Unless you are willing to use dynamic SQL (which is a separate topic, an advanced technique which is also a bad practice in most cases and has really nothing to do with your question about combinatorics) you will have to hard-code the number (and names) of columns in the query.
You could present the input data in rows rather than columns, and ask for the result in the format
row_num col_num val
------- ------- ---
1 1 1
1 2 -
1 3 -
(this mimics just your first output row) - and similar for all other rows - and then you would NOT have to hard-code the number of columns in the query; you could then easily adapt the code below to solve this more general problem.
I use SYS_CONNECT_BY_PATH below, which limits the number of columns (and the length of values in each column in the input); this can be avoided, but writing the query this way is fun.
I assumed the dash - in your desired output stands for NULL; if you actually want to show dashes, use NVL(..., -) in the final SELECT clause.
with
t (col1, col2, col3) as (
select 1 as col1, 2 as col2, 3 as col3 from dual
)
, prep (pth) as (
select sys_connect_by_path(val, '/') || '/'
from t
unpivot (val for col in (col1, col2, col3))
connect by nocycle prior col is not null
)
select to_number(substr(pth, instr(pth, '/', 1, 1) + 1,
instr(pth, '/', 1, 2) - instr(pth, '/', 1, 1) - 1)) col1,
to_number(substr(pth, instr(pth, '/', 1, 2) + 1,
instr(pth, '/', 1, 3) - instr(pth, '/', 1, 2) - 1)) col2,
to_number(substr(pth, instr(pth, '/', 1, 3) + 1,
instr(pth, '/', 1, 4) - instr(pth, '/', 1, 3) - 1)) col3
from prep
order by col1 nulls first, col2 nulls first, col3 nulls first
;
Output:
COL1 COL2 COL3
----- ----- -----
1
1 2
1 2 3
1 3
1 3 2
2
2 1
2 1 3
2 3
2 3 1
3
3 1
3 1 2
3 2
3 2 1

BigQuery: straight table format of matrix multiplication into more traditional Matrix multiplication format?

This question here shows how to get matrix multiplication into straight table format, for example given (6x1) (Path, value) matrix, you will get (36,1) straight table. Now I want to get the traditional matrix multiplication format, in the example it would be (6x6) matrix.
How to shape a straight table of matrix multiplication into more traditional matrix multiplication format?
--standardSQL
WITH MatrixA AS (
SELECT 1 AS p, 2 AS val UNION ALL
SELECT 2, -3 UNION ALL
SELECT 3, 4 UNION ALL
SELECT 4, -1 UNION ALL
SELECT 5, 0 UNION ALL
SELECT 6, 2
), MatrixB AS (
SELECT 1 AS p, -1 AS val UNION ALL
SELECT 2, 2 UNION ALL
SELECT 3, 3 UNION ALL
SELECT 4, 3 UNION ALL
SELECT 5, 0 UNION ALL
SELECT 6, 1
),
matrixMultiplication AS
(
SELECT a.p AS ap, b.p as bp, SUM(a.val * b.val) val
FROM MatrixA AS a
CROSS JOIN MatrixB AS b
GROUP BY a.p, b.p
ORDER BY a.p, b.p
)
--36 elements for the 6x6 PATHS Matrix
--TODO: how to shape it to 6x6 matrix?
SELECT * FROM matrixMultiplication
how to shape it to 6x6 matrix?
Below is for BigQuery Standard SQL. Few simple options
Option #1
#standardSQL
SELECT ap AS row, STRING_AGG(CAST(val AS STRING), ' ' ORDER BY bp) AS cols
FROM matrixMultiplication
GROUP BY row
-- ORDER BY row
when applied to dummy data from your question - result is
Row row cols
1 1 -2 4 6 6 0 2
2 2 3 -6 -9 -9 0 -3
3 3 -4 8 12 12 0 4
4 4 1 -2 -3 -3 0 -1
5 5 0 0 0 0 0 0
6 6 -2 4 6 6 0 2
Option #2
#standardSQL
SELECT row,
cols[OFFSET(0)] AS col1,
cols[OFFSET(1)] AS col2,
cols[OFFSET(2)] AS col3,
cols[OFFSET(3)] AS col4,
cols[OFFSET(4)] AS col5,
cols[OFFSET(5)] AS col6
FROM (
SELECT ap AS row, ARRAY_AGG(val ORDER BY bp) AS cols
FROM matrixMultiplication
GROUP BY ap
)
-- ORDER BY row
when applied to dummy data from your question - result is
Row row col1 col2 col3 col4 col5 col6
1 1 -2 4 6 6 0 2
2 2 3 -6 -9 -9 0 -3
3 3 -4 8 12 12 0 4
4 4 1 -2 -3 -3 0 -1
5 5 0 0 0 0 0 0
6 6 -2 4 6 6 0 2

Select onle group that have positivo and negative number in plsql

I have a table that have date grouped.
I need to select only groups that have positive and negative value inside.
For example:
id value1
2 7
2 8
2 -1
3 3
3 4
4 -1
4 -2
5 7
5 -5
the result should be
id value1
2 7
2 8
2 -1
5 7
5 -5
because the group with id 3 just have positive number and the group with id 4 just have negative number.
any idea how can I do it using case (when then) in a select or using if else inside a function. Or any other idea?
Try this.
select id,value1 FROM
(
select t.*,
count( DISTINCT SIGN (value1 ) ) OVER (PARTITION BY id ) n
from yourtable t
) WHERE n = 2
;
The Sign() function gives 1 for positive and -1 for negative numbers.
DEMO
If you group by the ID, you can use the aggregate functions MIN and MAX to find out if there are both positive and negative values. You need to decide how to treat 0 though... I have treated it as positive below :)
with your_table as(
-- Your example data here, this is not really part of the solution
select 2 as id, 7 as value1 from dual union all
select 2 as id, 8 as value1 from dual union all
select 2 as id, -1 as value1 from dual union all
select 3 as id, 3 as value1 from dual union all
select 3 as id, 4 as value1 from dual union all
select 4 as id, -1 as value1 from dual union all
select 4 as id, -2 as value1 from dual union all
select 5 as id, 7 as value1 from dual union all
select 5 as id, -5 as value1 from dual
)
select *
from your_table
where id in(select id
from your_table
group by id
having min(value1) < 0
and max(value1) >= 0);

Get Hierarchy level and all node references on Oracle

I have been reading about CONNECT BY and CTE in Oracle, but I can't come up with a solution. I don't know how to use properly CONNECT BY to my needs, and recursive CTE's in Oracle are limited to 2 branches(one UNION ALL) and I'm using 3 branches.
In SQL Server it was kind of easy after I found this article. I only added another UNION ALL regarding to return all node references.
What I trying to do is having a hierarchy like this:
Code|Father
1 |NULL
2 |1
3 |2
And this should return me:
Node|Father|Level|JumpsToFather
1 |1 |1 |0
2 |1 |2 |1
2 |2 |2 |0
3 |1 |3 |2
3 |2 |3 |1
3 |3 |3 |0
Note: Yes I need to return a reference to themselves counting as zero jumps on the hierarchy
Here is a solution using a recursive CTE. I used lvl as column header since level is a reserved word in Oracle. You will see other differences in terminology as well. I use "parent" for the immediately higher level and "ancestor" for >= 0 steps (to accommodate your requirement of showing a node as its own ancestor). I used an ORDER BY clause to cause the output to match yours; you may or may not need the rows ordered.
Your question stimulated me to read again, in more detail, about hierarchical queries, to see if this can be done with them instead of recursive CTEs. Actually I already know you can, by using CONNECT_BY_PATH, but using a substr on that just to retrieve the top level in a hierarchical path is not satisfying at all, there must be a better way. (If that was the only way to do it with hierarchical queries, I would definitely go the recursive CTE route if it was available). I will add the hierarchical query solution here, if I can find a good one.
with h ( node, parent ) as (
select 1 , null from dual union all
select 2 , 1 from dual union all
select 3 , 2 from dual
),
r ( node , ancestor, steps ) as (
select node , node , 0
from h
union all
select r.node, h.parent, steps + 1
from h join r
on h.node = r.ancestor
)
select node, ancestor,
1+ (max(steps) over (partition by node)) as lvl, steps
from r
where ancestor is not null
order by lvl, steps desc;
NODE ANCESTOR LVL STEPS
---------- ---------- ---------- ----------
1 1 1 0
2 1 2 1
2 2 2 0
3 1 3 2
3 2 3 1
3 3 3 0
Added: Hierarchical query solution
OK - found it. Please test both solutions to see which performs better; from tests on a different setup, recursive CTE was quite a bit faster than hierarchical query, but that may depend on the specific situation. ALSO: recursive CTE works only in Oracle 11.2 and above; the hierarchical solution works with older versions.
I added a bit more test data to match Anatoliy's.
with h ( node, parent ) as (
select 1 , null from dual union all
select 2 , 1 from dual union all
select 3 , 2 from dual union all
select 4 , 2 from dual union all
select 5 , 4 from dual
)
select node,
connect_by_root node as ancestor,
max(level) over (partition by node) as lvl,
level - 1 as steps
from h
connect by parent = prior node
order by node, ancestor;
NODE ANCESTOR LVL STEPS
---------- ---------- ---------- ----------
1 1 1 0
2 1 2 1
2 2 2 0
3 1 3 2
3 2 3 1
3 3 3 0
4 1 3 2
4 2 3 1
4 4 3 0
5 1 4 3
5 2 4 2
5 4 4 1
5 5 4 0
thx for question, i spent 1 hour to write this:
with t as ( select code, parent, level l
from (select 1 as code, NULL as parent from dual union
select 2 , 1 from dual union
select 3 , 2 from dual
-- add some more data for demo case
union
select 4 , 2 from dual union
select 5 , 4 from dual
)
start with parent is null
connect by prior code = parent )
select code, (select code
from t t1
where l = ll
and rownum = 1
start with t1.code = main_t.code
connect by prior t1.parent = t1.code
) parent,
l code_level,
jumps
from (
select distinct t.*, l-level jumps, level ll
from t
connect by level <= l
) main_t
order by code, parent
as you can see, i'am add some more data to test my sql, here is output
CODE PARENT CODE_LEVEL JUMPS
---------- ---------- ---------- ----------
1 1 1 0
2 1 2 1
2 2 2 0
3 1 3 2
3 2 3 1
3 3 3 0
4 1 3 2
4 2 3 1
4 4 3 0
5 1 4 3
5 2 4 2
5 4 4 1
5 5 4 0
13 rows selected

Want to generate o/p as Below in Oracle

I need an o/p as below.
1,1
2,1
2,2
3,1
3,2
3,3
4,1
4,2
4,3
4,4
... and so on.
I tried to write the query as below. But throwing error. SIngle row subquery returns more than one row.
with test1 as(
SELECT LEVEL n
FROM DUAL
CONNECT BY LEVEL <59)
select n,(
SELECT LEVEL n
FROM DUAL
CONNECT BY LEVEL <n) from test1
Appreciate your help in solving the same.
Here is one of the methods how you could get the desired result:
SQL> with t1(col) as(
2 select level
3 from dual
4 connect by level <= 5
5 )
6 select a.col
7 , b.col
8 from t1 a
9 join t1 b
10 on a.col >= b.col
11 ;
COL COL
---------- ----------
1 1
2 1
2 2
3 1
3 2
3 3
4 1
4 2
4 3
4 4
5 1
5 2
5 3
5 4
5 5
15 rows selected

Resources