Hierarchical Query Joined with another table - oracle

I would like to join result of hierarchical query to set of other tables. I dont want to hardcode start with clause in the hierarchical query and would like the hierarchical query to depend on the join table result to do that.
Below is the sample dataset
create table person( id number, name varchar2(20));
insert into person(id, name) values (1,'Liam');
insert into person(id, name) values (2,'Noah');
insert into person(id, name) values (3,'Oliver');
insert into person(id, name) values (4,'Elijah');
insert into person(id, name) values (5,'William');
insert into person(id, name) values (6,'James');
insert into person(id, name) values (7,'Benjamin');
insert into person(id, name) values (8,'Lucas');
insert into person(id, name) values (9,'Henry');
insert into person(id, name) values (10,'Alexande');
insert into person(id, name) values (11,'Mason');
insert into person(id, name) values (12,'Michael');
insert into person(id, name) values (13,'Ethan');
insert into person(id, name) values (14,'Daniel');
insert into person(id, name) values (15,'Jacob');
insert into person(id, name) values (16,'Logan');
insert into person(id, name) values (17,'Jackson');
insert into person(id, name) values (18,'Levi');
insert into person(id, name) values (19,'Sebastia');
insert into person(id, name) values (20,'Mateo');
create table friends( person number, friend number);
insert into friends (person, friend) values (1,2);
insert into friends (person, friend) values (2,3);
insert into friends (person, friend) values (3,4);
insert into friends (person, friend) values (4,5);
insert into friends (person, friend) values (2,6);
insert into friends (person, friend) values (2,7);
insert into friends (person, friend) values (3,8);
insert into friends (person, friend) values (8,9);
insert into friends (person, friend) values (4,10);
insert into friends (person, friend) values (5,11);
insert into friends (person, friend) values (5,12);
insert into friends (person, friend) values (5,13);
insert into friends (person, friend) values (6,14);
insert into friends (person, friend) values (6,15);
insert into friends (person, friend) values (7,16);
insert into friends (person, friend) values (7,17);
insert into friends (person, friend) values (7,18);
insert into friends (person, friend) values (17,19);
insert into friends (person, friend) values (18,20);
create table party(organizerid number, partydate date )
insert into party(organizerid, partydate ) values (1, '01-Jan-2022');
insert into party(organizerid, partydate ) values (2, '01-Feb-2022');
insert into party(organizerid, partydate ) values (3, '01-Mar-2022');
insert into party(organizerid, partydate ) values (5, '01-Apr-2022');
insert into party(organizerid, partydate ) values (6, '01-May-2022');
insert into party(organizerid, partydate ) values (7, '01-Jun-2022');
I want to get all the friends and their friends for a person with a specific party date. Only caveat is, I cant give party date inside the hierarchical query as my requirement to keep it outside.
for example, I want to get the all the friends for a person with partydate 01-Mar-2022
I want to get the result like below. except, I dont want to hardcode value 3 in start with clause and I cant add party table as join inside hierarchal query as I have many other tables to join for my requirement.
select connect_by_root(f.person) root, sys_connect_by_path(f.person, '/') path, p.id, (p.name || ' is friend of ' || pf.name) friendlink, f.person, f.friend from friends f
join person p on f.person = p.id
join person pf on f.friend = pf.id
start with f.person = 3 /*hardcoded*/
connect by nocycle prior f.friend = f.person;
instead I want to achieve something like below, but not sure what to fill <------->
select hq.* from party pty
join (select connect_by_root(f.person) root, sys_connect_by_path(f.person, '/') path, p.id, (p.name || ' is friend of ' || pf.name) friendlink, f.person, f.friend from friends f
join person p on f.person = p.id
join person pf on f.friend = pf.id
start with f.person = <------->
connect by nocycle prior f.friend = f.person) hq on <---------->
where pty.partydate = '01-Mar-2022';
ROOT
PATH
ID
FRIENDLINK
PERSON
FRIEND
3
/3
3
Oliver is friend of Elijah
3
4
3
/3/4
4
Elijah is friend of William
4
5
3
/3/4/5
5
William is friend of Mason
5
11
3
/3/4/5
5
William is friend of Michae
5
12
3
/3/4/5
5
William is friend of Ethan
5
13
3
/3/4
4
Elijah is friend of Alexande
4
10
3
/3
3
Oliver is friend of Lucas
3
8
3
/3/8
8
Lucas is friend of Henry
8
9
Please guide me in the right direction. Thank you.

