SQL Count people who have all 4 grandparents - sql

So I have these following tables:
1) ADDRESS PERSON_ID | START_DATE | END_DATE | STREET | HOUSE | ROOM | ZIPCODE | CITY
2) IDENTIFIER NR | PERSON_ID | ISSUE_DATE | VALID_UNTIL
3) MARRIAGE WIFE_ID | HUSBAND_ID | START_DATE | END_DATE
4) PERSON ID | FIRST_NAME | LAST_NAME | BIRTHDATE | MOTHER_ID | FATHER_ID
And my task is to calculate the number of people who have all 4 grandparents recorded in the database.
So if I got it right, I have to find all the people in the table where both MOTHER_ID and FATHER_ID are not null, whose MOTHER_ID and FATHER_ID columns are also not null.
What I have for now is:
SELECT COUNT(*)
FROM PERSON
WHERE MOTHER_ID IS NOT NULL AND FATHER_ID IS NOT NULL
which returns the number of people who have both parents recorded in the database and I'm stuck at this point.

I'd join the table on itself twice, once for the father and once for the mother, and then check their parents:
SELECT COUNT(*)
FROM person p
JOIN person f ON p.father_id = f.id AND f.father_id IS NOT NULL AND f.mother_id IS NOT NULL
JOIN person m ON p.mother_id = m.id AND m.father_id IS NOT NULL AND m.mother_id IS NOT NULL

You should join two instance of Person table together and then select rows with four mother|father_1|2 is not null.
the sql is something like this.
SELECT p1.father_id AS father_id_1, p2.father_id AS father_id_2,p1.mother_id AS mother_id_1,p2.mother_id AS mother_id_2,
FROM Person p1
JOIN Person p2 ON p1.FATHER_ID = p2.ID and p1.MOTHER_ID = p2.ID

Related

A better way to aggregate into a default value

