sql function for checking if two persons have common ancestors - sql

I have to solve the following problem: create a sql function that check if two persons have common ancestors, but I'm stuck.
I create
create type person as object
(
first_name varchar2(10),
last_namevarchar2(10)
);
and Persons table
create table client_iulia
(Person_Id varchar2(13)constraint pk_id_client primary key,
Mother_Id varchar2(13),
Father_Id varchar2(13),
Name person);
What I am trying to do is a function with three parameters (two people and search level) to return 1 if there are common ancestors and 0 otherwise)
Please, if anyone has any idea, help me.
Sorry for my poor english.

Recursive CTE is the way to go. I think it is the easiest to understand. There may be faster ways, but this should be clear how it works. I don't have access to an oracle server so I might have typos, I show two steps below so you can test and understand how it works.
1) Find all ancestors for a single input
with ancestors as
(
SELECT *
FROM client_iulia
WHERE Person_Id = #inputPerson
UNION ALL
SELECT *
FROM client_iulia
JOIN ancestors a on Person_ID = a.Mother_ID OR Person_ID = a.Father_ID
)
SELECT *
FROM ancestors
2) Find all ancestors for two targets
with ancestorsA as
(
SELECT *
FROM client_iulia
WHERE Person_Id = #inputPersonA
UNION ALL
SELECT *
FROM client_iulia
JOIN ancestorsA a on Person_ID = a.Mother_ID OR Person_ID = a.Father_ID
), ancestorsB as
(
SELECT *
FROM client_iulia
WHERE Person_Id = #inputPersonB
UNION ALL
SELECT *
FROM client_iulia
JOIN ancestorsB a on Person_ID = a.Mother_ID OR Person_ID = a.Father_ID
)
SELECT A.*
FROM ancestorsA A
JOIN ancestorsB B ON A.Person_Id = B.Person_Id

Wow, that's tricky. You need recursive queries to get all ancestors of either persons. As there are two parents, you will get pairs of mother and father, however, which you need to split into single ancestors. Then look if the sets have at least one ancestor in common. This statement gives you 1 when there is at least one common ancestor, else 0:
with ancestors_of_person_1 as
(
select mother_id, father_id
from client_iulia
start with person_id = 1
connect by person_id = prior mother_id or person_id = prior father_id
)
, ancestors_of_person_2 as
(
select mother_id, father_id
from client_iulia
start with person_id = 2
connect by person_id = prior mother_id or person_id = prior father_id
)
select count(*)
from
(
(
select mother_id as ancestor from ancestors_of_person_1
union
select father_id as ancestor from ancestors_of_person_1
)
intersect
(
select mother_id as ancestor from ancestors_of_person_2
union
select father_id as ancestor from ancestors_of_person_2
)
)
where rownum = 1;
EDIT: Here is an sqlfiddle. Person 1 and 3 have ancestor 122 in common, whereas person 1 and 2 have none in common. Try it: sqlfiddle

Related

How to perform depth first search in postgreSQL

I have simple structure:
CREATE TABLE room_chart(
room_id INT,
parent_id INT,
);
I can perform breadth-first search with this code
WITH RECURSIVE r AS (
SELECT room_id , parent_id FROM room_chart WHERE parent_id is null
UNION
SELECT room_chart.room_id, room_chart.parent_id FROM room_chart, r WHERE room_chart.parent_id = r.room_id
)
SELECT * from room_list where room_id in (SELECT room_id from r);
But how can I perform depth-first search to find all the routes in this tree for example? thanks in advance!

Oracle CONNECT BY recursive child to parent query, include ultimate parent that self references