You may use lateral join to pass a column to the correlated subquery, which allows to emulate its execution for each input row:
select pty.partydate, hq.*
from party pty
left join lateral (
select
connect_by_root(f.person) as root,
sys_connect_by_path(f.person, '/') as path,
p.id,
(p.name || ' is friend of ' || pf.name) as friendlink,
f.person,
f.friend
from friends f
join person p
on f.person = p.id
join person pf
on f.friend = pf.id
start with f.person = pty.organizerid
connect by nocycle prior f.friend = f.person
) hq
on pty.organizerid = hq.root
-- where pty.partydate = date '2022-03-01'
order by partydate, root, path
PARTYDATE | ROOT | PATH | ID | FRIENDLINK | PERSON | FRIEND
:--------- | ---: | :--------- | -: | :---------------------------- | -----: | -----:
2022-01-01 | 1 | /1 | 1 | Liam is friend of Noah | 1 | 2
2022-01-01 | 1 | /1/2 | 2 | Noah is friend of James | 2 | 6
2022-01-01 | 1 | /1/2 | 2 | Noah is friend of Oliver | 2 | 3
2022-01-01 | 1 | /1/2 | 2 | Noah is friend of Benjamin | 2 | 7
2022-01-01 | 1 | /1/2/3 | 3 | Oliver is friend of Lucas | 3 | 8
2022-01-01 | 1 | /1/2/3 | 3 | Oliver is friend of Elijah | 3 | 4
2022-01-01 | 1 | /1/2/3/4 | 4 | Elijah is friend of William | 4 | 5
2022-01-01 | 1 | /1/2/3/4 | 4 | Elijah is friend of Alexande | 4 | 10
2022-01-01 | 1 | /1/2/3/4/5 | 5 | William is friend of Michael | 5 | 12
2022-01-01 | 1 | /1/2/3/4/5 | 5 | William is friend of Mason | 5 | 11
2022-01-01 | 1 | /1/2/3/4/5 | 5 | William is friend of Ethan | 5 | 13
2022-01-01 | 1 | /1/2/3/8 | 8 | Lucas is friend of Henry | 8 | 9
2022-01-01 | 1 | /1/2/6 | 6 | James is friend of Daniel | 6 | 14
2022-01-01 | 1 | /1/2/6 | 6 | James is friend of Jacob | 6 | 15
2022-01-01 | 1 | /1/2/7 | 7 | Benjamin is friend of Logan | 7 | 16
2022-01-01 | 1 | /1/2/7 | 7 | Benjamin is friend of Levi | 7 | 18
2022-01-01 | 1 | /1/2/7 | 7 | Benjamin is friend of Jackson | 7 | 17
2022-01-01 | 1 | /1/2/7/17 | 17 | Jackson is friend of Sebastia | 17 | 19
2022-01-01 | 1 | /1/2/7/18 | 18 | Levi is friend of Mateo | 18 | 20
2022-02-01 | 2 | /2 | 2 | Noah is friend of Oliver | 2 | 3
2022-02-01 | 2 | /2 | 2 | Noah is friend of James | 2 | 6
2022-02-01 | 2 | /2 | 2 | Noah is friend of Benjamin | 2 | 7
2022-02-01 | 2 | /2/3 | 3 | Oliver is friend of Elijah | 3 | 4
2022-02-01 | 2 | /2/3 | 3 | Oliver is friend of Lucas | 3 | 8
2022-02-01 | 2 | /2/3/4 | 4 | Elijah is friend of Alexande | 4 | 10
2022-02-01 | 2 | /2/3/4 | 4 | Elijah is friend of William | 4 | 5
2022-02-01 | 2 | /2/3/4/5 | 5 | William is friend of Ethan | 5 | 13
2022-02-01 | 2 | /2/3/4/5 | 5 | William is friend of Michael | 5 | 12
2022-02-01 | 2 | /2/3/4/5 | 5 | William is friend of Mason | 5 | 11
2022-02-01 | 2 | /2/3/8 | 8 | Lucas is friend of Henry | 8 | 9
2022-02-01 | 2 | /2/6 | 6 | James is friend of Jacob | 6 | 15
2022-02-01 | 2 | /2/6 | 6 | James is friend of Daniel | 6 | 14
2022-02-01 | 2 | /2/7 | 7 | Benjamin is friend of Jackson | 7 | 17
2022-02-01 | 2 | /2/7 | 7 | Benjamin is friend of Logan | 7 | 16
2022-02-01 | 2 | /2/7 | 7 | Benjamin is friend of Levi | 7 | 18
2022-02-01 | 2 | /2/7/17 | 17 | Jackson is friend of Sebastia | 17 | 19
2022-02-01 | 2 | /2/7/18 | 18 | Levi is friend of Mateo | 18 | 20
2022-03-01 | 3 | /3 | 3 | Oliver is friend of Elijah | 3 | 4
2022-03-01 | 3 | /3 | 3 | Oliver is friend of Lucas | 3 | 8
2022-03-01 | 3 | /3/4 | 4 | Elijah is friend of Alexande | 4 | 10
2022-03-01 | 3 | /3/4 | 4 | Elijah is friend of William | 4 | 5
2022-03-01 | 3 | /3/4/5 | 5 | William is friend of Ethan | 5 | 13
2022-03-01 | 3 | /3/4/5 | 5 | William is friend of Michael | 5 | 12
2022-03-01 | 3 | /3/4/5 | 5 | William is friend of Mason | 5 | 11
2022-03-01 | 3 | /3/8 | 8 | Lucas is friend of Henry | 8 | 9
2022-04-01 | 5 | /5 | 5 | William is friend of Mason | 5 | 11
2022-04-01 | 5 | /5 | 5 | William is friend of Michael | 5 | 12
2022-04-01 | 5 | /5 | 5 | William is friend of Ethan | 5 | 13
2022-05-01 | 6 | /6 | 6 | James is friend of Daniel | 6 | 14
2022-05-01 | 6 | /6 | 6 | James is friend of Jacob | 6 | 15
2022-06-01 | 7 | /7 | 7 | Benjamin is friend of Jackson | 7 | 17
2022-06-01 | 7 | /7 | 7 | Benjamin is friend of Logan | 7 | 16
2022-06-01 | 7 | /7 | 7 | Benjamin is friend of Levi | 7 | 18
2022-06-01 | 7 | /7/17 | 17 | Jackson is friend of Sebastia | 17 | 19
2022-06-01 | 7 | /7/18 | 18 | Levi is friend of Mateo | 18 | 20
db<>fiddle here

