how can I write a postgresql query to find someone's cousin in database? - sql

Person(ID, name, gender, fatherID, motherID, spouseID);
This is my database columns.
for example if id = 5 how can I find this person's cousins?
i have to use just person table. And cousin means someone's mother's and father's siblings' children.
I try to use nested queries but it was too many query to follow for the result.
For example that query find someone's siblings
SELECT name
FROM person
WHERE motherid = (SELECT motherid
FROM person
WHERE id = x)
AND fatherid = (SELECT fatherid
FROM person
WHERE id = x)
EXCEPT
(SELECT name FROM person WHERE id = x);

Maybe joining to the parents of the parents, then back to their children's children.
(untested notepad scribble)
SELECT DISTINCT
kid.name as kid,
cousin.name as cousin
FROM person kid
LEFT JOIN person AS parent
ON parent.id IN (kid.fatherid, kid.motherid)
LEFT JOIN person AS grandparent
ON grandparent.id IN (parent.fatherid, parent.motherid)
LEFT JOIN person AS auntcle
ON grandparent.id IN (auntcle.fatherid, auntcle.motherid)
AND auntcle.id != parent.id
LEFT JOIN person AS cousin
ON auntcle.id IN (cousin.fatherid, cousin.motherid)
WHERE cousin.fatherid != kid.fatherid AND cousin.motherid != kid.motherid -- redneck check

Related

SQL statement : average

My question: What is the average age to become the first grandpa. The solution should be given out as average_age. The day a person becomes grandpa is where his first grandchild was born.
Relations:
human (name, gender, age)
parent (ParentName, ChildName) -> is subset of human(name).
Table:
I do know that grandpa is the person which has a parentname and a child in childname which is also a person(father) in parentname which has children in childname (grandchildren). The problem is now how do I get the average age to become grandpa.
What I got so far:
SELECT AVG(age) as average_age
FROM human h JOIN
parent p
ON h.name = p.parentname
WHERE h.gender = 'm' AND p.parentname = p.childname AND h.name = p.parentname
Expected outcome:
average_age : 52
It is extremely unusually to be storing the AGE of people in a table, because that changes -- every day. The data should be stored with a date of birth.
This is an aggregation query, but you have to join the tables multiple times. To get grandparents, you need a join on the parents table. Then you need to bring in humans for filtering:
select avg(min_age * 1.0)
from (select min(h_grandparent.age - h.grandchild.age) as min_age
from parent p join -- p.parentname is the grandparent
parent pchild
on p.childname = pchild.parentname join
human h_grandparent
on p.parentname = h_grandparent.name join
human h_grandchild
on pchild.childname = h_grandchild.name
where h_grandparent.gender = 'm'
group by h_grandparent.name
) a
I would address this with an exists condition that filters on humans that have grandchilds:
select avg(age) avg_age_of_grandpas
from human h
where
gender = 'm'
and exists (
select 1
from parent p1
inner join parent p2 on p2.parentName = p1.childName
where p1.parentName = h.name
)
The exists condition ensures that the person has at least one child and one grand child. The the outer query computes the average of such humans. Given the information available in your table structure, this seems to me like the most logical approach. Unlike joins, using exists avoids duplicating the records (and getting wrong results in the average) when a person has more than one line of descendants.
If you want the age of the grand parent at the date when their first grand child was born, then it is a bit complicated. This should get you close to what you expect:
select avg(h.age - g.maxGrandChildAge) avg_age_of_grandpas
from human h
inner join (
select
p1.parentName grandParentName,
max(h1.age) maxGrandChildAge
from parent p1
inner join parent p2 on p2.parentName = p1.childName
inner join human h1 on h1.name = p2.childName
) g
on g.grandParentName = h.name
You can do this with 2 more joins and subtraction the age of biggest grandchild:
SELECT AVG(p_age) average_age
FROM
(
SELECT h.name, h.age-MAX(h2.age) as p_age
FROM parent p1
LEFT JOIN parent p2 ON P1.childname = P2.parentname
INNER JOIN human h ON P1.parentname = h.name
INNER JOIN human h2 ON P2.ChildName = h2.name
WHERE h.gender = 'm' AND p2.childname IS NOT NULL
GROUP BY h.name, h.age
)pAges
Please consider that the name is not appropriate data for doing this task.

PostgreSQL: How do I get data from table `A` filtered by a column in table `B`

I want to fetch all parents that have kids in a specific grade only in a school.
Below are trimmed down version of the tables.
TABLE students
id,
last_name,
grade_id,
school_id
TABLE parents_students
parent_id,
student_id
TABLE parents
id,
last_name,
school_id
I tried the below query but it doesn't really work as expected. It rather fetches all parents in a school disregarding the grade. Any help is appreciated. Thank you.
SELECT DISTINCT
p.id,
p.last_name,
p.school_id,
st.school_id,
st.grade_id,
FROM parents p
INNER JOIN students st ON st.school_id = p.school_id
WHERE st.grade_id = 118
AND st.school_id = 6
GROUP BY p.id,st.grade_id,st.school_id;
I would think:
select p.*
from parents p
where exists (select 1
from parents_students ps join
students s
on ps.student_id = s.id
where ps.parent_id = p.id and
s.grade_id = 118 and
s.school_id = 6
);
Your question says that you want information about the parents. If so, I don't see why you are including redundant information about the school and grade (it is redundant because the where clause specifies exactly what those values are).