In the following example
id parent_id
A A
B A
C B
select id, parent_id
from table
start with id = 'A'
connect by nocycle parent_id = prior id
I get
A A
B A
C B
In my database I have millions of rows in the table and deep and wide hierarchies and I'm not interested in all children. I can derive the children I'm interested in. So I want to turn the query on its head and supply START WITH with the children ids. I then want to output the parent recursively until I get to the top. In my case the top is where the id and parent_id are equal. This is what I'm trying, but I can't get it to show the top level parent.
select id, parent_id
from table
START WITH id = 'C'
CONNECT BY nocycle id = PRIOR parent_id
This gives me
C B
B A
It's not outputting the A A. Is it possible to do this? What I'm hoping to do is not show the parent_id as a separate column in the output, but just show the name relating to the id. The hierarchy is then implied by the order.
I got that result by using WITH clause.
WITH REC_TABLE ( ID, PARENT_ID)
AS
(
--Start WITH
SELECT ID, PARENT_ID
FROM table
WHERE ID='C'
UNION ALL
--Recursive Block
SELECT T.ID, T.PARENT_ID
FROM table T
JOIN REC_TABLE R
ON R.PARENT_ID=T.ID
AND R.PARENT_ID!=R.ID --NoCycle rule
)
SELECT *
FROM REC_TABLE;
And it seems to work that way too.
select id, parent_id
from T
START WITH id = 'C'
CONNECT BY id = PRIOR parent_id and parent_id!= prior id;
-- ^^^^^^^^^^^^^^^^^^^^
-- break cycles
Hope it helps.

Linking together different columns in a SQL table?

I'm not very experienced with advance SQL queries, I'm familiar with basic statements and basic joins, currently trying to figure out how to write a query that seems to be out of my depth and I haven't been able to find a solution from google so far and I'm hoping somebody might be able to point me in the right direction.
The table I'm working with has an ID column, and a 'parent id' column.
I'm looking for all descendants of ID '1' - rows with a parent ID of '1', rows with a parent ID equal to any row's ID with a parent ID of '1' etc. Currently I've been doing this manually but there are hundreds of descendants so far and I feel like there's a way to put this into one query.
Any help would be appreciated, if this is unclear I can also try to clarify.
EDIT - I got it working with the following query:
with cteMappings as (
select map_id, parent_map_id, map_name
from admin_map
where map_id = '1'
union all
select a.map_id, a.parent_map_id, a.map_name
from admin_map a
inner join cteMappings m
on a.parent_map_id = m.map_id
)
select map_id, parent_map_id, map_name
from cteMappings
Sounds like it can be achieved by Common Table Expression:
DECLARE #temp TABLE (id INT IDENTITY(1, 1), parent_id INT);
INSERT #temp
SELECT NULL
UNION ALL
SELECT 1
UNION ALL
SELECT 2
UNION ALL
SELECT NULL
SELECT * FROM #temp
; WITH HierarchyTemp (id, parent_id, depth) AS (
SELECT id, parent_id, 0
FROM #temp
WHERE id = 1
UNION ALL
SELECT t.id, t.parent_id, ht.depth + 1
FROM #temp t
INNER JOIN HierarchyTemp ht ON ht.id = t.parent_id
)
SELECT *
FROM HierarchyTemp
So the above example is creating a table variable with 4 rows:
id parent_id
1 NULL
2 1
3 2
4 NULL
Result of descendent of id '1' (also including itself, but can be excluded with an additional WHERE clause):
id parent_id depth
1 NULL 0
2 1 1
3 2 2

Is there a better way to get the last level of groups from a table?

