Join a table with another table with columns containing null - sql

I want to join users table with both Groupid and superadmingroupid from group table.
Superadmingroupid may be null
I tried below query but not working
SELECT U.Name, G.Name
FROM Groups G
INNER JOIN USERS U ON G.Groupid = U.Gid
LEFT JOIN USERs U2 On G.superadmingroupid= U.Gid
where U.Name='Mishrsa'
Group table
Groupid Gname SuperAdminGroupId
----- ------ --------
17 A 3
2 B null
3 C null
Users
------
id Name Gid
-- ------- ----
1 mishra 2
2 mishrsa 3
I want to diplay the user with groups that are referenced as groupid or superadmingroupid
Ex: User does not have groupid 17 but superadmingroupid 3 is there in users table so group 17 should come in the output
Output
Name GName
Mishra B
Mishra C
Mishra A

Solution for your problem is:
SELECT U.Name, G.GName
FROM Groups G
INNER JOIN USERS U
ON G.Groupid = U.Gid
OR G.superadmingroupid= U.Gid;
Working example: dbfiddle Link

I believe you should use UNION for that. (Maybe this is not the most elegant way).
The first part will give you the match between Groupid to Gid.
The second part will give you the match between SuperAdminGroupId to Gid.
The order is different then what you mentioned, and I do not know if it is important for you, but please try the below example:
SELECT U.Name, G.Name
FROM Groups G
JOIN Users U ON G.Groupid = U.Gid
UNION
SELECT U.Name, G.Name
FROM Groups G
JOIN Users U ON G.SuperAdminGroupId = U.Gid

Posting this answer just because I'd already written it before Bogner Boy posted their answer.
I changed the table names a touch because GROUP is a reserved word in SQL Server.
Bonger Boy's UNION might be more efficient for larger tables, but for smaller tables you'll be fine to use an OR or an IN:
CREATE TABLE AdminGroup
(
GroupId INTEGER,
Gname CHAR(1),
SuperAdminGroupId INTEGER
);
CREATE TABLE Users
(
Id INTEGER,
Name NVARCHAR(64),
Gid INTEGER
);
INSERT INTO Users (Id, Name, Gid) VALUES (1, 'mishra', 2);
INSERT INTO Users (Id, Name, Gid) VALUES (2, 'mishra',3);
INSERT INTO AdminGroup (GroupId, Gname, SuperAdminGroupId) VALUES (17, 'A', 3);
INSERT INTO AdminGroup (GroupId, Gname, SuperAdminGroupId) VALUES (2, 'B', null);
INSERT INTO AdminGroup (GroupId, Gname, SuperAdminGroupId) VALUES (3, 'C', null);
SELECT U.Name, G.GName
FROM Users U
INNER JOIN AdminGroup G ON U.Gid = G.SuperAdminGroupId OR U.Gid = G.GroupId;
--INNER JOIN AdminGroup G ON U.Gid IN (G.SuperAdminGroupId, G.GroupId);
Here's a DBFiddle:
https://www.db-fiddle.com/f/p1RA4z67SH1DijFMZyKRuA/0

Related

SQL to Select only full groups of data