For this example I have three tables (individual, business, and ind_to_business). Individual has information on people. Business has information on businesses. And ind_to_business has information on which people are linked to which business. Here are their DDL:
CREATE TABLE individual
(
ID INTEGER PRIMARY KEY,
NAME VARCHAR2(100) NOT NULL,
ENTERPRISE_ID VARCHAR2(25) NOT NULL UNIQUE
);
CREATE TABLE business
(
ID INTEGER PRIMARY KEY,
NAME VARCHAR2(100) NOT NULL,
ENTERPRISE_ID VARCHAR2(25) NOT NULL UNIQUE
);
CREATE TABLE ind_to_business
(
ID INTEGER PRIMARY KEY,
IND_ID REFERENCES individual(id),
BUS_ID REFERENCES business(id),
START_DT DATE NOT NULL,
END_DT DATE
);
I'm looking for the best way to display one row for each person. If they are linked to one business, I want to display the the business's ENTERPRISE_ID. If they are linked to more than one business, I want to display the default value 'Multiple'. They will always be linked to a business, so there is no LEFT JOIN necessary. They can also be linked to a business more than once (Leaving and coming back). Multiple records for the same business would be aggregated.
So for the following sample data:
Individual:
+----+------------+---------------+
| ID | NAME | ENTERPRISE_ID |
+----+------------+---------------+
| 1 | John Smith | 53a23B7 |
| 2 | Jane Doe | 63f2a35 |
+----+------------+---------------+
Business:
+----+----------+---------------+
| ID | NAME | ENTERPRISE_ID |
+----+----------+---------------+
| 3 | ABC Corp | 2a34d9b |
| 4 | XYZ Inc | 34bf21e |
+----+----------+---------------+
ind_to_business
+----+--------+--------+-------------+-------------+
| ID | IND_ID | BUS_ID | START_DT | END_DT |
+----+--------+--------+-------------+-------------+
| 5 | 1 | 3 | 01-JAN-2000 | 31-DEC-2002 |
| 6 | 1 | 3 | 01-JAN-2015 | |
| 7 | 2 | 3 | 01-JAN-2000 | |
| 8 | 2 | 4 | 01-MAR-2006 | 05-JUN-2010 |
| 9 | 2 | 4 | 15-DEC-2019 | |
+----+--------+--------+-------------+-------------+
I would expect the following output:
+---------+------------+------------+
| IND_ID | NAME | LINKED_BUS |
+---------+------------+------------+
| 53a23B7 | John Smith | 2a34d9b |
| 63f2a35 | Jane Doe | Multiple |
+---------+------------+------------+
Here is my current query:
SELECT DISTINCT
sub.ind_id,
sub.name,
DECODE(sub.bus_count, 1, sub.bus_id, 'Multiple') AS LINKED_BUS
FROM (SELECT i.enterprise_id AS IND_ID,
i.name,
b.enterprise_id AS BUS_ID,
COUNT(DISTINCT b.enterprise_id) OVER (PARTITION BY i.id) AS BUS_COUNT
FROM individual i
INNER JOIN ind_to_business i2b ON i.id = i2b.ind_id
INNER JOIN business b ON i2b.bus_id = b.id) sub;
My query works, but this is running on a large dataset and taking a long time to run. I'm wondering if anyone has any ideas on how improve this so that there isn't so much wasted processing (i.e Needing to do a DISTINCT on the final result or doing COUNT(DISTINCT) in the inline view only to use that value in the DECODE above).
I've also created a DBFiddle for this question. (Link)
Thanks in advance for any input.
You could try and use a correlated subquery. This removes the need for outer distinct:
SELECT
i.enterprise_id ind_id,
i.name,
(
SELECT DECODE(COUNT(DISTINCT b.enterprise_id), 1, MIN(bus_id), 'Multiple')
FROM ind_to_business i2b
INNER JOIN business b ON i2b.bus_id = b.id
WHERE i2b.ind_id = i.id
) linked_bus
FROM individual i
You can join with the aggregated ind_to_business per individual. One way to do this:
select i.id, i.name, coalesce(b.enterprise_id, 'Multiple')
from individual i
join
(
select
ind_id,
case when min(bus_id) = max(bus_id) then min(bus_id) else null end as bus_id
from ind_to_business
group by ind_id
) ib on ib.ind_id = i.id
left join business b on b.id = ib.bus_id
order by i.id;
First you should sub-query to get all needed dimensions and then do all your final aggregation using CASE statement.
select
ind_id,
name,
case
when count(*) > 1 then 'Multiple'
else ind_id
end as linked_bus
from
(
select
distinct i.enterprise_id as ind_id,
i.name,
b.enterprise_id as bus_id
from individual i
join ind_to_business i2b
on i.id = i2b.ind_id
join business b
on i2b.bus_id = b.id
) vals
group by
ind_id,
name
order by
ind_id
No need of using DISTINCT twice. You could use subquery factoring and put the in-line view in WITH clause, and make the data set DISTINCT in the subquery itself.
WITH data AS
(
SELECT distinct
i.enterprise_id AS IND_ID,
i.name,
b.enterprise_id AS BUS_ID
FROM individual i
JOIN ind_to_business i2b ON i.id = i2b.ind_id
JOIN business b ON i2b.bus_id = b.id
)
SELECT ind_id,
name,
case
when count(*) = 1 then MIN(bus_id)
else 'Multiple'
end AS LINKED_BUS
FROM data
GROUP BY ind_id, name;
IND_ID NAME LINKED_BUS
---------- ---------- -------------------------
53a23B7 John Smith 2a34d9b
63f2a35 Jane Doe Multiple

SQL Count Number Of Classmates For a Student Across All Courses

