Table "links" contains 2 columns : PARENT_CODE, CHILD_CODE
PARENT_CODE | CHILD_CODE
------------|------------
A1 | B1
A1 | B2
B1 | C1
B1 | C2
B2 | C3
B3 | C3
C3 | D1
This table eventually used to form following graph :
My question will be how to write optimized hierarchy query in Oracle to get the full graph connected, and the input parameter will be any of node.
To illustrate the input/output, this is simplified SQL :
SELECT PARENT_CODE,CHILD_CODE FROM {HIERARCHY QUERY} where NODE = {ANY NODE}
The query below with show you all links. The idea is that first I create two ways connection between nodes. Using it we can do recursive query that returns our results. The last thing is decode that returns who is real parent and who is child basing on dir (0 - means original direction, 1 - means reverted direction).
with linkT (parent_code, child_code) as
(select 'A1','B1' from dual union all
select 'A1','B2' from dual union all
select 'B1','C1' from dual union all
select 'B1','C2' from dual union all
select 'B2','C3' from dual union all
select 'B3','C3' from dual union all
select 'C3','D1' from dual union all
select 'E1','F1' from dual ),
T1 as (
select parent_code, child_code, 0 Dir from linkT
union all
select child_code, parent_code, 1 Dir from linkT )
select distinct
decode(dir, 0, parent_code, child_code) as Parent_code,
decode(dir, 0, child_code, parent_code) as Child_Code
from (
select parent_code, child_code, dir
from T1 x
start with x.child_code='A1'
connect by nocycle prior x.parent_code=x.child_code )
order by 1,2
Related
I am creating a simple pivot but I get the ORA-00917: missing comma error on the line where the pivot function exists (third line). Please help.
Not sure what else to try as the syntax seems to be correct as I am new to the pivot function.
SELECT venue, notional
FROM ABC
pivot (sum(notional) FOR (venue) IN ('A' as A1 , 'B' as B1));
The expected result would be A1 and B1 showing their respective notionals.
You do not need a sub-query. The columns venue and notional in the ABC table have been pivoted and no longer exist in the output result set so the SELECT clause will not find those columns.
Instead SELECT the a1 and b1 columns you have pivoted:
Test Data:
CREATE TABLE abc ( id, venue, notional ) AS
SELECT 1, 'A', 1 FROM DUAL UNION ALL
SELECT 1, 'A', 2 FROM DUAL UNION ALL
SELECT 1, 'A', 3 FROM DUAL UNION ALL
SELECT 1, 'B', 1 FROM DUAL UNION ALL
SELECT 1, 'B', 2 FROM DUAL UNION ALL
SELECT 2, 'B', 3 FROM DUAL;
Query:
SELECT id, a1, b1 -- you can't use venue or notional here as they've been pivoted.
FROM ABC
pivot (sum(notional) FOR (venue) IN ('A' as A1 , 'B' as B1));
Output:
ID | A1 | B1
-: | ---: | -:
1 | 6 | 3
2 | null | 3
db<>fiddle here
It seems you miss the sub-query before PIVOT function -
SELECT *
FROM (SELECT venue, notional
FROM ABC
)
PIVOT (
SUM(notional) FOR (venue) IN ('A' as A1 , 'B' as B1)
);
I'm working on a query which use connect by prior.
I have written a query which retrieves all children of an entity. What I want is to retrieve both children and parents rows.
Here is my SQL :
Select *
From myTable tab
Connect By Prior tab.id= tab.child_id
Start With tab.id= 2;
How could I retrieve parents ?
Thanks.
Use UNION ALL to combine a query to get the children with another to get the ancestors.
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE myTable ( id, child_id ) AS
SELECT 0, 1 FROM DUAL UNION ALL
SELECT 1, 2 FROM DUAL UNION ALL
SELECT 2, 3 FROM DUAL UNION ALL
SELECT 3, 4 FROM DUAL UNION ALL
SELECT 4, 5 FROM DUAL UNION ALL
SELECT 3, 6 FROM DUAL UNION ALL
SELECT 0, 7 FROM DUAL UNION ALL
SELECT 1, 8 FROM DUAL;
Query 1:
SELECT * -- Child Rows
FROM mytable
START WITH id = 2
CONNECT BY PRIOR child_id = id
UNION ALL
SELECT * -- Ancestor Rows
FROM mytable
START WITH child_id = 2
CONNECT BY PRIOR id = child_id
Results:
| ID | CHILD_ID |
|----|----------|
| 2 | 3 |
| 3 | 4 |
| 4 | 5 |
| 3 | 6 |
| 1 | 2 |
| 0 | 1 |
An alternative approach to a hierarchical query, if you're on 11gR2 or higher, is recursive subquery factoring:
with rcte (id, child_id, some_other_col) as (
select id, child_id, some_other_col -- whichever columns you're interested in
from myTable
where id = 2
union all
select t.id, t.child_id, t.some_other_col -- whichever columns you're interested in
from rcte r
join myTable t
on t.id = r.child_id -- match parents
or t.child_id = r.id -- match children
)
cycle id set is_cycle to 1 default 0
select id, child_id, some_other_col -- whichever columns you're interested in
from rcte
where is_cycle = 0;
The anchor member finds your initial target row. The recursive member then looks for parents or children of each row found so far.
The final query can get whichever columns you want, do aggregation, etc.
(Possibly worth testing both approaches with real data to see if there is a performance difference, of course).
How can I do that in Oracle 11g?
The problem here is that the query on DUAL does not recognize APP table.
SELECT APP_VER || SUBVERSION.LEVEL
FROM APP,
(SELECT LEVEL
FROM DUAL
CONNECT BY LEVEL < APP.NUM_SUBVERSIONS) SUBVERSION
Assuming that you need one row for each possibile subvesion, you can avoid the problem with some nesting, like the following:
select APP_VER , NUM_SUBVERSIONS, subversions.num
from app app
inner join
( select level num
from dual
connect by level <= ( select max(num_subversions) from app)
) subversions
on (subversions.num <= num_subversions)
This requires more scanning of your tables, so it may not be so efficient, but, from the names of your tables and columns, I assume you have not so many records to handle.
If I understood your problem correctly, you can use Recursive With subquerying to do it:
with a1 (app_ver, num_subversions, lvl) as (
select app_ver, num_subversions, 1
from app
union all
select app.app_ver, app.num_subversions, a1.lvl + 1
from app, a1
where app.app_ver = a1.app_ver
and a1.lvl < app.num_subversions)
search depth first by app_ver, num_subversions set my_order
select app_ver || lvl
from a1;
Sample execution:
SQL> create table app as
2 with app (app_ver, num_subversions) as (
3 select 'A', 2 from dual union all
4 select 'B', 3 from dual union all
5 select 'C', 4 from dual
6 )
7 select app_ver, num_subversions
8 from app a;
Table created
SQL> with a1 (app_ver, num_subversions, lvl) as (
2 select app_ver, num_subversions, 1
3 from app
4 union all
5 select app.app_ver, app.num_subversions, a1.lvl + 1
6 from app, a1
7 where app.app_ver = a1.app_ver
8 and a1.lvl < app.num_subversions)
9 search depth first by app_ver, num_subversions set my_order
10 select app_ver || lvl
11 from a1;
APP_VER||LVL
-----------------------------------------
A1
A2
B1
B2
B3
C1
C2
C3
C4
9 rows selected
I have a table "person", an associative table "person_vaccination" and a table "vaccination".
I want to get the person who has missing vaccinations but so far I only got it to work when I have the id.
SELECT vac.VACCINATION_Name
FROM VACCINATION vac
WHERE vac.VACCINATION_NUMBER NOT IN
(SELECT v.VACCINATION_NUMBER
FROM PERSON per
Join PERSON_VACCINATION pv ON per.PERSON_NUMBER = pv.PERSON_NUMBER
JOIN VACCINATION v ON pv.VACCINATION_NUMBER = v.VACCINATION_NUMBER
WHERE per.PERSON_NUMBER = 6)
It works fine but how do I get all the people missing their vaccinations? (ex:
555 , Vacccination 1
555 , Vacccination 2
666 , Vacccination 1)
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE VACCINATION ( VACCINATION_NUMBER, VACCINATION_NAME ) AS
SELECT 1, 'Vac 1' FROM DUAL
UNION ALL SELECT 2, 'Vac 2' FROM DUAL
UNION ALL SELECT 3, 'Vac 3' FROM DUAL
UNION ALL SELECT 4, 'Vac 4' FROM DUAL;
CREATE TABLE PERSON_VACCINATION ( VACCINATION_NUMBER, PERSON_NUMBER ) AS
SELECT 1, 1 FROM DUAL
UNION ALL SELECT 2, 1 FROM DUAL
UNION ALL SELECT 3, 1 FROM DUAL
UNION ALL SELECT 4, 1 FROM DUAL
UNION ALL SELECT 1, 2 FROM DUAL
UNION ALL SELECT 2, 2 FROM DUAL
UNION ALL SELECT 3, 2 FROM DUAL;
CREATE TABLE PERSON ( PERSON_NUMBER, PERSON_NAME ) AS
SELECT 1, 'P1' FROM DUAL
UNION ALL SELECT 2, 'P2' FROM DUAL
UNION ALL SELECT 3, 'P3' FROM DUAL;
Query 1:
SELECT p.PERSON_NAME,
v.VACCINATION_NAME
FROM VACCINATION v
CROSS JOIN
PERSON p
WHERE NOT EXISTS ( SELECT 1
FROM PERSON_VACCINATION pv
WHERE pv.VACCINATION_NUMBER = v.VACCINATION_NUMBER
AND pv.PERSON_NUMBER = p.PERSON_NUMBER )
ORDER BY p.PERSON_NAME,
p.PERSON_NUMBER,
v.VACCINATION_NAME,
v.VACCINATION_NUMBER
Results:
| PERSON_NAME | VACCINATION_NAME |
|-------------|------------------|
| P2 | Vac 4 |
| P3 | Vac 1 |
| P3 | Vac 2 |
| P3 | Vac 3 |
| P3 | Vac 4 |
Instead of an INNER JOIN, you should use LEFT JOIN.
Take a look at this link: http://www.w3schools.com/sql/sql_join_left.asp
If you are after people with no vaccinations at all, then you can use a LEFT OUTER JOIN between PERSON and PERSON_VACCINATION, then find all entries where a PERSON_VACCINATION column is NULL.
SELECT PERSON_NUMBER
FROM PERSON P
LEFT OUTER JOIN
PERSON_VACCINATION PV
ON P.PERSON_NUMBER = PV.PERSON_NUMBER
WHERE PV.PERSON_NUMBER IS NULL
If you are unfamiliar with LEFT OUTER JOIN, it tries to find matching rows in PERSON_VACCINATION for each row in PERSON. If there are no matching rows, it leaves the PERSON row in the result set, and shows NULL values for all columns in the PERSON_VACCINATION table.
If you are looking for a list of people and the vaccinations they do not have then #MT0's answer is correct. You need to create a result set containing all possible combinations of PERSON and VACCINATION (a Cross Join), then check which of those combinations actually exist in PERSON_VACCINATION. Any entry that does not exist is are your missing vaccinations.
Firstly, I'm not used to SQL syntax at all.
I face the following issue. T1 and C2 are my two tables. I'd look to do this:
UPDATE T1 t
SET t.adr = C2.adr
WHERE t.cli != C2.cli;
I have the following errors : C2.adr and C2.cli : invalid identifier
Am I supposed to do this :
UPDATE T1 t, C2 c
SET t.adr = c.adr
WHERE t.cli != c.cli;
It's just a bit weird, because I don't update C2, but maybe, it's just the syntax, where the tables are referenced after the action (here update).
This will do
UPDATE T1 t, C2 c
SET t.adr = c.adr
WHERE t.cli != c.cli;
It would be helpful if you expanded on what you were trying to achieve as the query you've posted is rather abstract and there looks like there ought to be another column which can be used to connect rows in T1 and C1.
However, from what you have asked, you can try something like this:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE T1 ( cli, adr ) AS
SELECT 1, 1 FROM DUAL
UNION ALL SELECT 2, 2 FROM DUAL
UNION ALL SELECT 3, 3 FROM DUAL
UNION ALL SELECT 4, 4 FROM DUAL;
CREATE TABLE C1 ( cli, adr ) AS
SELECT 1, 5 FROM DUAL
UNION ALL SELECT 2, 6 FROM DUAL
UNION ALL SELECT 3, 7 FROM DUAL
UNION ALL SELECT 4, 8 FROM DUAL;
Query 1:
UPDATE T1 t
SET adr = ( SELECT MIN( c.adr )
FROM C1 c
WHERE c.cli <> t.cli )
Query 2:
SELECT * FROM T1
Results:
| CLI | ADR |
|-----|-----|
| 1 | 6 |
| 2 | 5 |
| 3 | 5 |
| 4 | 5 |
Its not quite like you asked for as I've had to put MIN( c.adr ) in to ensure the sub-query returns a single row but it gives you an example of how to reference a second table in an update query.
If you don't aggregate using MIN() then (for the test data I've created) the not equals condition will match multiple rows and you get an error:
Query 3:
UPDATE T1 t
SET adr = ( SELECT c.adr
FROM C1 c
WHERE c.cli <> t.cli )
Results:
ORA-01427: single-row subquery returns more than one row : UPDATE T1 t SET adr = ( SELECT c.adr FROM C1 c WHERE c.cli <> t.cli )