SQL JOIN on one field or the other - sql

Trying to order a family by father's name or, if there is no father, then the mother's name where the names are in a separate "person" table, something like:
SELECT DISTINCT family.myid FROM family
JOIN person
ON family.father_id = person.myid OR
family.mother_id = person.myid
ORDER BY person.surname,
person.given_name;
In this version, the families without fathers end up unsorted at the bottom. Would like families without fathers to appear in the order by the mother's name. Sqlite SQL will suffice.

Basically, you need a separate join for the fathers and the mothers:
select f.*
from family f left join
person d
on f.father_id = d.myid left join
person m
on f.mother_id = m.myid
order by (case when d.myid is null then m.surname else d.surname end),
(case when d.myid is null then m.given_name else d.given_name end);
Because a value could be missing, this should be a left join.

COALESCE should work
ORDER BY COALESCE(NULLIF(b.surname, ''), c.surname),
COALESCE(NULLIF(b.given_name, ''), c.given_name)

Related

Find diff between three tables

I'm trying to find the difference between three tables, a, b, and c. I'd like to join all three, showing nulls where each table does not have records.
Each table has two columns that I would like to join on, lets say first and last name.
So far, this is what I'm doing.
Select * from a
FULL OUTER JOIN b on
a.firstName = b.firstName AND a.lastname = b.lastname
FULL OUTER JOIN c on
(c.firstName = a.firstName and c.lastName = a.LastNAME) OR
(c.firstName = b.firstName AND c.lastname = b.LastName)
I'm not confident that the logic in my output is correct. Is there a better way of doing this? In english, the first join joins A and B, including records in each table that don't exist in the other table. The second join joins A+B to C, joining C to either A or B if it matches.
You may want:
select firstname, lastname, sum(in_a), sum(in_b), sum(in_c)
from ((select firstname, lastname, 1 as in_a, 0 as in_b, 0 as in_c
from a
) union all
(select firstname, lastname, 0 as in_a, 1 as in_b, 0 as in_c
from b
) union all
(select firstname, lastname, 0 as in_a, 0 as in_b, 1 as in_c
from c
)
) abc
group by firstname, lastname;
This should each firstname/lastname pair with information about how many times the pair is in each table.
I prefer the union all/group by approach for this over left join for several reasons:
Without the using clause, the on clause gets a bit tricky as you add more tables.
Minimal use of coalesce().
Better handles duplicates within each table.
Handles NULL values for the keys.

How to create a query to join three tables and make calculations in SQL?

I'm just at the beginning of my SQL studies and can't figure out how to resolve the next problem.
So, there are three tables:
! given tables
The task is: "Get number of pet type per owner"
Write a query to generate the result below:
! desired output
The best result I have for the moment:
SELECT owners.OWNER_NAME, COUNT(pets.OWNER_ID) AS pets
FROM owners
JOIN pets ON owners.ID = pets.OWNER_ID
JOIN pet_type ON pets.TYPE = pet_type.ID
GROUP BY owners.OWNER_NAME;
It returns first column with owner names and second column with the sum of particular owner pets.
Will appreciate any help.
You need conditional aggregation:
SELECT
o.OWNER_NAME,
SUM(CASE WHEN t.name = 'CAT' THEN 1 ELSE 0 END) CAT,
SUM(CASE WHEN t.name = 'DOG' THEN 1 ELSE 0 END) DOG,
SUM(CASE WHEN t.name = 'SNAKE' THEN 1 ELSE 0 END) SNAKE
FROM owners o
JOIN pets p ON o.ID = p.OWNER_ID
JOIN pet_type t ON p.TYPE = t.ID
GROUP BY o.OWNER_NAME;
I use name as the name of the column describing the type in table pet_type. Change it to the actual name of the column.
Check this. To Get number of pet type per owner, this is sufficient to join only Pets table with the owners table. A DISTINCT count of Pet.Type will give your desired output.
SELECT
owners.ID,
owners.OWNER_NAME,
COUNT(DISTINCT pets.TYPE) AS Num_Pet_Type
FROM owners
INNER JOIN pets ON owners.ID = pets.OWNER_ID
GROUP BY owners.ID,owners.OWNER_NAME;
If you wants number of Pet per type, use this below script-
SELECT
owners.ID,
owners.OWNER_NAME,
pets.TYPE,
COUNT(*) AS Num_Of_Pet
FROM owners
INNER JOIN pets ON owners.ID = pets.OWNER_ID
GROUP BY owners.ID,owners.OWNER_NAME,pets.TYPE;