I have the following tables:
Student: Student_ID (PK), Student_Name
Course: Course_ID (PK), Course_Name
Enrollment: Student_ID (FK), Course_ID (FK)
I need 2 queries:
A query that computes for each student id in the Student table the total number of different (Unique) classmates that this student has across all courses. If the student is not enrolled in any courses 0 should be returned.
For example if Student_ID 123 is enrolled in 3 courses and he has 10 different classmates in each of those courses, I should get the following result:
Student_ID Num_of_classmates
-----------------------------------
123 30
A SQL query that returns all students with all of their classmates. classmate of a student is another student who is enrolled in at least one same class.
It is unclear which of these you want:
Total students in all the classes (which would include a given student).
Total distinct students, since some classmates might be in multiple classes.
Total distinct students not including the given student ("I am not my own classmate").
In any case, the idea is basically two joins and aggregation:
select s.student_id,
count(ec.student_id) as total_classmates,
count(distinct s.student_id) as distinct_classmates,
count(distinct case when ec.student_id <> s.student_id then ec.student_id end) as distinct_other_classmates
from student s left join
enrollment e
on e.student_id = s.student_id left join
enrollment ec
on ec.class_id = e.class_id;
group by s.student_id;
Here I only give a solution to part 2) as #Gordon Linoff has done part 1) and you have also fixed the -1 problem.
A point: use inner join instead of left join here to avoid NULL in classmate names. Hopefully this also adds a little bit of help :)
Test dataset
if object_id('tempdb..#Student') is not null drop table #Student;
create table #Student (
Student_ID int PRIMARY key,
Student_Name varchar(50)
)
insert into #Student(Student_ID, Student_Name)
values (1,'Alice'), (2,'Bob'),(3,'Claire'),(4,'Danny'),(5,'Eve'),(6,'Frank');
if object_id('tempdb..#Course') is not null drop table #Course;
create table #Course (
Course_ID int PRIMARY key,
Course_Name varchar(50)
)
insert into #Course(Course_ID, Course_Name)
values (1,'Algorithm'), (2,'Bash'),(3,'Compiler'),(4,'Design Pattern'),(5,'Exception Handling');
if object_id('tempdb..#Enrollment') is not null drop table #Enrollment;
create table #Enrollment (
Student_ID int,
Course_ID int
)
insert into #Enrollment(Student_ID, Course_ID)
values (1,1),(1,3),
(2,2),(2,3),
(3,3),(3,4),
(4,1),(4,4),
(5,1),
(6,5); -- This Frank guy has no classmate
-- select * from #Student;
-- select * from #Course;
-- select * from #Enrollment;
Solution to 2)
select distinct
A.Student_Name as Student_Name,
D.Student_Name as Classmate_Name
from #Student as A
-- Student (A) -> Enrolled Courses (B)
inner join #Enrollment as B
on A.Student_ID = B.Student_ID
-- Enrollment Courses (B) -> Enrolled Students in that Course (C)
inner join #Enrollment as C
on B.Course_ID = C.Course_ID
and B.Student_ID <> C.Student_ID -- exclude self
-- Classmate's names
inner join #Student as D
on C.Student_ID = D.Student_ID
order by Student_Name, Classmate_Name;
Output
N.B. Frank has no classmates and do not show a NULL value.
| Student_Name | Classmate_Name |
|--------------|----------------|
| Alice | Bob |
| Alice | Claire |
| Alice | Danny |
| Alice | Eve |
| Bob | Alice |
| Bob | Claire |
| Claire | Alice |
| Claire | Bob |
| Claire | Danny |
| Danny | Alice |
| Danny | Claire |
| Danny | Eve |
| Eve | Alice |
| Eve | Danny |

How to get names based on it roles

Is there any way to display two person name in same row based on their role.
I having 4 tables which is person, student, teacher and TourAttend. Person is the parent of the two table student and teacher.
Person
-------
personID
Name
Role
Student
--------
StudentID
personID reference person
Teacher
-------
teacherID
personID reference person
TourAttend
-----------
TourID reference to Tour table (Which i didnt reveal)
TourAttendID primary key
PersonID reference person
example values of
PersonID | Name | Role
-----------------------
1 | student1| student
2 | student2| student
3 | teacher1| teacher
4 | teacher2| teacher
5 | student3| student
example values for TourAttend
TourID | tourAttend | PersonID
1 | 1 | 1
1 | 2 | 2
1 | 3 | 3
2 | 4 | 4
2 | 5 | 5
i would to get a query result that is each student is accompanied by who (teacher) Based on the Tour
Student Name | Teacher Name
student 1 | teacher 1
student 2 | teacher 1
student 3 | teacher 2
so far i only tried is
select person.name as 'student name' , person.name as 'Teacher name' from person
and i am stuck because the person.name is used for both. how do i resolve this problem?
This should work for you
select
t1.name as student_name, t2.name as teacher_name
from
(
select
p.name, t.TourID
from
Person p
inner join
TourAttend t
on
p.PersonID = t.PersonID
where
p.role = 'student'
) t1
inner join
(
select
p1.name, t3.TourID
from
Person p1
inner join
TourAttend t3
on
p1.PersonID = t3.PersonID
where
p1.role = 'teacher'
) t2
on
t1.TourID = t2.TourID;

Optimizing WHERE clause SQL query

