How to SQL in many-to-many relationship - sql

I want to display how many hobbies does john have. Could you tell me how to write SQL statement?
PERSON table
ID | NAME
1 | John
HOBBY table
ID | NAME
1 | music
2 | sport
PERSON_HOBBY_COMBINATION table
ID | PERSON_ID | HOBBY_ID
1 | 1 | 1
expected result
HOBBY_NAME | HOBBY_EXIST
music | YES
sport | NO

This might work for you:
SELECT h.name,
CASE WHEN ph.id IS NULL THEN 'No' ELSE 'Yes' END AS hobby_exist
FROM hobby h
CROSS JOIN person p
LEFT JOIN person_hobby_conbination ph ON (ph.p_id = p.id AND ph.h_id = h.id)
WHERE (p.name = 'John')

Select NAME, count(*) AS NoHobbies from Person p
inner join PERSON_HOBBY_COMBINATION phc
on p.ID = phc.PERSON_ID
group by p.ID, p.NAME
Note that you should group on both ID and NAME of the person.
You need to group on NAME because you have it in the output, but if you have duplicate names grouping on NAME will sum several persons hobbies together, that is why you need to group on ID too.
Edit
When inspection your expected result you don't want how many hobbies John has, but which hobbies. Then you need to write
Select p.NAME as PersonName, h.Name as HobbyName, case when phc.ID is null then 'No' else 'Yes' end as HasHobby from Person p
inner join Hobby h
on 1 = 1
left outer join dbo.PersonHobbyCombination phc
on p.ID = phc.PersonID and h.ID = phc.HobbyID

If you already have John's ID and just want a raw count, then this should work.
select count(*) from person_hobby_combination where person_id=?

Related

Join one table with two other ones by id

I am trying to join one table with two others that are unrelated to each other but are linked to the first one by an id
I have the following tables
create table groups(
id int,
name text
);
create table members(
id int,
groupid int,
name text
);
create table invites(
id int,
groupid int,
status int \\ 2 for accepted, 1 if it's pending
);
Then I inserted the following data
insert into groups (id, name) values(1,'group');
insert into members(id, groupid, name) values(1,1,'admin'),(1,1,'other');
insert into invites(id, groupid, status) values(1,1,2),(2,1,1),(3,1,1);
Obs:
The admin does not has an invite
The group has an approved invitation with status 2 (because the member 'other' joined)
The group has two pending invites with status 1
I am trying to do a query that gets the following result
groupid | name | inviteId
1 | admin | null
1 | other | null
1 | null | 2
1 | null | 3
I have tried the following querys with no luck
select g.id, m.name, i.id from groups g
left join members m ON m.groupid = g.id
left join invites i ON i.groupid = g.id and i.status = 1;
select g.id, m.name, i.id from groups g
join (select groupid, name from members) m ON m.groupid = g.id
join (select groupid, id from invites where status = 1) i ON i.groupid = g.id;
Any ideas of what I am doing wrong?
Because members and invites are not related, you need to use two separate queries and use UNION (automatically removes duplicates) or UNION ALL (keeps duplicates) to get the output you desire:
select g.id as groupid, m.name, null as inviteid from groups g
join members m ON m.groupid = g.id
union all
select g.id, null, i.id from groups g
join invites i ON (i.groupid = g.id and i.status = 1);
Output:
groupid | name | inviteid
---------+-------+----------
1 | admin |
1 | other |
1 | | 3
1 | | 2
(4 rows)
Without a UNION, your query implies that the tables have some sort of relationship, so the columns are joined side-by-side. Since you want to preserve the null values, implying that the tables are not related, you need to concatenate/join them vertically with UNION
Disclosure: I work for EnterpriseDB (EDB)

Avoid duplicated records in nullable relationship

Given a player table:
-------------------------
| id | name | email |
-------------------------
and a friendship table:
------------------------------------
| status | playerId | friendId |
------------------------------------
I' am using the following query to retrieve player informations and status of relationship between players:
declare #id int = 1
select * from
(SELECT
p.id,
p.name,
p.email,
p.email_verified,
p.gender,
p.picture,
f1.playerId,
f1.friendId,
f1.status
FROM players p
LEFT OUTER JOIN friendships f1
ON
f1.playerId = p.id or f1.friendId = p.id) as pl
WHERE
pl.id <> #id
The results are the following:
id name email playerId friendId status
2 Nina el#gmail.com 2 1 1
2 Nina el#gmail.com 2 49 1
49 Ciccio testpast#gmail.com 2 49 1
In this case the user 2 is in friendship with user 1 and with another user, I need to display the first record, since is in relationship with the user id parameter, but I need also to retrieve all users, in friendship and not.
How can I do?
use row_number() window function
select * from (SELECT
p.id,
p.name,
p.email,
p.email_verified,
p.gender,
p.picture,
f1.playerId,
f1.friendId,
f1.status,
row_number() over(partition by p.id,
p.name,
p.email,f1.friendId order by id) rn
FROM players p
LEFT OUTER JOIN friendships f1
ON
f1.playerId = p.id or f1.friendId = p.id
) t where t.rn=1

