Given a simple (id, description) table t1, such as
id description
-- -----------
1 Alice
2 Bob
3 Carol
4 David
5 Erica
6 Fred
And a parent-child relationship table t2, such as
parent child
------ -----
1 2
1 3
4 5
5 6
Oracle offers a way of traversing this as a tree with some custom syntax extensions:
select parent, child, sys_connect_by_path(child, '/') as "path"
from t2
connect by prior parent = child
The exact syntax is not important, and I've probably made a mistake in the above. The
important thing is that the above will produce something that looks like
parent child path
------ ----- ----
1 2 /1/2
1 3 /1/3
4 5 /4/5
4 6 /4/5/6
5 6 /5/6
My question is this: is it possible to join another table within the sys_connect_by_path(), such as the t1 table above, to produce something like:
parent child path
------ ----- ----
1 2 /Alice/Bob
1 3 /Alice/Carol
... and so on...
In your query, replace T2 with a subquery that joins T1 and T2, and returns parent, child and child description. Then in the sys_connect_by_path function, reference the child description from your subquery.
Based on Mike McAllister's idea, the following uses a derived table to achieve the desired result:
select
T.PARENT
,T.CHILD
,sys_connect_by_path(T.CDESC, '/')
from
(
select
t2.parent as PARENT
,t2.child as CHILD
,t1.description as CDESC
from
t1, t2
where
t2.child = t1.id
) T
where
level > 1 and connect_by_isleaf = 1
connect by prior
T.CHILD = T.PARENT
In my problem, all the parents are anchored under a "super-parent" root, which means that the paths can be fully described with SYS_CONNECT_BY_PATH, thereby obviating the need for cagcowboy's technique of concatenating the parent with the path.
SELECT parent, child, parents.description||sys_connect_by_path(childs.description, '/') AS "path"
FROM T1 parents, T1 childs, T2
WHERE T2.parent = parents.id
AND T2.child = childs.id
CONNECT BY PRIOR parent = child
Related
so, recently i attended an interview where they asked the question like this,
there are 2 tables with parents and id in 1 table and relations between the id's on the 2nd table and we need to output based on the relations table the name of child father and mother .
input table parents:
id name
1 yogi
2 sidda
4 arpitha
5 sushma
6 navya
7 divya
8 sanju
9 ashwin
10 chetu
11 seena
12 vindi
input table relations:
child_id parent_id
4 1
4 2
5 10
5 6
7 8
7 9
11 12
11 13
The SQL is
select child,p2.name1, from parents p2 join
( select p1.name1 as child,r1.p_id,r1.c_id,p1.id1 from parents p1 join (select * from parents join relations on p_id=id1) r1 on p1.id1=r1.c_id) r2 on p2.id1=p_id;
which is giving me outlook like:
child parents
arpita yogi
arpita sidda
sushma chetu
sushma navya
divya sanju
divya ashwin
seena vindi
seena varshini
My expected output is:
child father mother
arpitha yogi sidda
sushma chetu navya
divya sanju ashwin
seena vindi varshini
this looks a lot like normalization question.
Not a great interview question as there's no way to solidly differentiate between mother and father - it's setting up for failure. It seems the first name is the father consistently so an approach using row number in a CTE would work
WITH c AS (
SELECT
T1.name
,T2.p_id
,T3.name as p_name
,row_number() OVER(PARTITION BY T2.c_id ORDER BY T2.p_id) AS row_no
FROM
names T1
INNER JOIN link T2
ON T1.id = T2.c_id
LEFT OUTER JOIN names AS T3
ON T2.p_id = T3.id
)
SELECT
T1.name AS c_name
,IS_NULL(T2.p_name,'none') AS f_name
,IS_NULL(T3.p_name,'none') AS m_name
FROM
c AS T1
LEFT OUTER JOIN c AS T2
ON T1.name = T2.name AND T2.row_no = 1
LEFT OUTER JOIN c AS T3
ON T1.name = T3.name AND T3.row_no = 2
I have two tables, structures of the same is as below:
Table 1. Transactional data table
trx id.
1
2
3
4
5..etc
Table 2
table 2 has the parent child relationship as below .
id subject_id (Child) object_id (Parent)
1 2 1
2 3 1
3 4 1
4 5 1
Now using above tables, the expected output is as follow:
1
2
3
4
5
Please let me know how can I achieve the same. I need to get the details from the Table 1 along with parent and its all children in the hierarchy. I just have one level of hierarchy.
As you just have a one level hierarchy you can get it to work just ordering the results correctly. Try this one:
select obj.object_id, t.*
from
(
select
object_id,
object_id as parent_id
from table2
union
select
subject_id as object_id,
object_id as parent_id
from table2
) obj
inner join table1 t
on t.id = obj.object_id
order by
obj.parent_id,
case when obj.object_id = obj.parent_id then 0 else 1 end,
obj.object_id
;
I light variation on the comment of the #a_horse...
select * from table1
where id in
(select object_id from table2
union all
select subject_id from table2)
order by id;
as expected
1
2
3
4
5
If you want to constraint the parent, simple add WHERE predicates in the subquery.
I have a parent/child relation with the following child table:
CHILD_ID PARENT_ID CHILD_VALUE
------------------------------
1 1 x
2 1 y
3 2 y
Now I want to select all existing distinct parents where the CHILD_VALUE is:
child has value = x (there will be only 1 or none)
if no child with value x exists - NULL
In other words show all parents with "matching" child or NULL if no child "matching" the value exists.
So the result shall look like this:
PARENT_ID CHILD_ID CHILD_VALUE
------------------------------
1 1 x
2 NULL NULL
Question would be how I can narrow down the join in this case.
This does the trick:
select distinct a.parent_id, b.child_id, b.child_value
from test1 a
left outer join test1 b
on a.parent_id = b.parent_id
and b.child_value = 'x'
See also this SQL fiddle.
I have a self reference table to store hierarchical values in order to show up them in a TreeView or so on ,according to James Crowley article (Tree structures in ASP.NET and SQL Server)
Our table would look something like this:
Id ParentId Name Depth Lineage
1 NULL Root Node 0 /1/
2 1 Child A 1 /1/2/
3 1 Child B 1 /1/3/
4 1 Child C 1 /1/4/
5 2 Child D 2 /1/2/5/
To get path of a node (for example id=5) he suggest following query against table
SELECT *
FROM dfTree
WHERE (SELECT lineage
FROM dfTree
WHERE id = 5) LIKE lineage + '%'
result would be :
Id ParentId Name Depth Lineage
1 NULL Root Node 0 /1/
2 1 Child A 1 /1/2/
5 2 Child D 2 /1/2/5/
And Is Acceptable
But how about to have a result set when there are multiple IDs which i want to have their path ? therefore for example in above example instead of Id=5 i would like to pass multiple values something like this :
SELECT *
FROM dfTree
WHERE (SELECT lineage
FROM dfTree
WHERE id IN (5,6,8,9)) LIKE lineage + '%'
But the above statement make no sense and it is invalid sql server expression
How could i solve this problem ?
Thanks in advance
This query...
SELECT DISTINCT T2.*
FROM
(
SELECT lineage
FROM dfTree
WHERE id IN (4, 5)
) T1
JOIN dfTree T2
ON
T1.Lineage LIKE T2.Lineage + '%'
...returns the following result on your test data:
Id ParentId Name Depth Lineage
1 NULL Root Node 0 /1/
2 1 Child A 1 /1/2/
4 1 Child C 1 /1/4/
5 2 Child D 2 /1/2/5/
As you can see, all paths are "merged" together - for example, the path component Id=1 belongs to both the path: /1/4/ and the path: /1/2/5/, yet exists in the result set only once.
One the other hand, if you do need to distinguish between different paths, you'd need to do something like this:
SELECT T2.*, T1.Id LeafId
FROM
(
SELECT id, lineage
FROM dfTree
WHERE id IN (4, 5)
) T1
JOIN dfTree T2
ON
T1.Lineage LIKE T2.Lineage + '%'
Result:
Id ParentId Name Depth Lineage LeafId
1 NULL Root Node 0 /1/ 4
4 1 Child C 1 /1/4/ 4
1 NULL Root Node 0 /1/ 5
2 1 Child A 1 /1/2/ 5
5 2 Child D 2 /1/2/5/ 5
In this case, each path is identified by its leaf. This assumes there are no diamond-shaped dependencies (i.e. this is a real tree and not just any DAG); if there are, then you'd need to use T1.Lineage instead of T1.Id to identify the path.
If you are running SQL Server 2005 or higher you could move the subquery to a CTE:
; with cte as (
SELECT lineage
FROM dfTree
WHERE id IN (5,6,8,9)
)
SELECT d.*
FROM dfTree d
inner join cte on cte.lineage like d.lineage + '%'
Or just restructure the subquery:
SELECT d.*
FROM dfTree d
inner join (
SELECT lineage
FROM dfTree
WHERE id IN (5,6,8,9)
) s on s.lineage like d.lineage + '%'
Let's say I have two tables, "Parent" and "Child". Parent-to-Child is a many:many relationship, implemented through a standard cross-referencing table.
I want to find all records of Parent that are referenced by ALL members of a given set of Child using SQL (in particular MS SQL Server's T-SQL; 2005 syntax is acceptable).
For example let's say I have:
List item
Parent Alice
Parent Bob
Child Charlie references Alice, Bob
Child David references Alice
Child Eve references Bob
My goals are:
If I have Children Charlie, I want the result set to include Alice and Bob
If I have Children Charlie and David, I want the result set to include Alice and NOT Bob.
If I have Children Charlie, David, and Eve, I want the result set to include nobody.
Relying on a numerical trick (where the number of parent-child links = the number of children, that parent is linked to all children):
SELECT Parent.ParentID, COUNT(*)
FROM Parent
INNER JOIN ChildParent
ON ChildParent.ParentID = Parent.ParentID
INNER JOIN Child
ON ChildParent.ChildID = Child.ChildID
WHERE <ChildFilterCriteria>
GROUP BY Parent.ParentID
HAVING COUNT(*) = (
SELECT COUNT(Child.ChildID)
FROM Child WHERE <ChildFilterCriteria>
)
Here's an answer.
SQL query: Simulating an "AND" over several rows instead of sub-querying
And here's a specific application of that to this problem.
SELECT * FROM Parents
WHERE ParentId in
(
SELECT ParentId FROM ChildParent
WHERE ChildId in
(
SELECT ChildId FROM Child
WHERE ChildName in ('Charlie', 'David')
)
GROUP BY ParentId
HAVING COUNT(*) = 2
)
( I guess where you said "Child Eve references Eve" you meant "Child Eve references Bob", right?)
I think I've got it... looks ugly... the secret is the double negation... that is, everyone for which it's true,, is the same as not anyone for which is false... (ok, I have troubles with my english, but I guess you understand what I mean)
select * from parent
parent_id name
--------------------------------------- --------------------------------------------------
1 alice
2 bob
select * from child
child_id name
--------------------------------------- --------------------------------------------------
1 charlie
2 david
3 eve
select * from parent_child
parent_id child_id
--------------------------------------- ---------------------------------------
1 1
2 1
1 2
2 3
select * from parent p
where not exists(
select * from child c
where
c.child_id in ( 1, 2, 3 ) and
not exists(
select * from parent_child pc where
pc.child_id = c.child_id and
pc.parent_id = p.parent_id
)
)
--when child list = ( 1 )
parent_id name
--------------------------------------- --------------------------------------------------
1 alice
2 bob
--when child list = ( 1, 2 )
parent_id name
--------------------------------------- --------------------------------------------------
1 alice
--when child list = ( 1, 2, 3 )
parent_id name
--------------------------------------- --------------------------------------------------
well, I hope it helps...