I'm using SQL Server. I find myself doing complex queries in the WHERE clause with the following syntax:
SELECT ..
WHERE StudentID IS NULL OR StudentID NOT IN (SELECT StudentID from Students)
was wondering if there's a better approach/more cleaner way to replace it with because this is a small example of the bigger query I'm doing which includes multiple conditions like that.
As you can see I'm trying to filter for a specific column the rows which its column value is null or not valid id.
EDIT
Courses:
|CourseID | StudentID | StudentID2|
|-----------------------------------|
| 1 | 100 | NULL |
| 2 | NULL | 200 |
| 3 | 1 | 1 |
Students
|StudentID | Name |
|--------------------
| 1 | A |
| 2 | B |
| 3 | C |
Query:
SELECT CourseID
FROM Courses
WHERE
StudentID IS NULL OR StudentID NOT IN (SELECT * FROM Students)
OR StudentID2 IS NULL OR StudentID2 NOT IN (SELECT * FROM Students)
Result:
| CourseID |
|-----------|
| 1 |
| 2 |
As you can see, course 1 and 2 has invalid students.
Alain was close, except the studentID2 column is associated with the courses table. Additionally, this is joining each studentID column to an instance of the students table and the final WHERE is testing if EITHER of the student ID's fail, so even if Student1 is valid and still fails on Student2, it will capture the course as you are intending.
SELECT
C.CourseID
FROM
Courses C
LEFT JOIN Students S
ON C.StudentId = S.StudentId
LEFT JOIN Students S2
OR C.StudentId2 = S2.StudentId
WHERE
S.StudentId IS NULL
OR S2.StudentID IS NULL
this is not a sure shot but i have had experience that this is better performer than the question one:
SELECT CourseID from Courses WHERE
Courses.StudentID NOT exists (SELECT 1 FROM Students where Students.StudentID=nvl(Courses.StudentID,-1));
Also Create an index on StudentId in the Students Table.
And if your data model supports create a primary key foreign key relationship between the 2 tables. That way u definitely avoid invalid values in the courses table.
After your update:
SELECT CourseID from Courses WHERE
Courses.StudentID NOT exists (SELECT 1 FROM Students where Students.StudentID=nvl(Courses.StudentID1,-1) or Students.StudentID=nvl(Courses.StudentID2,-1));
The NOT EXISTS pattern is fine, however, you have several ways to do that.
You should check here and here
For example with LEFT JOIN (two left joins since two variables are checked)
SELECT *
from Courses
LEFT JOIN Students Student1
on Courses.StudentId = Student1.StudentId
LEFT JOIN Students Student2
on Courses.StudentId2 = Student2.StudentId
WHERE
-- No matching Student
student1.StudentId IS NULL
and student2.StudentId IS NULL

sql query related

Hi I have an "Student" table with below records
INSERT into Student(studId,name)
Values(1,A)
values(2,B)
values(3,C)
values(4,D)
I have a "Department" table with below records
INSERT into dept(deptId,Deptname,Emp Id)
Values(D1,Phy,1)
values(D2,Maths,2)
values(D3,Geo,3)
How can i find the student who does not belong to any department i.e. in this case the result should be "D".
I know the left outer join would return all the records from the student table but i am only interested to get 1 record i.e.: of student "D".
SELECT *
FROM Student
WHERE studId NOT IN (SELECT EmpId FROM dept)
Find all Student.studId entries that do not already exist in the dept.EmptId column as an entry. (Assuming I read your table correctly)
Ideally though, you should probably break out the "Student" and "Department" and creating a joining table (maybe called) "Student_Department" that links the keys of each table.
+--------------+ +--------------------+ +--------------+
| Student | | Student_Department | | Dept |
|--------------| |--------------------| |--------------|
| studId | <-----| studId | .-> | deptId |
| name | | deptId | --' | name |
| ... | +--------------------+ | ... |
+--------------+ +--------------+
This allows you to only have to define a student and department once, but can assign one student to multiple departments, one department to multiple students, or any combination therein.
Pseudo-code:
select *
from students
where id not in(select a.id from students a
inner join department b where b.id in('D1','D2','D3'))
While I agree with some commenters that this sounds like a homework problem, I'll answer with a question...
I know the left outer join would return all the records from the student table but i am only interested to get 1 record i.e.: of student "D".
OK, let's say you run the following query:
SELECT * FROM Student
LEFT OUTER JOIN dept ON Student.studId = dept.EmpId
You'd get the results:
studId name deptId deptName EmpId
1 A D1 Phy 1
2 B D2 Maths 2
3 C D3 Geo 3
4 D NULL NULL NULL
Can you add a WHERE clause to this query that will filter out only the data you want? :)
SELECT s.name
FROM Student s
LEFT JOIN Dept d ON d.empId = s.studId
WHERE d.empId IS NULL