Use SYS_CONNECT_BY_PATH to aggregate values - oracle

I would like to generate a data hierarchy.
This query:
select connect_by_root(parent_id) as root_id
,ID, NAME
,SYS_CONNECT_BY_PATH(PARENT_ID,'/') PATH
,level
,line
,LINE*power(10,-level+1) CALC
,ltrim(SYS_CONNECT_BY_PATH(lpad(LINE,3,'0'), '.'),'.') SORT
from (
select 3 ID, 1 LINE, 2 PARENT_ID FROM DUAL
union all
select 4 ID, 2 LINE, 2 PARENT_ID FROM DUAL
union all
select 5 ID, 3 LINE, 2 PARENT_ID FROM DUAL
union all
select 6 ID, 1 LINE, 5 PARENT_ID FROM DUAL
union all
select 7 ID, 1 LINE, 6 PARENT_ID FROM DUAL
) v
start with v.parent_id = 2
connect by nocycle prior id=parent_id
Generates:
ROOT_ID ID PATH LEVEL LINE CALC SORT
2 3 /2 1 1 1 001
2 4 /2 1 2 2 002
2 5 /2 1 3 3 003
2 6 /2/5 2 1 0.1 003.001
2 7 /2/5/6 3 1 0.01 003.001.001
What I would like:
ROOT_ID ID PATH LEVEL LINE CALC
2 3 /2 1 1 1
2 4 /2 1 2 2
2 5 /2 1 3 3
2 6 /2/5 2 1 3.1
2 7 /2/5/6 3 1 3.11
Is there a way to get sys_connect_by_path (or another function) to tally the CALC column and its parents?
Currently, I'm using the SORT field for ordering the rows; I'd rather sort on a proper numerical value (CALC field).

Try this:
select connect_by_root(parent_id) as root_id
,ID
,SYS_CONNECT_BY_PATH(PARENT_ID,'/') PATH
,level
,line
,LINE*power(10,-level+1) CALC
,XMLCAST(XMLQUERY(ltrim(SYS_CONNECT_BY_PATH(LINE*power(10,-level+1), '+'),'+') RETURNING CONTENT) AS NUMBER) SORT
from (
select 3 ID, 1 LINE, 2 PARENT_ID FROM DUAL
union all
select 4 ID, 2 LINE, 2 PARENT_ID FROM DUAL
union all
select 5 ID, 3 LINE, 2 PARENT_ID FROM DUAL
union all
select 6 ID, 1 LINE, 5 PARENT_ID FROM DUAL
union all
select 7 ID, 1 LINE, 6 PARENT_ID FROM DUAL
) v
start with v.parent_id = 2
connect by nocycle prior id=parent_id

You may take your SORT column and after some fidling (changing the first dot to comma and removing other dots) convert the result to a number.
The key part is here
to_number(regexp_replace(regexp_replace(SORT,'\.',',',1,1),'\.',null),
'99D9999' , ' NLS_NUMERIC_CHARACTERS = '',.'' ') sort2
Example
3.1.1 -> 3,1.1 -> 3,11 and convert to number
The complete query here
with v as (
select 3 ID, 1 LINE, 2 PARENT_ID FROM DUAL
union all
select 4 ID, 2 LINE, 2 PARENT_ID FROM DUAL
union all
select 5 ID, 3 LINE, 2 PARENT_ID FROM DUAL
union all
select 6 ID, 1 LINE, 5 PARENT_ID FROM DUAL
union all
select 7 ID, 1 LINE, 6 PARENT_ID FROM DUAL
), v2 as (
select connect_by_root(parent_id) as root_id
,ID
,SYS_CONNECT_BY_PATH(PARENT_ID,'/') PATH
,level my_level
,line
,LINE*power(10,-level+1) CALC
,ltrim(SYS_CONNECT_BY_PATH( LINE , '.'),'.') SORT
from v
start with v.parent_id = 2
connect by nocycle prior id=parent_id
)
select ROOT_ID, ID, PATH, my_level, LINE, CALC, SORT,
to_number(regexp_replace(regexp_replace(SORT,'\.',',',1,1),'\.',null),'99D9999' , ' NLS_NUMERIC_CHARACTERS = '',.'' ') sort2
from v2

Related

How to correlate data and counts from columns to rows in Oracle (19c)?