How to count occurence of IDs and show this amount with name of item with this ID from other table in SQL?

if I have tables
Person: ID_Person, Name
Profession: ID_Prof, Prof_Name, ID_Person
If ID_Person appears multiple times in second table and I want to show all Person names with number of their professions how can I do this?
I know that if I want to count something I can write
SELECT ID_Person, count(*) as c
FROM Profession
GROUP BY ID_Person;
but don't know how to link it with column from other table in order to proper values.
Here is one way (MySQL InnoDB)
Person
+-----------+-------+
| ID_Person | Name |
+-----------+-------+
| 1 | bob |
| 2 | alice |
+-----------+-------+
Profession
+---------+--------------------+-----------+
| ID_Prof | Prof_Name | ID_Person |
+---------+--------------------+-----------+
| 1 | janitor | 1 |
| 2 | cook | 1 |
| 3 | computer scientist | 2 |
| 4 | home maker | 2 |
| 7 | astronaut | 2 |
+---------+--------------------+-----------+
select Name, count(Prof_Name)
from Person left join Profession
on (Person.ID_Person=Profession.ID_Person)
group by Name;
+-------+------------------+
| Name | count(Prof_Name) |
+-------+------------------+
| alice | 3 |
| bob | 2 |
+-------+------------------+
Hope this helps.
To just show those with multiple Profession then you would join the two tables, and aggregate with count() using group by and filter using having():
select pe.ID_Person, pe.Name, count(*) as ProfessionCount
from Person pe
inner join Profession pr
on pe.ID_Person = pr.ID_Person
group by pe.ID_Person, pe.Name
having count(*)>1
If you want to show the professions for those people as well:
select
multi.ID_Person
, multi.Name
, multi.ProfessionCount
, prof.ID_Prof
, prof.Prof_Name
from (
select pe.ID_Person, pe.Name, count(*) as ProfessionCount
from Person pe
inner join Profession pr
on pe.ID_Person = pr.ID_Person
group by pe.ID_Person, pe.Name
having count(*)>1
) multi
inner join Profession prof
on multi.ID_Person = prof.ID_Person
you can probably try something like this below. However, you will have to think about whether or not you need to left join versus inner join. You would want to left join if there is potentially someone who has not had any professions and therefore does not exist in the professions table.
SELECT pe.Name
, Professions = COUNT(pr.Prof_Name)
FROM dbo.Person (NOLOCK) pe
JOIN dbo.Profession (NOLOCK) pr ON pe.ID_Person = pr.ID_Person
GROUP BY pe.Name
You're looking for something like this I believe. The left join will bring in all the data and won't exclude any users.
The join can also be a inner join. Inner join would then only show users that exist in both tables.
LEFT
select x.ID_Person, count(x.ID_Person) as [count] from table1 x
left join table2 y on y.ID_Person= x.ID_Person
where x.ID_Person <> null
group by x.ID_Person
INNER
select x.ID_Person, count(y.ID_Person) from table1 x
inner join table2 y on y.ID_Person= x.ID_Person
group by x.ID_Person
The easiest solution is probably counting in a subquery:
select
id_person,
name,
(select count(*) from profession pr where pr.id_person = p.id_person) as profession_count
from person p;
You can achieve the same with an outer join:
select
p.id_person,
p.name,
coalesce(pr.cnt, 0) as profession_count
from person p
left join (select id_person, count(*) as cnt from profession group by id_person) pr
on pr.id_person = p.id_person;
It's usually a good idea to aggregate before joining. Anyway, this is how to join first and aggregate then:
select
p.id_person,
p.name,
coalesce(count(pr.id_person), 0) as profession_count
from person p
left join profession pr on pr.id_person = p.id_person
group by p.id_person, p.name;
As per standard SQL it would suffice to group by p.id_person, as the name functionally depends on the id (i.e. the id uniquely defines a person, so it's one single name belonging to it). Some DBMS however don't fully comply with the standard here and demand you to either put the name in the group by clause as shown or dummy-aggregate it in the select clause (e.g. max(p.name)) instead.

MSSQL - Cannot work out how to join three tables to find the building that contains people with the correct skills

I cannot for the life of me work out a query that will take a list of Skill_ID's and return a list of Buildings that contain a person/people with all of the skills searched for
I currently have these tables
Building
===========
ID | name
===========
1 | BlockA
2 | BlockB
People
============================
ID | name | Building_ID
============================
1 | PersonA | 1
2 | PersonB | 2
Skills
===========
ID | name
===========
1 | SkillA
2 | SkillB
SkillsToPerson
====================
Person_ID | Skill_ID
====================
1 | 1
1 | 2
2 | 2
For example, I want to find buildings that contain at least one person with SkillA and SkillB, BlockA should be returned, because Person1 has both skills, and is in BlockA
Can anyone offer some advice?
Thanks
You can do this using GROUP BY and HAVING:
SELECT b.Name AS Building
FROM Building b JOIN
People p
ON b.ID = p.Building_ID JOIN
SkillsToPerson sp
ON p.ID = sp.Person_ID
WHERE sp.skill_id IN (1, 2, 3) -- Skill IDs to look for
GROUP BY b.Name
HAVING COUNT(DISTINCT sp.skill_id) = 3; -- 3 skills
Note that you do not need the Skills table because you have the skills id in SkillsToPerson. Similarly, if you are happy with the building id, you don't need the building table.
I call this type of query a "set-within-sets" query, because you are looking for sets of something (skills) within another (buildings). GROUP BY and HAVING provide a very flexible method for handling this type of query.
How about:
SELECT DISTINCT Building.Name
FROM People
INNER JOIN Building
ON Building.ID = People.Building_ID
INNER JOIN SkillsToPerson
ON SkillsToPerson.Person_ID = People.ID
INNER JOIN Skills
ON Skills.ID = SkillsToPerson.Skills_ID
WHERE Skills.Skill_ID IN (1, 2, 3, ...) -- list of skills here
What about this?
SELECT *
FROM people p
LEFT JOIN building b ON p.building_id = b.id
LEFT JOIN skillstoperson s ON p.id = s.person_id
LEFT JOIN skill sn ON s.skill_id = sn.id
WHERE s.skill_id = #myskill;
SELECT * FROM BUILDING WHERE BUILDING_ID IN (SELECT BUILDING_ID FROM PEOPLE WHERE ID IN (SELECT PERSON_ID FROM SKILLSTOPERSON WHERE ID IN(SELECT ID FROM SKILLS WHERE SKILLNAME = 'required_skill')))

How to write query to join table A to table B OR C based on table A one column value?

Table PERSON
ID | NAME | PHNO |ADDRESS_TYPE
1 | XXXX | 999 | HOME
2 | YYYY | 888 | OFFICE
Table HOME_ADDRESS
ID PERSON_ID ADDRESS
1 | 1 | XXXXXXXXXXX
Table OFFICE_ADDRESS
ID PERSON_ID ADDRESS
1 | 2 | XXXXXXXXXXX
Here I want query to get record from table PERSON by joining HOME_ADDRESS and OFFICE_ADDRESS based on ADDRESS_TYPE, if ADDRESS_TYPE is HOME then it should get address details from table HOME_ADDRESS other wise address details should come from table OFFICE_ADDRESS.
I am using postgresql database.
it's mssql sintax, but try this:
select p.*, CASE WHEN p.ADDRESS_TYPE = 'HOME' THEN h.ADDRESS ELSE o.ADDRESS END from Person p
LEFT OUTER JOIN HOME_ADDRESS h on p.Id = h.PERSON_ID AND p.ADDRESS_TYPE = 'HOME'
LEFT OUTER JOIN OFFICE_ADDRESS o on p.ID = o.PERSON_ID AND p.ADDRESS_TYPE= 'OFFICE'
From the DB-Design-Perspecitve:
Maybe there is a Problem with your Database-Design.
Why isn't there one Address-Table?
Technically there are two things you need to do in order to achieve what you want:
Left-Join both Address-Tables
In the select-clause use CASE ... WHEN ... THEN ... ELSE ... END to conditionally use the right address-type.For Details see the PostgreSQL-Documentation
Example:
select p.ID,
case when p.ADDRESS_TYPE = 'HOME' then ha.address else oa.address end as address
from PERSON p
left join HOME_ADDRESS ha on p.ID = ha.PERSON_ID and p.ADDRESS_TYPE = 'HOME'
left join OFFICE_ADDRESS oa on p.ID = oa.PERSON_ID and p.ADDRESS_TYPE != 'HOME'