I want to get the all the friends for a person with partydate 01-Mar-2022
You can use your query and replace the hardcoded START WITH with an EXISTS condition:
select connect_by_root(f.person) AS root,
sys_connect_by_path(f.person, '/') AS path,
p.id,
p.name || ' is friend of ' || pf.name AS friendlink,
f.person,
f.friend
from friends f
join person p on f.person = p.id
join person pf on f.friend = pf.id
start with
EXISTS(
SELECT 1
FROM party p
WHERE partydate = DATE '2022-03-01'
AND f.person = p.organizerid
)
connect by nocycle prior f.friend = f.person;
Which, for your sample data, outputs:
ROOT
PATH
ID
FRIENDLINK
PERSON
FRIEND
3
/3
3
Oliver is friend of Elijah
3
4
3
/3/4
4
Elijah is friend of William
4
5
3
/3/4/5
5
William is friend of Mason
5
11
3
/3/4/5
5
William is friend of Michael
5
12
3
/3/4/5
5
William is friend of Ethan
5
13
3
/3/4
4
Elijah is friend of Alexande
4
10
3
/3
3
Oliver is friend of Lucas
3
8
3
/3/8
8
Lucas is friend of Henry
8
9
db<>fiddle here

Related

How to find and group records which have same conditions subsequently?