I believe there is probably an easy way to solve these problems with pivots or partitions but I can't seem to find the proper solutions. I have a round about solution for problem 1 by using a long list of select sum()s and a long solution for problem 2 where I just select the count(*) from table B where id = id from table A multiple times in a (select) blocks but if I have a large number of IDs both of those solution equal very long SQL that gets very tedious and I'm sure there is a better way it is just eluding me.
I would really like solutions that would allow me to include a large set of multiple IDs or supply the solution with a table of IDs to evaluate.
Problem 1:
Table:
------------------
ID DESC YEAR
1 A 2021
1 B 2021
1 C 2021
2 A 2021
2 B 2021
2 C 2021
3 A 2019
3 B 2019
I would like to have the count of the ID's for each DESC by year.
Expected Result:
------------------
Year CountA CountB CountC
2019 1 1 0
2021 2 2 2
Problem 2:
Table A:
------------------
ID DESC
1 A
2 B
3 C
Table B:
------------------
SET ID
10 1
10 1
12 1
13 2
14 3
I would like to see (1) how many of each ID from Table A can be found in each SET in Table B and (2) how many of each ID from Table A can be found in each SET in Table B and not in any other SET of Table B (unique matches).
Expected Result 1:
------------------
ID Count10 Count12 Count13 Count14
1 2 1 0 0
2 0 0 1 0
3 0 0 0 1
Expected Result 2:
------------------
ID UniqueCount10 UniqueCount12 UniqueCount13 UniqueCount14
1 0 0 0 0
2 0 0 1 0
3 0 0 0 1
Thank you for any and all assistance.
All three problems can be solved with pivoting (calling Problem 2 "two different problems"), although it is not clear what purpose Result 2 would serve (in the second problem; see my comments to you).
Note that desc and set are reserved keywords, and year is a keyword, so they shouldn't be used as column names. I changed to descr, set_ (with an underscore) and yr. Also, I do not use double-quotes for column names in the output; all-caps column names are just fine.
In the second problem it is not clear why you need Table A. Could you have some id values that don't appear at all in Table B, but you still want them in the final output? If so, you will need to change my semi-joins to outer joins; left as an exercise, since it's a different (and much more basic) type of question.
In the first problem, you must pivot the result of a subquery, which selects only the relevant columns from the base table. There is no such need for the second problem (unless your tables have other columns that should not be considered - left for you to figure out).
Problem 1
Data:
create table tbl (id, descr, yr) as
select 1, 'A', 2021 from dual union all
select 1, 'B', 2021 from dual union all
select 1, 'C', 2021 from dual union all
select 2, 'A', 2021 from dual union all
select 2, 'B', 2021 from dual union all
select 2, 'C', 2021 from dual union all
select 3, 'A', 2019 from dual union all
select 3, 'B', 2019 from dual
;
Query and output:
select *
from (select descr, yr from tbl)
pivot (count(*) for descr in ('A' as count_a, 'B' as count_b, 'C' as count_c))
order by yr
;
YR COUNT_A COUNT_B COUNT_C
---- ------- ------- -------
2019 1 1 0
2021 2 2 2
Problem 2
Data:
create table table_a (id, descr) as
select 1, 'A' from dual union all
select 2, 'B' from dual union all
select 3, 'C' from dual
;
create table table_b (set_, id) as
select 10, 1 from dual union all
select 10, 1 from dual union all
select 12, 1 from dual union all
select 13, 2 from dual union all
select 14, 3 from dual
;
Part 1 - Query and result:
select *
from table_b
pivot (count(*) for set_ in (10 as count_10, 12 as count_12,
13 as count_13, 14 as count_14))
where id in (select id from table_a) -- is this needed?
order by id -- if needed
;
ID COUNT_10 COUNT_12 COUNT_13 COUNT_14
-- -------- -------- -------- --------
1 2 1 0 0
2 0 0 1 0
3 0 0 0 1
Part 2 - Query and result:
select *
from (
select id, case count(distinct set_) when 1 then max(set_) end as set_
from table_b
where id in (select id from table_a) -- is this needed?
group by id
)
pivot (count(*) for set_ in (10 as unique_ct_10, 12 as unique_ct_12,
13 as unique_ct_13, 14 as unique_ct_14))
order by id -- if needed
;
ID UNIQUE_CT_10 UNIQUE_CT_12 UNIQUE_CT_13 UNIQUE_CT_14
-- ------------ ------------ ------------ ------------
1 0 0 0 0
2 0 0 1 0
3 0 0 0 1
In this last part of the second problem, you might as well just take the subquery and run it separately - what's the purpose of pivoting its output?

Find Maximal Value of other Rows per Group