Let's say I have three sample tables for groups of people as shown below.
Table users:
id
name
available
1
John
true
2
Nick
true
3
Sam
false
Table groups:
id
name
1
study
2
games
Table group_users:
group_id
user_id
role
1
1
teach
1
2
stdnt
1
3
stdnt
2
1
tank
2
2
heal
And I need to show to a user all groups that he participates in and also available right now, which means all users in that group have users.available = true.
I tried something like:
SELECT `groups`.*, `users`.* , `group_users`.*
FROM `groups`
LEFT JOIN `group_users` ON `groups`.`id` = `group_users`.`group_id`
LEFT JOIN `users` ON `users`.`id` = `group_users`.`user_id`
WHERE `users`.`available` = true AND `users`.`id` = 1
But it just shows groups and part of their users, that are available. And I need to have ONLY the groups that have all their users available.
If I were to find all available groups as User 1 - I should get only group 2 and it's users. How to do this the right way?
Tables DDL:
CREATE TABLE users (
id int PRIMARY KEY,
name varchar(256) NOT NULL,
available bool
);
CREATE TABLE teams (
id int PRIMARY KEY,
name varchar(256) NOT NULL
);
CREATE TABLE team_users (
team_id int NOT NULL,
user_id int NOT NULL,
role varchar(64)
);
INSERT INTO users VALUES
(1, 'John', true ),
(2, 'Nick', true ),
(3, 'Sam' , false);
INSERT INTO teams VALUES
(1, 'study'),
(2, 'games');
INSERT INTO team_users VALUES
(1, 1, 'teach'),
(1, 2, 'stdnt'),
(1, 3, 'stdnt'),
(2, 1, 'tank' ),
(2, 2, 'heal' );
mySQL select version() output:
10.8.3-MariaDB-1:10.8.3+maria~jammy
Check do you need in this:
WITH cte AS (
SELECT users.name username,
teams.id teamid,
teams.name teamname,
SUM(NOT users.available) OVER (PARTITION BY teams.id) non_availabe_present,
SUM(users.name = #user_name) OVER (PARTITION BY teams.id) needed_user_present
FROM team_users
JOIN users ON team_users.user_id = users.id
JOIN teams ON team_users.team_id = teams.id
)
SELECT username, teamid, teamname
FROM cte
WHERE needed_user_present
AND NOT non_availabe_present;
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=605cf10d147fd904fb2d4a6cd5968302
PS. I use user name as a criteria, you may edit and use user's identifier, of course.
Join the tables and aggregate with the conditions in the HAVING clause:
SELECT t.id, t.name
FROM teams t
INNER JOIN team_users tu ON t.id = tu.team_id
INNER JOIN users u ON u.id = tu.user_id
GROUP BY t.id
HAVING MIN(u.available) AND SUM(u.id = 1);
The HAVING clause is a simplification of:
HAVING MIN(u.available) = true AND SUM(u.id = 1) > 0
See the demo.
first you need to find those group which users is available. then find the all the group details of those group which is not related to those group which user is available.
SELECT * FROM team_users a
JOIN teams b ON a.team_id=b.id
JOIN users c ON a.user_id=c.id
WHERE NOT EXISTS
(
SELECT 1 FROM team_users tu
JOIN users u ON tu.user_id=u.id AND u.available =1
WHERE tu.team_id=a.Team_id
)

Get room members, room's owner and admin at the same time in one query with grouped by id (unique) on PostgreSQL 12

I want to get room's member list, room's owner member in case of he doesn't exists in other table and admin member at the same time. Currently i fetch them individually.
CREATE TABLE public.room_members (
id bigint NOT NULL,
member_id bigint,
room_id bigint,
group_id bigint
);
CREATE TABLE public.rooms (
id bigint NOT NULL,
member_id bigint,
group_id bigint,
name varchar(128)
);
CREATE TABLE public.members (
id bigint NOT NULL,
group_id bigint,
username varchar(128),
is_admin bool default false
);
CREATE TABLE public.groups (
id bigint NOT NULL,
name varchar(128)
);
-- My Group created
INSERT INTO "groups" (id, name) VALUES (1, 'My Group');
-- Create users for this group. We have 4 users/members
INSERT INTO "members" (id, group_id, username, is_admin) VALUES (1, 1, 'Pratha', true);
INSERT INTO "members" (id, group_id, username) VALUES (2, 1, 'John');
INSERT INTO "members" (id, group_id, username) VALUES (3, 1, 'Mike');
INSERT INTO "members" (id, group_id, username) VALUES (4, 1, 'April');
-- April creates a room and he is owner of this room
INSERT INTO "rooms" (id, group_id, member_id, name) VALUES (1, 1, 4, 'My Room'); -- 4 is April
-- April also adds Mike to the room members. But she does not add herself. As she is owner.
INSERT INTO "room_members" (id, group_id, room_id, member_id) VALUES (1, 1, 1, 3); -- 3 is Mike
What I want is:
room_members list of 'My Room' (Which is only Mike at the moment)
My Room's owner in case of he didn't add himself to room_members table. Because he is the owner of that room (Which is April)
Plus, admin member (Which is Pratha)
And this should be unique. For example, if user add himself to room_members and also owner then it should fetch member one time only.
What I tried so far?
select * from members
left outer join room_members cm on cm.member_id = members.id
left outer join rooms c on c.id = cm.room_id
where c.name = 'My Room' or members.id = 1
I couldn't use group by here either. Also i don't need the all fields. Just room_members table fields only.
See here: https://rextester.com/XWDS42043
Expected output for room_members:
+-------------+------------+------------+
| member_id | group_id | username |
+-------------+------------+------------+
| 1 | 1 | Pratha |
+-------------+------------+------------+
| 3 | 1 | Mike |
+-------------+------------+------------+
| 4 | 1 | April |
+-------------+------------+------------+
Pratha: Because he is ADMIN
Mike: Because he is member of My Room. MEMBER
April: Because she created that room. OWNER
room_members can be many. I just added only Mike but it can have multiple members including admins and owners.
You can address this with UNION:
-- list the admin(s) of the room group
select m.id, m.group_id, m.username
from rooms r
inner join members m on m.group_id = r.group_id and m.is_admin = true
where r.name = 'My Room'
union
-- list the members of the room
select m.id, m.group_id, m.username
from rooms r
inner join room_members rm on r.id = rm.room_id
inner join members m on rm.member_id = m.id
where r.name = 'My Room'
union
-- recover the room owner
select m.id, m.group_id, m.username
from rooms r
inner join members m on r.member_id = m.id
where r.name = 'My Room'
UNION eliminates duplicates accross queries, so if a user is both member and/or group admin and/or owner of the room, they will only appear once.
In your fiddle, this query returns:
id group_id username
1 4 1 April
2 3 1 Mike
3 1 1 Pratha

Inner join an inner join with another inner join

I'm wondering if it is possible to inner join an inner join with another inner join.
I have a database of 3 tables:
people
athletes
coaches
Every athlete or coach must exist in the people table, but there are some people who are neither coaches nor athletes.
What I am trying to do is find a list of people who are active (meaning play or coach) in at least 3 different sports. The definition of active is they are either coaches, athletes or both a coach and an athlete for that sport.
The person table would consist of (id, name, height)
the athlete table would be (id, sport)
the coaching table would be (id, sport)
I have created 3 inner joins which tell me who is both a coach and and an athlete, who is just a coach and who is just an athlete.
This is done via inner joins.
For example,
1) who is both a coach and an athlete
select
person.id,
person.name,
coach.sport as 'Coaches and plays this sport'
from coach
inner join athlete
on coach.id = athlete.id
and coach.sport = athlete.sport
inner join person
on athlete.id = person.id
That brings up a list of everyone who both coaches and plays the same sport.
2) To find out who only coaches sports, I have used inner joins as below:
select
person.id,
person.name,
coach.sport as 'Coaches this sport'
from coach
inner join person
on coach.id = person.id
3) Then to find out who only plays sports, I've got the same as 2) but just tweaked the words
select
person.id,
person.name,
athlete.sport as 'Plays this sport'
from athlete
inner join person
on athlete.id = person.id
The end result is now I've got:
1) persons who both play and coach the same sport
2) persons who coach a sport
3) persons who play a sport
What I would like to know is how to find a list of people who play or coach at least 3 different sports? I can't figure it out because if someone plays and coaches a sport like hockey in table 1, then I don't want to count them in table 2 and 3.
I tried using these 3 inner joins to make a massive join table so that I could pick the distinct values but it is not working.
Is there an easier way to go about this without making sub-sub-queries?
What I would like to know is how to find a list of people who play /
coach at least 3 different sports? I can't figure it out because if
someone plays and coaches a sport like hockey in table 1, then I don't
want to count them in table 2 and 3.
you can do something like this
select p.id,min(p.name) name
from
person p inner join
(
select id,sport from athlete
union
select id,sport from coach
)
ca
on ca.id=p.id
group by p.id
having count(ca.sport)>2
CREATE TABLE #person (Id INT, Name VARCHAR(50));
CREATE TABLE #athlete (Id INT, Sport VARCHAR(50));
CREATE TABLE #coach (Id INT, Sport VARCHAR(50));
INSERT INTO #person (Id, Name) VALUES(1, 'Bob');
INSERT INTO #person (Id, Name) VALUES(2, 'Carol');
INSERT INTO #person (Id, Name) VALUES(2, 'Sam');
INSERT INTO #athlete (Id, Sport) VALUES(1, 'Golf');
INSERT INTO #athlete (Id, Sport) VALUES(1, 'Football');
INSERT INTO #coach (Id, Sport) VALUES(1, 'Tennis');
INSERT INTO #athlete (Id, Sport) VALUES(2, 'Tennis');
INSERT INTO #coach (Id, Sport) VALUES(2, 'Tennis');
INSERT INTO #athlete (Id, Sport) VALUES(2, 'Swimming');
-- so Bob has 3 sports, Carol has only 2 (she both coaches and plays Tennis)
SELECT p.Id, p.Name
FROM
(
SELECT Id, Sport
FROM #athlete
UNION -- this has an implicit "distinct"
SELECT Id, Sport
FROM #coach
) a
INNER JOIN #person p ON a.Id = p.Id
GROUP BY p.Id, p.Name
HAVING COUNT(*) >= 3
-- returns 1, Bob
I have created a SQL with some test data - should work in your case:
Connecting the two results in the subselect with UNION:
UNION will return just non-duplicate values. So every sport will be just counted once.
Finally just grouping the resultset by person.Person_id and person.name.
Due to the HAVING clause, just persons with 3 or more sports will be returned-
CREATE TABLE person
(
Person_id int
,name varchar(50)
,height int
)
CREATE TABLE coach
(
id int
,sport varchar(50)
)
CREATE TABLE athlete
(
id int
,sport varchar(50)
)
INSERT INTO person VALUES
(1,'John', 130),
(2,'Jack', 150),
(3,'William', 170),
(4,'Averel', 190),
(5,'Lucky Luke', 180),
(6,'Jolly Jumper', 250),
(7,'Rantanplan ', 90)
INSERT INTO coach VALUES
(1,'Football'),
(1,'Hockey'),
(1,'Skiing'),
(2,'Tennis'),
(2,'Curling'),
(4,'Tennis'),
(5,'Volleyball')
INSERT INTO athlete VALUES
(1,'Football'),
(1,'Hockey'),
(2,'Tennis'),
(2,'Volleyball'),
(2,'Hockey'),
(4,'Tennis'),
(5,'Volleyball'),
(3,'Tennis'),
(6,'Volleyball'),
(6,'Tennis'),
(6,'Hockey'),
(6,'Football'),
(6,'Cricket')
SELECT person.Person_id
,person.name
FROM person
INNER JOIN (
SELECT id
,sport
FROM athlete
UNION
SELECT id
,sport
FROM coach
) sports
ON sports.id = person.Person_id
GROUP BY person.Person_id
,person.name
HAVING COUNT(*) >= 3
ORDER BY Person_id
The coaches & athletes, ie people who are coaches or athletes, are relevant to your answer. That is union (rows in one or another), not (inner) join rows in one and another). (Although outer join involves a union, so there is a complicated way to use it here.) But there's no point in getting that by unioning only-coaches, only-athletes & coach-athletes.
Idiomatic is to group & count the union of Athletes & Coaches.
select id
from (select * from Athletes union select * from Coaches) as u
group by id
having COUNT(*) >= 3
Alternatively, you want ids of people who coach or play a 1st sport and coach or play a 2nd sport and coach or play a 3rd sport where the sports are all different.
with u as (select * from Athletes union select * from Coaches)
select u1.id
from u u1
join u u2 on u1.id = u2.id
join u u3 on u2.id = u3.id
where u1.sport <> u2.sport and u2.sport <> u3.sport and u1.sport <> u3.sport
If you wanted names you would join that with People.
Is there any rule of thumb to construct SQL query from a human-readable description?](https://stackoverflow.com/a/33952141/3404097)

How to let COUNT show zeros in tables with many to many relationship?

the following query does not show the Groups where no users belong to.
I would like to have the shown with a count of 0 too. How do I do this?
Like this should it be
Group A 8
Group B 0
Group C 2
This is it now
Group A 8
Group C 2
SELECT UsersToGroups.GroupID,
groups.Group,
COUNT(UsersToGroups.UserID) AS countUsersPerGroup
FROM users_Groups AS groups
LEFT JOIN AssociationUsersToGroups AS UsersToGroups ON
UsersToGroups.GroupID =
groups.ID
LEFT JOIN users_Users AS users ON
UsersToGroups.UserID =
users.ID
GROUP BY GroupID,
groups.Group
ORDER BY groups.Group ASC
Query will select all groups
SELECT groups.ID,
groups.Group,
FROM users_Groups AS groups
If you add LEFT JOIN AssociationUsersToGroups you should receive groups with number of participants:
SELECT groups.ID,
groups.Group,
COUNT(UsersToGroups.UserID) AS countUsersPerGroup
FROM users_Groups AS groups
LEFT JOIN AssociationUsersToGroups AS UsersToGroups ON
UsersToGroups.GroupID =
groups.ID
GROUP BY groups.ID, groups.Group
First of all i don't see why you need to join the user table?
There's no need to assuming that you have a foreign key relationship between "users" and "users-to-group" association
table with ON DELETE CASCADE
This works for me:
-- setting up test-tables and test-data
create table #Groups
(
ID int,
GroupName varchar(100)
)
create table #UsersToGroup
(
GroupID int,
UserID int
)
insert into #Groups
values (1,'Group A'),(2,'Group B'),(3,'Group C')
insert into #UsersToGroup
values (1,1),(1,2),(1,3),(1,4),(1,5),(1,6),(1,7),(1,8),(3,1),(3,2)
-- the query you want:
select g.ID as GroupID,
g.GroupName,
count(utg.UserID) as countUsersPerGroup
from #Groups g
left join #UsersToGroup utg on g.ID = utg.GroupID
group by g.ID, g.GroupName
order by g.GroupName asc
-- cleanup
drop table #Groups
drop table #UsersToGroup
output:

SQL Query JOIN or IN operator?

I have two tables,
PERSON
and FRIENDS.
FRIENDS has the fields NAME and SURNAME.
A person has N friends.
I want to retrieve all the PERSONs that have atleast two FRIENDs, one with name ="mark", and the other with name="rocco" and surname ="siffredi".
Example: if I have a person that has 5 friends, one of them is called mark and no one is called rocco siffredi, no tables are returned.
I was thinking about:
SELECT * FROM person p
JOIN friends AS f ON p.ID=f.personID
WHERE f.name ="mark" AND f IN
( SELECT * from FRIENDS WHERE name="rocco" and surname="siffredi")
or
SELECT * FROM person p
JOIN friends AS f1 ON p.ID=f1.personID
JOIN friends AS f2 ON p.ID=f2.personID
WHERE f1.name="mark" AND f2.name="rocco" AND f2.surname="siffredi"
What is the best way? I mean the fastest way to execute it.
I don't care about readability.
Is there any other way to execute this query?
Ty.
EDIT: added the join on the ID...
I had to guess your column names and make up a table:
Use EXISTS:
CREATE table FRIENDS(person_id INT, friend_id INT)
go
SELECT *
FROM person
WHERE
EXISTS
(SELECT *
FROM friends f
JOIN person per
ON f.friend_id = per.id
WHERE
per.name ='mark' AND
person.id = f.person_id) AND
EXISTS
(SELECT *
FROM friends f
JOIN person per
ON f.friend_id = per.id
WHERE
per.name = 'rocco' AND
per.surname='siffredi' AND
person.id = f.person_id)
Your schema design isn't very good for what you are trying to do... I would have a Person table as you have, which would also contain a unique identifier called PersonId. I would then have a Friends table which took two fields - Person1Id and Person2Id.
This gives you a couple of important advantages - first of all your system is able to handle more than one bloke called John Smith (because we join on Ids rather than Names...). Secondly, a person's details are only ever recorded in the Person table. One definition of truth...
With these data as input:
INSERT INTO Person VALUES
(1, 'Bob', 'Smith'),
(2, 'Jim', 'Jones')
INSERT INTO Friends VALUES
(1, 1, 'Mark', 'Tally'),
(2, 1, 'John', 'Smith'),
(3, 1, 'Jack', 'Pollock'),
(4, 2, 'Mark', 'Rush'),
(5, 2, 'Rocco', 'Siffredi'),
(6, 2, 'Mark', 'Bush')
you can use this query:
SELECT PersonId, COUNT(*) AS NoOfFriends
FROM (
SELECT DISTINCT PersonId, Name,
Surname = CASE WHEN NAME = 'Mark' THEN NULl
ELSE Surname
END
FROM Friends
WHERE Name = 'Mark' OR (Name = 'Rocco' AND Surname = 'Siffredi') ) t
GROUP BY PersonId
to get the distinct number of required friends per PersonID:
PersonId NoOfFriends
------------------------
1 1
2 2
You can now join with the above table expression on PersonId and filter it by NoOfFriends:
SELECT p.*
FROM Person AS p
INNER JOIN (
SELECT PersonId, COUNT(*) AS NoOfFriends
FROM (
SELECT DISTINCT PersonId, Name,
Surname = CASE WHEN NAME = 'Mark' THEN NULl
ELSE Surname
END
FROM Friends
WHERE Name = 'Mark' OR (Name = 'Rocco' AND Surname = 'Siffredi') ) t
GROUP BY PersonId ) s ON s.PersonId = p.ID
WHERE s.NoOfFriends = 2
so as to get only persons having the required combination of associated friends:
ID Name Surname
-------------------
2 Jim Jones
P.S. I have completely re-written my answer after #t-clausen.dk's comment.