We have a table which we record our students' attendances in. We need to find out which students missed n number of contacts in a row.
Here is the attendance table structure with example records
----------------------------------------------------
| id | student_id | class_id | checkedin_time |
--------------------------------------------------
| 1 | 1 | 1 | null |
| 2 | 1 | 2 | 2019-07-09 10:30 |
| 3 | 1 | 3 | null |
| 4 | 1 | 4 | null |
| 5 | 1 | 5 | 2019-07-12 12:00 |
----------------------------------------------------
What I'm looking for is a code that show me records with id 3 and 4 (two subsequent missed contacts) grouped by student_id
This was my starting point:
$attendances = \App\Attendance::where('checked_time' , null)->get();
$attendances->groupBy('student_id')

Oracle select from values and substract from last value

Need help on Oracle SELECT statement.
I Have table like this with used days. User can have 11 days
+----+----------+-----+-----------+
| ID | NAME | USED| DATE |
+----+----------+-----+-----------+
| 1 | John | 1 |01/01/2018 |
| 2 | John | 2 |01/03/2018 |
| 3 | John | 2 |01/05/2018 |
+----+----------+-----+-----------+
So on QUERY SELECT i want to have result of left days like this
+----+----------+------+-----------+
| ID | NAME | DAYS | USED| LEFT|
+----+----------+------+-----------+
| 1 | John | 11 | 1 | 10 |
| 2 | John | 10 | 2 | 8 |
| 3 | John | 8 | 2 | 6 |
+----+----------+------+-----------+
Any help how to achieve this result ?
The main thing you need is the analytic version of sum. Also, try not to use Oracle keywords (like date) as column names.
-- sample data
with ex as (select 1 as id, 'John' as name, 1 as used, to_date('01/01/2018','mm/dd/yyyy') as date1 from dual
union select 2 as id, 'John' as name, 2 as used, to_date('01/03/2018','mm/dd/yyyy') from dual
union select 3 as id, 'John' as name, 2 as used, to_date('01/05/2018','mm/dd/yyyy') from dual)
-- main query
select id,
name,
11-sum(used) over (partition by name order by date1) + used as days,
used,
11-sum(used) over (partition by name order by date1) as left
from ex;
Output:
ID NAME DAYS USED LEFT
---------- ---- ---------- ---------- ----------
1 John 11 1 10
2 John 10 2 8
3 John 8 2 6

Sort values in an associative array pl/sql