I have a simple table with values (ID) in groups (GRP_ID).
create table tst as
select 1 grp_id, 1 id from dual union all
select 1 grp_id, 1 id from dual union all
select 1 grp_id, 2 id from dual union all
select 2 grp_id, 1 id from dual union all
select 2 grp_id, 2 id from dual union all
select 2 grp_id, 2 id from dual union all
select 3 grp_id, 3 id from dual;
It is straightforward to find a maximum value per group using analytical functions.
select grp_id, id,
max(id) over (partition by grp_id) max_grp
from tst
order by 1,2;
GRP_ID ID MAX_GRP
---------- ---------- ----------
1 1 2
1 1 2
1 2 2
2 1 2
2 2 2
2 2 2
3 3 3
But the goal is to find the maximum value excluding the value of the current row.
This is the expected result (column MAX_OTHER_ID):
GRP_ID ID MAX_GRP MAX_OTHER_ID
---------- ---------- ---------- ------------
1 1 2 2
1 1 2 2
1 2 2 1
2 1 2 2
2 2 2 2
2 2 2 2
3 3 3
Note that in the GRP_ID = 2 a tie on the MAX value exists, so the MAX_OTHER_ID remains the same.
I did manage this two step solution, but I'm wondering if there is a more straightforward and simple solution.
with max1 as (
select grp_id, id,
row_number() over (partition by grp_id order by id desc) rn
from tst
)
select GRP_ID, ID,
case when rn = 1 /* MAX row per group */ then
max(decode(rn,1,to_number(null),id)) over (partition by grp_id)
else
max(id) over (partition by grp_id)
end as max_other_id
from max1
order by 1,2
;
I wish the window functions supported multiple range specifications something like:
max(id) over (
partition by grp_id
order by id
range between unbounded preceding and 1 preceding
or range between 1 following and unbounded following
)
But unfortunately they don't.
As a workaround, you can avoid subqueries and CTEs using the function twice on the different ranges and call coalesce on that.
select grp_id,
id,
coalesce(
max(id) over (
partition by grp_id
order by id
range between 1 following and unbounded following
)
, max(id) over (
partition by grp_id
order by id
range between unbounded preceding and 1 preceding
)
) max_grp
from tst
order by 1,
2
Coalesce works out of the box because of the ordering clause as the result of the window function call will be either the max in the given window or a null value.
Demo - http://rextester.com/SDXVF13962
SELECT GRP_ID,ID, (SELECT Max(ID) FROM TEST A WHERE A.ROWID<>B.ROWID AND A.GRP_ID=B.GRP_ID) maX_ID FROM TEST B;
Got the expected result with Co-Related Query ! Hope this helps .

How to create id with AUTO_INCREMENT on a view in Oracle

can any one help to create a AUTO_INCREMENT column on a view in oracle 11g.
Thanks
While it's not possible to return a single unique identity column for a view whose underlying data does not have any single unique identifier, it is possible to return composite values that uniquely identify the data. For example given a table of CSV Data with a unique ID on each row:
create table sample (id number primary key, csv varchar2(4000));
where the CSV column contains a string of comma separated values:
insert into sample
select 1, 'a' from dual union all
select 2, 'b,c' from dual union all
select 3, 'd,"e",f' from dual union all
select 4, ',h,' from dual union all
select 5, 'j,"",l' from dual union all
select 6, 'm,,o' from dual;
The following query will unpivot the csv data and the composite values (ID, SEQ) will uniquely identify each VALue, The ID column idetifies the record the data came from, and SEQ uniquely identifies the position in the CSV:
WITH pvt(id, seq, csv, val, nxt) as (
SELECT id -- Parse out individual list items
, 1 -- separated by commas and
, csv -- optionally enclosed by quotes
, REGEXP_SUBSTR(csv,'(["]?)([^,]*)\1',1,1,null,2)
, REGEXP_INSTR(csv, ',', 1, 1)
FROM sample
UNION ALL
SELECT id
, seq+1
, csv
, REGEXP_SUBSTR(csv,'(["]?)([^,]*)\1',nxt+1,1,null,2)
, REGEXP_INSTR(csv, ',', nxt+1, 1)
FROM pvt
where nxt > 0
)
select * from pvt order by id, seq;
ID SEQ CSV VAL NXT
---------- ---------- ---------- ---------- ----------
1 1 a a 0
2 1 b,c b 2
2 2 b,c c 0
3 1 d,"e",f d 2
3 2 d,"e",f e 6
3 3 d,"e",f f 0
4 1 ,h, [NULL] 1
4 2 ,h, h 3
4 3 ,h, [NULL] 0
5 1 j,"",l j 2
5 2 j,"",l [NULL] 5
5 3 j,"",l l 0
6 1 m,,o m 2
6 2 m,,o [NULL] 3
6 3 m,,o o 0
15 rows selected.

scalar subquery has an aggregate operation