I have a Group table and within that table, there is a ParentId column that denotes a groups parent within the Group table. The purpose is to build a dynamic menu from these groups. I know I can loop and grab the last child and construct a result set, but I'm curious if there's a more SQL-y way of accomplishing this.
The table has Id, ParentId and Title fields of int, int and varchar.
Basically, a hierarchy may be constructed this way (People is the base group):
People -> Male -> Boy
-> Man
-> Female
I want to grab the last child(ren) of each branch. So, {Boy, Man, Female} in this case.
As I mentioned, getting that info isn't a problem. I'm just looking for a better way of getting it without having to write a bunch of unions and loops where I can basically change the base group and traverse the entire hierarchy outward, dynamically. I'm not really a Db guy, so I don't know if there's a slick way of doing this or not.
To get the leaf levels for one of many hierarchies, you can use a Recursive Common Table Expressions (CTEs) to enumerate the hierarchy, and then check which members are not the parent of another group to filter to the leaves:
Declare #RootID int = 1
;with cte as (
select
Id,
ParentId,
Title
From
Groups
Where
Id = #RootID
Union All
Select
g.Id,
g.ParentId,
g.Title
From
cte c
Inner Join
Groups g
On c.Id = g.ParentID
)
Select
*
From
cte g
Where
Not Exists (
Select
'x'
From
Groups g2
Where
g2.ParentID = g.Id
);
You can also do this with a left join rather than a not exists
http://sqlfiddle.com/#!6/8f1aa/9
Since you are using SQL Server 2012 you could take advantage of hierarchyid; here is an example following Laurence's schema:
CREATE TABLE Groups
(
Id INT NOT NULL
PRIMARY KEY
, Title VARCHAR(20)
, HID HIERARCHYID
)
INSERT INTO Groups
VALUES ( 1, 'People', '/' ),
( 2, 'Male', '/1/' ),
( 3, 'Female', '/2/' ),
( 4, 'Boy', '/1/1/' ),
( 5, 'Man', '/1/2/' );
SELECT Id
, Title
FROM Groups
WHERE HID NOT IN ( SELECT HID.GetAncestor(1)
FROM Groups
WHERE HID.GetAncestor(1) IS NOT NULL )
http://sqlfiddle.com/#!6/00330/1/0
Results:
ID TITLE
3 Female
4 Boy
5 Man

MySQL: group by and IF statement

By default, parent_id = 0. I want to select all records with parent_id = 0 and only the last ones with parent_id > 0.
I tried this, but it didn't work:
SELECT * FROM `articles`
IF `parent_id` > 0 THEN
GROUP BY `parent_id`
HAVING COUNT(`parent_id`) >= 1
END;
ORDER BY `time` DESC
I mean, that if there are a few records with parent_id = 2, only one of them should be return. Also, if there a number of records with parent_id = 5, only one of them is returned. In other words, there should no more than one record of each parent_id apart from those having parent_id = 0.
What could be the solution?
If I understood you question well, you need all records with parent_id == 0 and only records with the greatest value of 'time' column for other parent_id's ? I suppose that parent_id + time is unique.
SELECT a.* FROM `articles` a
INNER JOIN
(SELECT parent_id, MAX(`time`) AS `time` FROM `articles` WHERE parent_id >0) t
ON (t.parent_id = a.parent_id AND a.`time` = t.`time`)
UNION
SELECT * FROM `articles` WHERE parent_id = 0;
You're mashing all the concepts together. WHERE, GROUP BY, and HAVING do different things. You probably need to read this page more thoroughly:
http://dev.mysql.com/doc/refman/5.1/en/select.html
But if I make some assumptions about what you're doing...
You probably want to start with this part:
SELECT * FROM articles WHERE parent_id > 0
But now you want to group together the articles that have the same parent. Assuming that each article has an id, perhaps you want
SELECT parent_id, COUNT(*) AS article_count FROM articles WHERE parent_id >= 0 GROUP BY parent_id
Now, all of these rows will be parent_id values with at least one child article, but if you wanted to only look at parents with more than 2 child articles, you might then want:
SELECT parent_id, COUNT(*) AS article_count
FROM articles
WHERE parent_id >= 0
GROUP BY parent_id
HAVING article_count >= 2
That will show you the identity of all parent articles with at least 2 child articles.
I think you want to do it as two queries and join the results with a UNION. I don't really have enough info from your post to know for sure what it would look like, but maybe something like this:
SELECT parent_id, time FROM articles WHERE parent_id = 0
UNION ALL
SELECT parent_id, MAX(TIME) FROM articles WHERE parent_id > 0
GROUP BY parent_id