If ID is even I must sort the values that correspond to that ID DESC , if the ID is odd I must sort the values ASC. This is the table called Grades.
ID|COL1|COL2|COL3|COL4|COL5|COL6|COL7|
1 | 6 | 3 | 8 | 4 | 7 | 8 | 4 |
2 | 5 | 7 | 9 | 2 | 1 | 7 | 8 |
3 | 2 | 7 | 4 | 8 | 1 | 5 | 9 |
4 | 8 | 4 | 7 | 9 | 4 | 1 | 4 |
5 | 7 | 5 | 2 | 5 | 2 | 6 | 4 |
The result must be this:
ID|COL1|COL2|COL3|COL4|COL5|COL6|COL7|
1 | 3 | 4 | 4 | 6 | 7 | 8 | 8 |
2 | 9 | 8 | 7 | 7 | 5 | 2 | 1 |
3 | 1 | 2 | 4 | 5 | 7 | 8 | 9 |
4 | 9 | 8 | 7 | 4 | 4 | 4 | 1 |
5 | 2 | 2 | 4 | 5 | 5 | 6 | 7 |
As you can see ID=1->odd number so the values must be sorted ASC
This is the code so far:
declare
type grades_array is table of grades%rowtype index by pls_integer;
grades_a grades_array;
cnt number;
begin
Select count(id) into cnt from grades;
For i in 1..cnt loop
--I used an associative array
Select * into grades_a(i) from grades where grades.id=i;
end loop;
For i in grades_a.FIRST..grades_a.LAST loop
if (mod(grades_a(i).id,2)=1)then .......
--I don't know how to sort the specific rows, in this case ASC
--dbms_output.put_line(grades_a(i).col1);
end if;
end loop;
--Also it is specified in the exercise that the table can change, e.g add more columns
end;
I would simply use PIVOT/UNPIVOT for this.
First UNPIVOT the table and assign a rank to each column value in ascending/descending order.
SQL Fiddle
Query 1:
SELECT id,
colval,
ROW_NUMBER () OVER (
PARTITION BY id
ORDER BY CASE MOD (id, 2) WHEN 1 THEN colval END,
CASE MOD (id, 2) WHEN 0 THEN colval END DESC) r
FROM x UNPIVOT (colval FOR colname
IN (col1 AS 'col1', col2 AS 'col2', col3 AS 'col3', col4 AS 'col4',
col5 AS 'col5', col6 AS 'col6', col7 AS 'col7')
)
Results:
| ID | COLVAL | R |
|----|--------|---|
| 1 | 3 | 1 |
| 1 | 4 | 2 |
| 1 | 4 | 3 |
| 1 | 6 | 4 |
| 1 | 7 | 5 |
| 1 | 8 | 6 |
| 1 | 8 | 7 |
| 2 | 9 | 1 |
| 2 | 8 | 2 |
| 2 | 7 | 3 |
| 2 | 7 | 4 |
| 2 | 5 | 5 |
| 2 | 2 | 6 |
| 2 | 1 | 7 |
| 3 | 1 | 1 |
| 3 | 2 | 2 |
| 3 | 4 | 3 |
| 3 | 5 | 4 |
| 3 | 7 | 5 |
| 3 | 8 | 6 |
| 3 | 9 | 7 |
| 4 | 9 | 1 |
| 4 | 8 | 2 |
| 4 | 7 | 3 |
| 4 | 4 | 4 |
| 4 | 4 | 5 |
| 4 | 4 | 6 |
| 4 | 1 | 7 |
| 5 | 2 | 1 |
| 5 | 2 | 2 |
| 5 | 4 | 3 |
| 5 | 5 | 4 |
| 5 | 5 | 5 |
| 5 | 6 | 6 |
| 5 | 7 | 7 |
Then PIVOT the result based on the rank.
Query 2:
WITH pivoted AS (
SELECT id,
colval,
ROW_NUMBER () OVER (
PARTITION BY id
ORDER BY CASE MOD (id, 2) WHEN 1 THEN colval END,
CASE MOD (id, 2) WHEN 0 THEN colval END DESC) r
FROM x UNPIVOT (colval FOR colname
IN (col1 AS 'col1', col2 AS 'col2', col3 AS 'col3', col4 AS 'col4',
col5 AS 'col5', col6 AS 'col6', col7 AS 'col7')
)
)
SELECT * FROM pivoted
PIVOT (MAX (colval)
FOR r
IN (1 AS col1, 2 AS col2, 3 AS col3, 4 AS col4,
5 AS col5, 6 AS col6, 7 AS col7))
Results:
| ID | COL1 | COL2 | COL3 | COL4 | COL5 | COL6 | COL7 |
|----|------|------|------|------|------|------|------|
| 1 | 3 | 4 | 4 | 6 | 7 | 8 | 8 |
| 2 | 9 | 8 | 7 | 7 | 5 | 2 | 1 |
| 3 | 1 | 2 | 4 | 5 | 7 | 8 | 9 |
| 4 | 9 | 8 | 7 | 4 | 4 | 4 | 1 |
| 5 | 2 | 2 | 4 | 5 | 5 | 6 | 7 |

max() issue in Oracle SQL [duplicate]

