Combination of all values (Oracle) - 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

Related

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

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

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

Oracle Connect By seems to produce too many rows

Oracle Database 12c Enterprise Edition Release 12.1.0.2.0
I expect I'm just missing something, but if I run this query without the "connect by", I get 2 rows. When I add "connect by level <= 4", I would expect to get each of those 2 rows 4 times. The actual result is different.
Can anyone help me understand what's happening here? I'm not looking for a solution that only repeats each row 4 times - I've already got that. I'm just looking to understand what's happening and why.
with alpha as (
select 1 as id
from dual
),
beta as (
select 1 as alpha_id,
1 as beta_no
from dual
union all
select 1 as alpha_id,
2 as beta_no
from dual
)
select a.id,
b.beta_no,
level as the_level
from alpha a
inner join beta b
on b.alpha_id = a.id
connect by level <= 4
order by a.id,
b.beta_no,
level
;
ID BETA_NO THE_LEVEL
1 1 1
1 1 2
1 1 2
1 1 3
1 1 3
1 1 3
1 1 3
1 1 4
1 1 4
1 1 4
1 1 4
1 1 4
1 1 4
1 1 4
1 1 4
1 2 1
1 2 2
1 2 2
1 2 3
1 2 3
1 2 3
1 2 3
1 2 4
1 2 4
1 2 4
1 2 4
1 2 4
1 2 4
1 2 4
1 2 4
30 rows selected
Many thanks to mathguy. The second link he provided in the answer below had exactly what I was looking for. Specifically:
1 with t as (select 1 as id from dual union all
2 select 2 from dual)
3 --
4 select id, level
5 ,prior id
6 ,sys_connect_by_path(id,'=>') as cpath
7 from t
8* connect by level <= 3
SQL> /
ID LEVEL PRIORID CPATH
---------- ---------- ---------- --------------------------------------------------
1 1 =>1
1 2 1 =>1=>1
1 3 1 =>1=>1=>1
2 3 1 =>1=>1=>2
2 2 1 =>1=>2
1 3 2 =>1=>2=>1
2 3 2 =>1=>2=>2
2 1 =>2
1 2 2 =>2=>1
1 3 1 =>2=>1=>1
2 3 1 =>2=>1=>2
2 2 2 =>2=>2
1 3 2 =>2=>2=>1
2 3 2 =>2=>2=>2
14 rows selected.
It's clear to me from that example, but I'd be hard-pressed to succinctly put it into words.
With no condition other than "level <= 4", every row from the original table, view etc. (from the join, in this case) will produce two rows at level 2, then four more rows at level 3, and 8 more at level 4. "Connect by" is essentially a succession of joins, and you are doing cross joins if you have no condition with the PRIOR operator.
You probably want to add "and prior a.id = a.id". This will lead to Oracle complaining about cycles (because Oracle decides a cycle is reached when it sees the same values in the columns subject to PRIOR). That, in turn, is solved by adding a third condition, usually "and prior sys_guid() is not null".
(Edited; the original answer made reference to NOCYCLE, which is not needed when using the "prior sys_guid() is not null" approach.)
This has been discussed recently on OTN: https://community.oracle.com/thread/3999985
Same question discussed here: https://community.oracle.com/thread/2526535
To illustrate Mathguy's answer, you are missing some predicates out of your CONNECT BY clause:
with alpha as (
select 1 as id
from dual
),
beta as (
select 1 as alpha_id,
1 as beta_no
from dual
union all
select 1 as alpha_id,
2 as beta_no
from dual
)
select a.id,
b.beta_no,
level as the_level
from alpha a
inner join beta b
on b.alpha_id = a.id
connect by level <= 4
AND PRIOR a.id = a.id
AND PRIOR b.beta_no = b.beta_no
AND PRIOR sys_guid() IS NOT NULL
order by a.id,
b.beta_no,
LEVEL;
ID BETA_NO THE_LEVEL
---------- ---------- ----------
1 1 1
1 1 2
1 1 3
1 1 4
1 2 1
1 2 2
1 2 3
1 2 4
An alternative would be to use the recursive with clause:
with alpha as (
select 1 as id
from dual
),
beta as (
select 1 as alpha_id,
1 as beta_no
from dual
union all
select 1 as alpha_id,
2 as beta_no
from dual
),
multiply (id, beta_no, rn) AS (SELECT a.id,
b.beta_no,
1 rn
FROM alpha a
INNER JOIN beta b
ON a.id = b.alpha_id
UNION ALL
SELECT ID,
beta_no,
rn + 1
FROM multiply
WHERE rn + 1 <= 4)
SELECT ID,
beta_no,
rn AS the_level
FROM multiply
order by id,
beta_no,
rn;
ID BETA_NO THE_LEVEL
---------- ---------- ----------
1 1 1
1 1 2
1 1 3
1 1 4
1 2 1
1 2 2
1 2 3
1 2 4

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

Resources