joining a table on 2 fields

I want to pull a person and their supervisor names from a table. The persons table has the supervisor_id and the person_id. The names table has name_id and a Full Name field. If I join Person ON either supervisor_id or person_id, how do I get the other to display as well?
You need to join twice, one for each relationship you have:
SELECT
-- Persons' columns
P.*,
-- Superviser name columns
SN.*,
-- Person name columns
PN.*
FROM
persons AS P
LEFT JOIN names AS SN ON P.supervisor_id = SN.name_id
LEFT JOIN names AS PN ON P.person_id = PN.name_id
Or you can join with an OR clause, but you won't be able to know which record did you join with unless you check with a CASE.
SELECT
-- Persons' columns
P.*,
-- name columns
N.*,
IsSupervisor = CASE WHEN P.supervisor_id = N.name_id THEN 'Yes' ELSE 'No' END
FROM
persons AS P
LEFT JOIN names AS N ON
P.supervisor_id = N.name_id OR
P.person_id = N.name_id
This last approach will display 2 rows as it will match either one or the other on different occasions, not both with the same persons row (as the first example).
A (self)join is what you need:
select p.*, supervisor=ps.name
from Person p join person ps on p.supervisor_id=ps.id

Which Join for SQL plus query

I have 4 tables, I would like to select one column from each table, but only if the department has both 'Mick' and 'Dave working in it (must have both names, not one or the other). But it does not seem to be working properly:
SELECT SCHOOL_NAME, TOWN, COUNTY
FROM STUDENTS
NATURAL JOIN SCHOOLS NATURAL JOIN TOWNS NATURAL JOIN
COUNTIES
WHERE FIRST_NAME IN ('Mick','Dave)
/
I'm going wrong somewhere (probably lots of places :( ). Any help would be great
Don't use NATURAL JOIN. It is an abomination, because it does not take properly declared foreign key relationships into account. It only looks at the names of columns. This can introduce really hard to find errors.
Second, what you want is aggregation:
select sc.SCHOOL_NAME, t.TOWN, c.COUNTY
from STUDENTS st join
SCHOOLS sc
on st.? = sc.? join
TOWNS t
on t.? = ? join
COUNTIES c
on c.? = t.?
where FIRST_NAME in ('Mick', 'Dave')
group by sc.SCHOOL_NAME, t.TOWN, c.COUNTY
having count(distinct st.first_name) = 2;
The ? are placeholders for table and column names. If you are learning SQL, it is all the more important that you understand how columns line up for joins in different tables.
A where clause can only check the values in a single row. There is a separate row for each student, so there is no way -- with just a where -- to find both students. That is where the aggregation comes in.
You need at least three Join conditions, and properly end the string Dave with quote :
SELECT SCHOOL_NAME, TOWN, COUNTY
FROM SCHOOLS h
JOIN TOWNS t ON (t.id=h.town_id)
JOIN COUNTIES c ON (t.county_id=c.id)
WHERE EXISTS ( SELECT school_id
FROM STUDENTS s
WHERE s.first_name in ('Mick','Dave')
AND school_id = h.id
GROUP BY school_id
HAVING count(1)>1
);
SQL Fiddle Demo
You can use an analytic function in a sub-query to count the students who have the name Mick or Dave for each school_id (assuming that is your identifier for a school):
SELECT SCHOOL_NAME, TOWN, COUNTY
FROM ( SELECT *
FROM (
SELECT d.*,
COUNT(
DISTINCT
CASE WHEN FIRST_NAME IN ( 'Mick', 'Dave' ) THEN FIRST_NAME END
) OVER( PARTITION BY school_id )
AS num_matched
FROM STUDENTS d
)
WHERE num_matched = 2
)
NATURAL JOIN SCHOOLS
NATURAL JOIN TOWNS
NATURAL JOIN COUNTIES;
SQLFiddle
You would also be better to use an INNER JOIN and explicitly specify the join condition rather than relying on NATURAL JOIN.

LEFT JOIN confusion -- I need to retrieve a student count

I have four tables:
students
classes
teachers
teacher_assignments
classes and teachers has a many-to-many relationship and so teacher_assignments acts as the xref table (with fields teacher_id and class_id).
Each student in students has a class_id (many-to-one -- many students to one class).
I should also mention that teacher_assignments has an active column (BOOL) which indicates whether that assignment is currently active
What I want to do:
I want to retrieve the following:
class_name -- a concat of its level and sub_level, e.g. 3 and A
teacher_names -- the names of the teachers currently assigned to that class
student_count -- a count of the students in each class
At first, I tried retrieving just the class_name and teacher_names, like so:
SELECT
CONCAT(CONVERT(classes.level, CHAR(8)), classes.sub_level) AS class_name,
GROUP_CONCAT(DISTINCT teachers.name SEPARATOR ',') AS teacher_names
FROM
teacher_assignments
LEFT JOIN teachers
ON teachers.id = teacher_assignments.teacher_id
AND teacher_assignments.active = TRUE
LEFT JOIN classes
ON classes.id = teacher_assignments.class_id
GROUP BY classes.id
This works fine and outputs:
class_name | teacher_names
--------------------------------------
1A | NULL
2A | John, Sam
3B | Sam, Sarah
(Class 1A has no teachers currently, and so the NULL is expected)
... BUT, now I have no idea how to work the student_count into this.
My question:
How exactly should the students table be joined with the others in the above query so I can produce a student_count column?
Use:
SELECT CONCAT(CONVERT(c.level, CHAR(8)), c.sub_level) AS class_name,
GROUP_CONCAT(DISTINCT teachers.name SEPARATOR ',') AS teacher_names,
COUNT(s.id) AS studentCount
FROM CLASSES c
LEFT JOIN TEACHER_ASSIGNMENTS ta ON ta.class_id = c.id
AND ta.active = TRUE
LEFT JOIN TEACHERS t ON t.id = ta.teacher_id
LEFT JOIN STUDENTS s ON s.class_id = c.id
GROUP BY class_name
Column aliases can be referenced in the GROUP BY when using MySQL, otherwise you'd have to duplicate the logic that produces the class_name column value. This is also the column to GROUP on, as GROUP_CONCAT and COUNT are aggregate functions.
To get zero as the count value, you might need to use:
SELECT CONCAT(CONVERT(c.level, CHAR(8)), c.sub_level) AS class_name,
GROUP_CONCAT(DISTINCT teachers.name SEPARATOR ',') AS teacher_names,
COALESCE(COUNT(s.id), 0) AS studentCount
FROM CLASSES c
LEFT JOIN TEACHER_ASSIGNMENTS ta ON ta.class_id = c.id
AND ta.active = TRUE
LEFT JOIN TEACHERS t ON t.id = ta.teacher_id
LEFT JOIN STUDENTS s ON s.class_id = c.id
GROUP BY class_name
Just thinking off the top of my head...
Join classes and students tables to get the student count...
Instead of doing a left join on classes in your above query, you will do a left join with the result from #1 (essentially an inner join between classes and students tables) that allows you to pull the student count.
I dont think I would use a join but instead would use an inline column select on student like this:
SELECT
CONCAT(CONVERT(classes.level, CHAR(8)), classes.sub_level) AS class_name,
GROUP_CONCAT(DISTINCT teachers.name SEPARATOR ',') AS teacher_names,
( SELECT COUNT(*) FROM students WHERE students.class_id = classes.id ) AS student_count
FROM ...