This question already has answers here:
Fetch the rows which have the Max value for a column for each distinct value of another column
(35 answers)
Closed 8 years ago.
I'm using Oracle SQL and i need some help with max() function.
I have the following table:
ID | Type | Price | Quantity
1 | A | 10 | 2
2 | B | 5 | 5
3 | C | 10 | 3
4 | A | 8 | 7
5 | A | 6 | 9
6 | A | 7 | 5
7 | B | 15 | 3
8 | A | 20 | 4
9 | A | 3 | 7
10 | B | 11 | 8
I need to aggregate the table by Type column. For each group of Type (A, B, C), i need to select the price and the quantity of max(id).
I this case:
ID | Type | Price | Quantity
9 | A | 3 | 7
10 | B | 11 | 8
3 | C | 10 | 3
Any Suggestion?
max won't help you with this. You can use the row_number partitioning function.
select id, type, price, quantity
from
(
select yourtable.*,
row_number() over (partition by type order by id desc) rn
from yourtable
) v
where rn = 1
Something like this:
Select t.* From
(Select Max(ID) As ID From Table
Group By Type) tmp
Join Table t On t.ID = tmp.ID

MySQL equivalent of ORACLES rank()

Oracle has 2 functions - rank() and dense_rank() - which i've found very useful for some applications. I am doing something in mysql now and was wondering if they have something equivalent to those?
Nothing directly equivalent, but you can fake it with some (not terribly efficient) self-joins. Some sample code from a collection of MySQL query howtos:
SELECT v1.name, v1.votes, COUNT(v2.votes) AS Rank
FROM votes v1
JOIN votes v2 ON v1.votes < v2.votes OR (v1.votes=v2.votes and v1.name = v2.name)
GROUP BY v1.name, v1.votes
ORDER BY v1.votes DESC, v1.name DESC;
+-------+-------+------+
| name | votes | Rank |
+-------+-------+------+
| Green | 50 | 1 |
| Black | 40 | 2 |
| White | 20 | 3 |
| Brown | 20 | 3 |
| Jones | 15 | 5 |
| Smith | 10 | 6 |
+-------+-------+------+
how about this "dense_rank implement" in MySQL
CREATE TABLE `person` (
`id` int(11) DEFAULT NULL,
`first_name` varchar(20) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
`gender` char(1) DEFAULT NULL);
INSERT INTO `person` VALUES
(1,'Bob',25,'M'),
(2,'Jane',20,'F'),
(3,'Jack',30,'M'),
(4,'Bill',32,'M'),
(5,'Nick',22,'M'),
(6,'Kathy',18,'F'),
(7,'Steve',36,'M'),
(8,'Anne',25,'F'),
(9,'Mike',25,'M');
the data before dense_rank() like this
mysql> select * from person;
+------+------------+------+--------+
| id | first_name | age | gender |
+------+------------+------+--------+
| 1 | Bob | 25 | M |
| 2 | Jane | 20 | F |
| 3 | Jack | 30 | M |
| 4 | Bill | 32 | M |
| 5 | Nick | 22 | M |
| 6 | Kathy | 18 | F |
| 7 | Steve | 36 | M |
| 8 | Anne | 25 | F |
| 9 | Mike | 25 | M |
+------+------------+------+--------+
9 rows in set (0.00 sec)
the data after dense_rank() like this,including "partition by" function
+------------+--------+------+------+
| first_name | gender | age | rank |
+------------+--------+------+------+
| Anne | F | 25 | 1 |
| Jane | F | 20 | 2 |
| Kathy | F | 18 | 3 |
| Steve | M | 36 | 1 |
| Bill | M | 32 | 2 |
| Jack | M | 30 | 3 |
| Mike | M | 25 | 4 |
| Bob | M | 25 | 4 |
| Nick | M | 22 | 6 |
+------------+--------+------+------+
9 rows in set (0.00 sec)
the query statement is
select first_name,t1.gender,age,FIND_IN_SET(age,t1.age_set) as rank from person t2,
(select gender,group_concat(age order by age desc) as age_set from person group by gender) t1
where t1.gender=t2.gender
order by t1.gender,rank

Resources