My oracle version is 10.2.
It's very strange when a scalar subquery has an aggregate operation.
my table named t_test looked like this;
t_id t_name
1 1
2 1
3 2
4 2
5 3
6 3
query string looked like this;
select t1.t_id,
(select count(t_name)
from (select t2.t_name
from t_test t2
where t2.t_id=t1.t_id
group by t2.t_name)) a
from t_test t1
this query's result is,
t_id a
1 3
2 3
3 3
4 3
5 3
6 3
which is very weird,
take t1.t_id=1 for example,
select count(t_name)
from (select t2.t_name
from t_test t2
where t2.t_id=1
group by t2.t_name)
the result is 1,
somehow,the 'where' operator doesn't work,the result is exactly the same as I put my query like this:
select t1.t_id,
(select count(t_name)
from (select t2.t_name
from t_test t2
group by t2.t_name)) a
from t_test t1
why?
Can you post a cut-and-paste from SQL*Plus showing exactly what query you're running? The query you posted does not appear to be valid-- the alias t1 is not going to be valid in the subquery where you're referencing it. That makes me suspect that you're simplifying the problem to post here but you've accidentally left something important out.
SQL> ed
Wrote file afiedt.buf
1 with x as (
2 select 1 id, 1 name from dual union all
3 select 2,1 from dual union all
4 select 3,2 from dual union all
5 select 4,2 from dual union all
6 select 5,3 from dual union all
7 select 6,3 from dual
8 )
9 select t1.id
10 ,(select count(b.name)
11 from (select t2.name
12 from x t2
13 where t2.id = t1.id
14 group by t2.name) b) a
15* from x t1
SQL> /
where t2.id = t1.id
*
ERROR at line 13:
ORA-00904: "T1"."ID": invalid identifier
Presumably, it would be much more natural to write the query like this (assuming you really want to use a scalar subquery) where t1 is going to be a valid alias in the scalar subquery.
SQL> ed
Wrote file afiedt.buf
1 with x as (
2 select 1 id, 1 name from dual union all
3 select 2,1 from dual union all
4 select 3,2 from dual union all
5 select 4,2 from dual union all
6 select 5,3 from dual union all
7 select 6,3 from dual
8 )
9 select t1.id
10 ,(select count(t2.name)
11 from x t2
12 where t2.id = t1.id) cnt
13* from x t1
SQL> /
ID CNT
---------- ----------
1 1
2 1
3 1
4 1
5 1
6 1
6 rows selected.

plsql: Getting another field values along with the aggregation values in a grouping statement

I am working on a time attendance system. I have the employees' transactions stored in the following table:
I want to get the earliest and the latest transactions for each employee including their date and type.
I am able to get the dates using grouping and aggregation. However, I am not able to figure out how to get types with them.
Would you please help me in it.
Thank you.
That's what the FIRST and LAST aggregate functions are designed for.
Here is a link to the documentation:
FIRST: http://download.oracle.com/docs/cd/E11882_01/server.112/e17118/functions065.htm#SQLRF00641
LAST: http://download.oracle.com/docs/cd/E11882_01/server.112/e17118/functions083.htm#sthref1206
And here is an example:
SQL> create table my_transactions (id,employee_id,action_date,type)
2 as
3 select 1, 1, sysdate, 'A' from dual union all
4 select 2, 1, sysdate-1, 'B' from dual union all
5 select 3, 1, sysdate-2, 'C' from dual union all
6 select 4, 1, sysdate-3, 'D' from dual union all
7 select 5, 2, sysdate-11, 'E' from dual union all
8 select 6, 2, sysdate-12, 'F' from dual union all
9 select 7, 2, sysdate-13, 'G' from dual
10 /
Table created.
SQL> select *
2 from my_transactions
3 order by id
4 /
ID EMPLOYEE_ID ACTION_DATE T
---------- ----------- ------------------- -
1 1 04-07-2011 10:15:07 A
2 1 03-07-2011 10:15:07 B
3 1 02-07-2011 10:15:07 C
4 1 01-07-2011 10:15:07 D
5 2 23-06-2011 10:15:07 E
6 2 22-06-2011 10:15:07 F
7 2 21-06-2011 10:15:07 G
7 rows selected.
SQL> select employee_id
2 , min(action_date) min_date
3 , max(type) keep (dense_rank first order by action_date) min_date_type
4 , max(action_date) max_date
5 , max(type) keep (dense_rank last order by action_date) max_date_type
6 from my_transactions
7 group by employee_id
8 /
EMPLOYEE_ID MIN_DATE M MAX_DATE M
----------- ------------------- - ------------------- -
1 01-07-2011 10:15:07 D 04-07-2011 10:15:07 A
2 21-06-2011 10:15:07 G 23-06-2011 10:15:07 E
2 rows selected.
Regards,
Rob.
You could try to use analytical(or windowing functions)
select *
from
(select id, employee_id, action_date,type,
max(action_date) over (partition by employee_id) max_action_date,
min(action_date) over (partition by employee_id) min_action_date
from transaction)
where action_date in (max_action_date, min_action_date)

Resources