Select name and surname in sql

So I have four tables:
Team:
(PK) ID
Name
Player:
(PK) ID
(FK) Person
PlayerContract:
(PK) ID
(FK) Player
(FK) Team
Person:
(PK) ID
Name
Surname
I have to select all names and surnames of players which are in team X. How can I do it? I wanted to do this in this way:
SELECT
name,
surname
FROM
Person
WHERE
ID = SELECT Person
FROM Player
WHERE ID = SELECT Player
FROM PlayerContact
WHERE Team = SELECT ID
FROM Team
WHERE Name = "X";
Is it ok?
No, it's not ok, you can easily test your code. Anyway, what you need to do is to JOIN your tables:
SELECT pe.Name,
pe.Surname
FROM Player AS pl
INNER JOIN PlayerContract AS pc
ON pl.ID = pc.Player
INNER JOIN Team AS t
ON pc.Team = t.ID
INNER JOIN Person AS pe
ON pc.Person = pe.ID
WHERE t.Name = 'X'
It would work is you enclosed the subqueries in parentheses:
SELECT
name,
surname
FROM
Person
WHERE
ID = (SELECT Person
FROM Player
WHERE ID = (SELECT Player
FROM PlayerContact
WHERE Team = (SELECT ID
FROM Team
WHERE Name = "X")));
Note that the results may be different from a JOIN, for example, if a player is on more than one team (in which case the query above would not work since the second subquery would return two records).

SQL to select a table through a generic relationship

In a relational database I have three tables. Using SQL Server.
person(id, type)
student(id, person_id, type, student specific fields)
teacher(id, person_id, type, teacher specific fields)
Student and teacher are both people, therefore a student will have a record in both the person and student tables, as will the teacher. Student and teacher have foreign keys to person. Student and teacher have different field definitions therefore a union will NOT work.
Now I have the person's id and depending on whether the person is a student or teacher I would like to select * from the relevant table (not person).
For example, if the person is a student I would like my query to select the student table.
I can think of a few inefficient methods but I am looking for the optimum one.
I would suggest a UNION
SELECT student.*
FROM student
WHERE person_id= #id
UNION
SELECT teacher.*
FROM teacher
WHERE person_id= #id
if exists(select person_id from student where person_id = #id)
select * from student where person_id = #id
else
if exists(select person_id from teacher where person_id = #id)
select * from teacher where person_id = #id
If your RDBMS is SQLServer, then I would abstract a view along the lines of podiluska's union, mapping out specific fields in Student and Teacher to common names, and padding with NULLs where no mapping is possible
And assuming that Students and Teachers inherit from person (i.e. both are 0..1 to 1 with Person), then they can share the same primary key, i.e. no need for new surrogates keys on Teacher and Student.
I've assumed that person.type determines whether the person is a Student(S) or Teacher(T).
CREATE VIEW SubClassesOfPerson AS
SELECT p.id as PersonId,
p.name as PersonName,
p.OtherBaseFieldsHere,
s.SomeStudentSpecificField AS MappedField1,
s.SomeStudentSpecificFieldX AS MappedFieldX,
s.SomeStudentSpecificField as MappedFieldForStudentOnly,
NULL as MappedFieldForTeacherOnly -- Pad this because it can't be mapped
FROM person p
INNER JOIN student s
on s.person_id = p.id AND p.type = 'S'
UNION
SELECT p.id as PersonId,
p.name as PersonName,
p.OtherBaseFieldsHere,
t.SomeTeacherSpecificField AS MappedField1,
t.SomeTeacherSpecificFieldX AS MappedFieldX,
NULL as MappedFieldForStudentOnly, -- Pad this because it can't be mapped
t.SomeTeacherSpecificField as MappedFieldForTeacherOnly
FROM person p
INNER JOIN teacher t
on t.person_id = p.id AND p.type = 'T'

SQL query for finding row with same column values that was created most recently

If I have three columns in my MySQL table people, say id, name, created where name is a string and created is a timestamp.. what's the appropriate query for a scenario where I have 10 rows and each row has a record with a name. The names could have a unique id, but a similar name none the less. So you can have three Bob's, two Mary's, one Jack and 4 Phil's.
There is also a hobbies table with the columns id, hobby, person_id.
Basically I want a query that will do the following:
Return all of the people with zero hobbies, but only check by the latest distinct person created, if that makes sense. Meaning if there is a Bob person that was created yesterday, and one created today.. I only want to know if the Bob created today has zero hobbies. The one from yesterday is no longer relevant.
select pp.id
from people pp, (select name, max(created) from people group by name) p
where pp.name = p.name
and pp.created = p.created
and id not in ( select person_id from hobbies )
SELECT latest_person.* FROM (
SELECT p1.* FROM people p1
WHERE NOT EXISTS (
SELECT * FROM people p2
WHERE p1.name = p2.name AND p1.created < p2.created
)
) AS latest_person
LEFT OUTER JOIN hobbies h ON h.person_id = latest_person.id
WHERE h.id IS NULL;
Try This:
Select *
From people p
Where timeStamp =
(Select Max(timestamp)
From people
Where name = p.Name
And not exists
(Select * From hobbies
Where person_id = p.id))